20200921のJavaに関する記事は14件です。

【Java】Enumでif文を少なくする

はじめに

保守・開発でプログラムに触れる際、その8割はソースコードを読む時間に費やされるというのはよく聞く話です。
そのため、可読性の高く、保守性の高いソースコードというのは常に意識されるべきですが、どうしても業務システムとなると、ある契約の種類だとか、取引先の種類だとか、種類による条件分岐がどうしても多くなりがちだと感じています。
そこで、if文を少なくなるする方法の1つとしての、Enumを使用した方法があるということを知ったので、忘れないように投稿します。

if文を少なくするメリット

if文による条件分岐が多い場合、Enumを使用することでとても読みやすくなります。

具体例

SampleEnum.java
public enum SampleEnum {

    kind1(new Kind1()),
    kind2(new Kind2()),
    kind3(new Kind3()),
    kind4(new Kind4());

    SuperKind kind;

    private SampleEnum(SuperKind kind) {
        this.kind = kind;
    }

    public void execute(Object obj) {
        kind.execute(obj);
    }
}

まずはEnumを用意します。
次に、各プロパティに対して、実行したい処理クラスを記載し、コンストラクタを定義します。
最後に、実行したい処理クラスの実行メソッドを定義します。

Enum自体は、この3ステップで実装完了です。
あとは、種類が追加された場合は、プロパティを追加して、その種類ごとに実行したい処理クラスを記載するだけです。
 

Kind1.java
public class Kind1 implements SuperKind {

    public void execute(Object obj){

        if(!(obj instanceof String)) {
            System.out.println("引数には文字列を指定してください");
        }
        System.out.println("Kind1のクラスで" + obj);
    }
}

実行したい処理クラスです。
ここでは簡単に、そのクラスで実行されたことがわかる文を、コンソールに出力するようにします。
SuperKindは、interfaceです。ここでは中身はexecuteメソッドを定義しているだけなので、割愛します。
 

Main.java
public class Main {

    public static void main(String[] args) {

        SampleEnum test1 = SampleEnum.valueOf("kind1");
        SampleEnum test2 = SampleEnum.valueOf("kind2");
        SampleEnum test3 = SampleEnum.valueOf("kind3");
        SampleEnum test4 = SampleEnum.valueOf("kind4");

        String string = "Enumのテスト";

        test1.execute(string);
        test2.execute(string);
        test3.execute(string);
        test4.execute(string);
    }
}

mainクラスです。ここで実行した結果は、以下の通りになります。

Kind1のクラスでEnumのテスト
Kind2のクラスでEnumのテスト
Kind3のクラスでEnumのテスト
Kind4のクラスでEnumのテスト

最後に

このような実装をすることで、Factoryクラスでのif文判定を減らすこともできれば、どの業務ロジック処理を呼び出すかなどのif文判定を減らしたりなど、活用の幅は大きいかと感じました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Eclipse】補完機能を使いたいけど、スペースで補完を確定してしまうのをなんとかしたい

現象

使用しているエディタ
Eclipse 4.8.0
macOS Catalina 10.15.6

Eclipseの補完機能を有効にして、トリガーには.abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
を当てている。

String str =

と入力したいのに、strの後に押すspaceキーで補完が確定される。つまり、自分で決める変数名でも意図しない補完が適応されてしまうので、バックスペースで毎回消去しないといけなくなる。いやだ〜

string.gif

解決

自動有効化遅延を500に設定

設定.png

環境設定ページはショートカット「⌘,」で開く。
上の画像のように、設定を変更します。

string_after.gif

補完候補が表示されるまでに遅延があるので、すばやくSpaceキーを押せば大丈夫でした。

新しいバージョンのEclipseではEnterキーだけで確定をするというチェックボックスがあるみたいですが、自分の(Eclipse4.8.0)はなかったです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

2020年に最も人気があるプログラミング言語 10選

元記事: https://jp.scrapestorm.com/tutorial/10-most-popular-programming-languages-in-2020/

プログラミングの初心者について、より良い見通しがある言語を選択するは非常に重要です。本文は十種類の2020年に最も人気があるプログラミング言語を紹介します。

まずTIOBEランキングを見てみます。非常に権威のあるランキングです。次の14つは長く生きているので、SQLがGOに置き換えられたことを除いて、他の9つのプログラミング言語がすべて存在し、長い間リストを支配していることがわかります。これから一つずつ紹介しましょう。
無題.png

1.Java

Javaは実際にはC ++の代替品です。Sunは当初、C ++よりも簡単なオブジェクト指向プログラミング言語を開発したいと考えていました。時間の経過とともに、Javaは学習とクロスプラットフォーム化が容易になるため、Javaの人気はC ++の人気をはるかに超えています。
Java仮想マシンの助けを借りて、JavaはLinux、Windows、Mac-OSなどのさまざまなオペレーティングシステムで自由に使用できるため、エンタープライズレベルの開発で非常に人気があります。

2.C++

名前からわかるように、C ++はC言語の拡張機能であり、C言語のオブジェクト指向関数の作成を目的としています。
C ++はすべてのプラットフォームで実行でき、あらゆるタイプのハードウェアを効果的に利用できるため、リソースが限られたプラットフォームで最高のパフォーマンスを発揮できます。

3.C

C言語は、1972年にAT&Tベル研究所のデニス・リッチーが主体となって開発した汎用プログラミング言語である。それは、システムリソースを効果的に使用できるを目指します。当時、メモリのすべてのバイトが高価だったからです。C言語は非常に早い時期に誕生しましたが、依然として最も一般的に使用されているプログラミング言語の1つです。
C ++と同様に、Cもメモリに直接アクセスしてハードウェアを制御できます。 Cはオペレーティングシステムと密接に関連しており、プログラマーはメモリ割り当ての詳細を処理する必要があるため、把握するのが困難です。

4.Python

学習コストは非常に低いため、すべてのプログラマーはPythonを好みますが、現在の非常に深い人工知能、機械学習、データ分析などのアプリケーションレベルは非常に高くなっています。
Pythonの構文はシンプルでエレガントで、コミュニティも非常に活発です。 しかし、言うべきことの1つは、Pythonの職位には高い学歴が必要であることです。

5.JavaScript

JavaScriptは高級なダイナミンクプログラミング言語です。非常に人気のあるフロントエンドフレームワークVue.jsはjsJavaScriptで作成しました。フロントエンド開発に従事したい場合は、JavaScriptは必須であると言えます。

6.C

当初、C#はJavaのコピーと見なされていましたが、それらには著しい類似点があり、Javaとほぼ同じ構文であり、コンパイルして実行する必要があります。 時間の開発とMicrosoftによる多大な努力により、C#は豊富なクラスライブラリとフレームワークを蓄積し、開発者はこれに基づいて.NETプラットフォームに基づくさまざまなアプリケーションをすばやく作成できます。

7.Swift

Swiftは、Appleによって作成された強力で直感的なプログラミング言語であり、iOS、Mac、Apple TV、およびApple Watch用のアプリの開発に使用できます。 開発者に完全な自由を提供することを目的としています。 Swiftは使いやすくオープンソースであり、アイデアさえあれば、誰でも素晴らしいものを作成できます。

8.Go

Go言語のデザインは非常に洗練されており、使用方法も非常に簡単で、開発と拡張を解決する能力も非常に優れています。 重要なのは、学習が非常に簡単であり、これらの利点がGo言語の急速な成長に貢献していることです。
Google、AWS、Cloudflare、CoreOSなどはすべて、クラウドコンピューティング関連製品の開発にGolangを大規模に使用し始めています。 未来はとても明るいと言えます。

9.PHP

PHPがWebアプリケーションを開発したのは35年以上の歴史があります。PHPは常にWeb開発の王様でした。特に、WordPressなどのコンテンツ管理プラットフォームの人気とFacebook(PHPが開発した)の支持が相まって、業界でのPHPの地位が強化されました。

10.Ruby

Rubyはもともとオブジェクト向けのスクリプトプログラミング言語でしたが、時間が経つにつれて、徐々に解釈された高水準の汎用プログラミング言語に発展しました。 開発者の生産性を向上させるのに非常に役立ちます。シリコンバレーでは、Rubyは非常に人気があり、クラウドコンピューティング時代のWebプログラミング言語として知られています。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Java static 【個人まとめ】

【Java static】

Java static について理解が浅かったのでいろいろ調べてまとめました。
ご指摘など頂けると嬉しいです。

随時追加して、備忘録として使っていきます。

staticメソッド

このメソッドは、あるクラスのどのインスタンスから呼ばれても処理内容が変わらない。
staticメソッドは、インスタンス化による影響を受けない。

クラス名.メソッド名の形で、インスタンスなしで呼び出せる。
Javaのmainメソッドには、static(最初だから)が必ず必要である。

非staticメソッド

staticを使わない変数は、クラスをインスタンスしないと使えない。
→非static変数

変数

・クラス変数 クラス内 グローバル変数
→インスタンス全て(クラスから作られた)で共有される。

・インスタンス変数 インスタンス固有 ローカル変数
→インスタンス1個の中だけで使われる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Kotlin について調べたこと

Kotlin について調べたこと

kotlinとは

Kotlinは、IntelliJ IDEAで有名なJetBrainsが開発したオブジェクト指向プログラミング言語です。
コンパイルされたコードはJVM上で動作するため、
これまでJAVAで作成した資産を流用できる様になっています。

Kotlinの基本構文

変数宣言

// val で宣言すると値の再代入ができなくなる  
val firstName: String = "Tanaka"  
// 型推論でいちいち型の定義がいらない  
val lastName = "Taro"  

// var で宣言すると再代入ができる  
var age = 20  
age = 21

配列

// 配列(array)を作成  
val nameList: Array<String> = arrayOf("tanaka", "saitou", "kimura")  
println(nameList[0]) // tanaka が出力  

// 配列の要素の書き換え  
nameList[1] = "sakurai"  
println(nameList[1]) // sakurai が出力  

// 配列(リスト)を作成  
val animalList: List<String> = listOf("dog", "cat", "rabbit")  
println(animalList[0]) // dog が出力  

// 配列の要素の書き換え  
animalList[1] = "tiger" // エラー インターフェイスListは読み取り専用

List でも mutableListOf() で変更可能なリストができるので Array と List の使い分けがいまいちわからないです。

マップ

// マップ作成  
val numberMap: MutableMap<String, Int> = mutableMapOf("one" to 1)  
println(numberMap["one"]) // 1 が出力  
//// 値の追加  
numberMap["tow"] = 2  
println(numberMap)        // {one=1, tow=2} が出力  

// mapOf でマップ作成  
val reNumberMap: Map<Int, String> = mapOf(1 to "one")  
println(reNumberMap[1])   // 1 が出力  
//// 値の追加  
reNumberMap["tow"] = 2    // エラー mapOf は 読み取り専用

条件分岐

// 普通のif文  
if (true) {  
    println("if")   // if が出力  
} else {  
    println("else")  
}  

// Kotlinに三項演算子はないのでそれっぽいの  
val animal = "dog"  
val isDog = if (animal == "dog") true else false  
println(isDog)      // true が出力  

// 空チェックとかなら  
val person:String? = null  
val personName = person?: "NoName"  
println(personName) // NoName が出力  

// when文 switch文と同じような感じ  
val result = when("hoge") {  
    "hoge" -> "hoge"  
    "fuga" -> "fuga"  
     else -> "else"  
}  
println(result)     // hoge が出力

ループ処理

// while 文  
var count = 5  
while(0 < count--) {  
    println("while count: ${count}")  
}  

// for 文  
for (i in 1..5) {  
    println("for count: ${i}")  
}

JAVA と Kotlin の書き比べ

userクラスに年齢、名前、性別を加え、
自己紹介メソッドを実行するコードを書いてみました。

Kotlin

user.kt

fun main() {  
    val user = userData(20, "Taro", "Men")  
    user.selfIntroduction()  
}

userData.kt

data class userData (  
    var age: Int? = 0,  
    var name: String = "NoName",  
    var gender: String? = null  
) {  
    fun selfIntroduction() {  
        println("My name is ${name}")  
        println("Age is ${age}")  
        println("Gender is ${gender}")  
    }  
}

JAVA

user.java

public class user {  
    public static void main(String[] args) {  
        userData user = new userData(20, "Taro", "Men");  
        user.selfIntroduction();  
    }  
}

userData.java

public class userData {  
    private int age;  
    private String name;  
    private String gender;  

    public userData(int age, String name, String gender) {  
        this.age = age;  
        this.name = name;  
        this.gender = gender;  
    }  

    public void selfIntroduction() {  
        System.out.println("My name is " + this.name);  
        System.out.println("Age is " + this.age);  
        System.out.println("Gender is " + this.gender);  
    }  
}

記述して感じた違いとしては

  • コンストラクタの作成に New がいらない
  • 型推論の機能により必ず型の定義をする必要がない
  • 末尾のセミコロンが必須ではない
  • 文字列リテラルの中に式を入れることができる

Spring Boot で Hello World

環境

Windows10 Pro
IntelliJ Community Edition 2020.2
JAVA 14

プロジェクトの雛形を作る

Spring Initializr を用いてプロジェクトの雛形を入手する。

設定は
Project : Gradle Project
Language : Kotlin
Spring Boot : 2.3.4
Project Metadata : デフォルトのまま
Packaging Jar
Java : 14
Dependencies : Spring Web

chrome_8cN8PNZDsH.png

最後に、GENERATE を押下してプロジェクトの雛形をダウンロード

作成したプロジェクトの雛形をビルド

IntelliJ から File→Open...→ダウンロードしたプロジェクトの雛形を選択→OK の手順で展開

コントローラを作成

demo/src/main/kotlin/com.example.demo/controller フォルダーに対して
右クリック→New→Kotlin File/Class を押下
HelloController.kt を作成する。

HelloController.kt

package com.example.demo.controller

import org.springframework.web.bind.annotation.RestController
import org.springframework.web.bind.annotation.RequestMapping

@RestController
public class HelloController {
    @RequestMapping("/")
    fun hello():String {
        return "Hello World"
    }
}

Webサーバの起動

idea64_VFRC5iORoS.png

右上の 再生ボタン を押下し、ビルドを行う

接続

ビルドが終わったら http://localhost:8080 に接続

chrome_Dk8vUW6lcC.png

感想

筆者はJAVAをあまり触ったことが無いので、
JAVAを知らないでいきなりKotlinって大丈夫なのかなと不安を感じていたのですが、
いきなりKotlinから書き始めてみたところ、いきなりKotlinでも問題なく
むしろKotlinから始めたほうがとっつきやすいと感じました。
JAVAと書き比べてみたところ、Kotlinでのコード量が少なくなり、
型推論やデータクラス等がコードを簡潔の手助けになっていると感じました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Azure Batch で Java アプリケーションを実行する

はじめに

Azure Batch はオートスケール可能な仮想マシン (VM) 上で任意のスクリプトやアプリケーションを実行できる便利なサービスです。VM に JVM (Java Virtual Machine) がインストールされていれば Java アプリケーションも実行することができます。

公式ドキュメントでは .NET や Python アプリケーションについてのクイックスタートがありますが Java のドキュメントは現状無いため、本記事では、Azure Batch 上で Java アプリケーションを実行する流れを簡単にまとめたいと思います。

大筋は以下のクイックスタートの記事に沿っていますので、合わせてご覧ください。
Azure クイックスタート: Azure portal で最初の Batch ジョブを実行する - Azure Batch | Microsoft Docs

Batch 関連リソース作成

それでは順に Batch 関連のリソース (アカウント、プール、ジョブ、タスク) を作成していきます。各リソースの大まかな解説は以下の通りです。

  • アカウント : Azure Batch の最上位のリソースです。
  • プール : VM の集合であり、計算資源としての役割を持ちます。1 つのアカウントに複数のプールを作成することができます。
  • ジョブ : 計算の単位であるタスクの集合です。ジョブは 1 つまたは複数のプールに関連付けることができます。配下のタスク群は、ジョブに関連付けられたプールを計算資源として利用します。
  • タスク : 計算の単位です。アプリケーションの実行コマンドなどを定義します。

Batch アカウント作成

Azure ポータルの検索ボックスで [Batch] と入力すると [Batch アカウント] が候補に表示されますのでクリックします。
image.png

新しい Batch アカウントの画面では任意のリソースグループ名やアカウント名などを入力します。Batch アカウントにストレージアカウントを関連付けることができますので [ストレージ アカウントの選択] のリンクをクリックします。
image.png

既存のストレージアカウント、または新規作成を選ぶことができます。ここでは新規でストレージアカウントを作成します。特にこだわりが無ければ推奨されている StorageV2 (汎用 v2) を選択します。レプリケーションは要件に合わせて任意のレベルを設定ください (ここでは最もコストが安いローカル上長ストレージを選択しています)。
image.png

後の設定は任意です。問題なければ作成します。
image.png

プールの追加

Batch アカウントの左側メニュー [プール] にて [追加] をクリックします。
image.png

ここで VM (の集合) を作成するため、設定項目がかなり多いです。まずイメージですが、様々な種類を選ぶことができます。ここでは以下の内容を選択しています。

  • 発行者 : canonical
  • オファー : ubuntuserver
  • SKU : 18.04-lts

image.png

選択可能なイメージについては本記事末尾に表形式でまとめています。
APPENDIX - 選択可能なイメージの種類

選択可能な VM サイズは以下のドキュメントをご覧ください。
プールの VM サイズを選択する - Azure Batch | Microsoft Docs

ここでは VM サイズはデフォルトの [Standard A1] を選択しています。スケーリングは台数固定か自動スケールを選択できますが、ここでは [固定] とし、1 台の VM が起動するようにしています。
開始タスクにて VM が起動する際に実行するコマンドを定義できます。[有効] を選択します。
image.png

(開始タスク)
先に選択した Ubuntu 18.04-lts のイメージには JVM がインストールされていないため、開始タスクの [コマンドライン] に JVM のインストールコマンド apt install -y openjdk-11-jdk を定義します。合わせて [ユーザーID] を [プール autouser、管理者] に変更します。残りの設定は任意で、問題なければ [OK] をクリックします。
image.png

プールの追加では他にも仮想ネットワークなど色々な設定ができますが、ここでは最小限とし [OK] をクリックします。
image.png

これでプールの作成は完了です。専用ノードが 0 -> 1 になっていますが VM の起動中を意味します。これが 1 になったら VM の起動は完了です。もし 1 にならない場合はノードの起動に失敗しています。開始タスクが正しく設定されていない可能性などがあるため、ミスなどが無いか見直してみましょう。
image.png

ジョブの追加

Batch アカウントの左側メニュー [ジョブ] にて [追加] をクリックします。
image.png

任意のジョブ ID、関連付けるプールを選択し [OK] をクリックします。
image.png

モード & 詳細設定

モードと詳細設定でより細かい制御を行うことができますが、ここでは特に設定せずに参考としてキャプチャだけ載せておきます。
image.png
image.png

タスクの追加 (Java 動作確認)

Java が問題なくインストールされていることを確認するため、Java のバージョン確認のコマンドをタスクにて実行していきます。

先に作成したジョブをクリックします。
image.png

タスクの追加をクリックします。
image.png

任意 & ジョブ内で一意なタスク ID を入力、コマンドラインに java --version を入力し、[送信] をクリックします。
image.png

すると、タスクはジョブのキューに送信され、アクティブ → 実行中 → 完了という状態遷移をします。他のタスクが実行されていない状態のためあまり待たずにタスクの実行が完了するはずです。詳細を確認するため送信したタスクをクリックします。
image.png

正常にタスクが完了していれば、標準エラーへの出力結果「stderr.txt」や標準出力への出力結果「stdout.txt」などが表示されるはずです。stdout.txt をクリックします。
image.png

以下のように Java のバージョン情報が出力されていれば OK です。
これで Java アプリケーションを実行するための Batch 関連リソースの準備が整いました。
image.png

Java アプリケーションの実行

ここから本題の Java アプリケーションの実行に入ります。実行可能 JAR ファイルをストレージアカウントにアップロードし、その JAR ファイルを Batch のタスクでダウンロードして実行する流れになります。

実行可能 JAR ファイルの準備

適当な実行可能 JAR ファイルを準備します。ここでは以下の Spring Boot + Sprint Batch のサンプルアプリケーション作成の手順を進めることで生成できる実行可能 JAR ファイルを利用します。

https://spring.io/guides/gs/batch-processing/

JAR ファイルをストレージアカウントにアップロード

Batch アカウントに関連付けたストレージアカウントに適当な Blob コンテナーを作成して JAR ファイルをアップロードしておきます。
image.png

タスクの追加 (Java アプリケーション実行)

Batch アカウントのジョブからタスクの追加を行います。コマンドラインでは以下の JAR 実行のコマンドを入力します。JAR ファイルのダウンロード設定のため [リソースファイル] をクリックします。

java -jar batch-processing-0.0.1-SNAPSHOT.jar

image.png

リソースファイルの設定画面で [ストレージ BLOB の選択] をクリックします。
image.png

[SAS を含める] にチェック、[有効期限] は任意の値 (ここではデフォルトの 7) を入力し [OK] をクリックします。
image.png

アップロードした JAR ファイルを指定して [選択] をクリックします。
image.png

[送信] をクリックします。これにより、タスクで定義したコマンドラインを実行する前に、当該 JAR ファイルが VM の作業ディレクトリにダウンロードされます。
image.png

タスクの送信 & 実行完了後、以下のように状態が完了になっており、stdout.txt に標準出力結果が出力されていれば、処理は正常に完了しています。
image.png
image.png

本編は以上となります。

APPENDIX - 選択可能なイメージの種類

2020 年 9 月 20 日時点でイメージの種類 - Marketplace から選択可能な項目の一覧です。あくまで一時点のスナップショットですので、最新情報は Azure ポータルからご確認ください。

イメージの種類

  • Marketplace
  • Cloud Services (Windows のみ)
  • カスタム イメージ - 共有イメージギャラリー
  • グラフィックスとレンダリング

イメージの種類 - Marketplace

発行者 オファー SKU
canonical ubuntuserver 16.04-lts
canonical ubuntuserver 18.04-lts
credativ debian 8
credativ debian 9
debian debian-10 10
micrsoft-azure-batch centos-container 7-7
micrsoft-azure-batch centos-container-rdma 7-4
micrsoft-azure-batch centos-container-rdma 7-7
micrsoft-azure-batch ubuntu-server-container 16-04-lts
micrsoft-azure-batch ubuntu-server-container-rdma 16-04-lts
micrsoftwindowsserver windowsserver 2008-r2-sp1
micrsoftwindowsserver windowsserver 2008-r2-sp1-smalldisk
micrsoftwindowsserver windowsserver 2012-datacenter
micrsoftwindowsserver windowsserver 2012-datacenter-smalldisk
micrsoftwindowsserver windowsserver 2012-r2-datacenter
micrsoftwindowsserver windowsserver 2012-r2-datacenter-smalldisk
micrsoftwindowsserver windowsserver 2016-datacenter
micrsoftwindowsserver windowsserver 2016-datacenter-smalldisk
micrsoftwindowsserver windowsserver 2016-datacenter-with-containers
micrsoftwindowsserver windowsserver 2019-datacenter
micrsoftwindowsserver windowsserver 2019-datacenter-core
micrsoftwindowsserver windowsserver 2019-datacenter-core-smalldisk
micrsoftwindowsserver windowsserver 2019-datacenter-core-with-containers
micrsoftwindowsserver windowsserver 2019-datacenter-core-with-containers-smalldisk
micrsoftwindowsserver windowsserver 2019-datacenter-smalldisk
micrsoftwindowsserver windowsserver 2019-datacenter-with-containers
micrsoftwindowsserver windowsserver 2019-datacenter-with-containers-smalldisk

以上です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Thymeleaf テンプレートフラグメントで共通化] Headerの作成

こんにちは。
今回の記事ではSpring, Thymeleafで作るTODOアプリのヘッダー部分の作成について触れてみたいと思います。

TODOアプリ作成リンク集

1: [超基礎の理解] MVCの簡単な説明
2: [雛形を用意する] Spring Initializrで雛形を作ってHello worldしたい
3: [MySQLとの接続・設定・データの表示] MySQLに仮のデータを保存 -> 全取得 -> topに表示する
4: [POST機能] 投稿機能の実装
5: [PATCH機能] TODOの表示を切り替える
6: [JpaRepositoryの簡単な使い方] 検索機能の実装
7: [Thymeleaf テンプレートフラグメントで共通化] Headerの作成(今ここ)

現状のページ構成

http://localhost:8080/top
http://localhost:8080/search

現状はこの様に表示するのは2ページだけです。

今後このTODOアプリではViewとして

・エラーを表示するページ
・編集ページ

を作るので、フロント表示領域としては合計4ページになる予定です。

そしてそれら4ページ全てに共通のヘッダーがつく、というのが仕様になります。

4ページぐらいであれば、個々のHTMLに

html
<header>....</header>

と追加していってもいいのですが

ヘッダーに何か新しいリンクを追加したい際に、一つのページを編集すれば全ページに反映されるようになるので編集がとても楽になります。

テンプレートフラグメントって?

テンプレートフラグメントとは共通化したいHTMLを管理するThymeleafの機能です。

1つのHTMLファイルや要素を複数のHTML内で表示する時に使用します。

この考え方はVue.jsとかReactで使われるコンポーネントという概念に近いと思うので意識してみると良いかもしれません。

さてテンプレートフラグメントの使い方ですが

これは

1. 複数のページで表示したい要素をフラグメント化して

2. 使いたいページでそのフラグメントを呼び出す

に尽きます。

HTMLファイルの位置関係

今回はヘッダーを

main/resources/templates/common/header.html

こんなパスで作成しフラグメント化します。

そしてそれを

main/resources/templates/top.html
main/resources/templates/search.html

のヘッダー内に表示してみましょう!

HTML要素をフラグメント化する

さてそれではフラグメント化を行ってみましょう。

まずはフラグメント化という概念を簡単に説明します。

フラグメント化ってどういう意味?

フラグメントとは”かけら・断片・破片”という意味です。

つまりフラグメント化とはHTMLの要素を一つのかけらとして登録(変数化)するイメージ良いでしょう!

フラグメント化のやり方

共通化したい要素(例えばheader)に

<header th:fragment="header_fragment()>
  ...
</header>

th:fragment="フラグメント名(引数を渡したいときは引数)

としてやる事でフラグメント化できます。

ヘッダーをフラグメント化してみる

main/resources/templates/common/header.html
<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
    <header th:fragment="header_fragment()">
        <div class="text-center bg-primary">
            <div class="d-flex p-3">
                <p class="display-4 w-75 pl-5 mb-0 text-left font-weight-bold">
                    <a th:href="@{/top}" class="text-dark">ToDoリスト</a>
                </p>
                <p class="display-4 w-25 mb-0 mr-5 text-right font-weight-bold ">
                    <a th:href="@{/search}" class="text-dark">検索</a>
                </p>
            </div>
        </div>
    </header>
</html>

見ての通り、<header th:fragment="header_fragment()">と書いてあげる事でこのヘッダーはフラグメント化されています。

これを別のHTML上から呼び出してみましょう!

フラグメント化してViewを別のHTMLから呼び出す。

main/resources/templates/top.html
<body>
  <header th:replace="common/header :: header_fragment()"></header>
    .....略
  </header>
</body>

注目したいのはth:replace="...."の部分です。

replaceは置き換えるという意味を持っており、文字通りそのHTML要素を置き換えてしまいます。

なのでまるっと上書きするイメージです。他にもth:insertth:includeという表示方法もあるので使い分けたい方は調べてみると良いでしょう。

th:replace="呼び出したいフラグメントの相対パス :: 呼びたいフラグメント名"

とする事でそのフラグメントを呼ぶことができます!

これで今後Viewが増えても簡単に共通のヘッダーを呼び出すことができる様になりました!

まとめ

テンプレートフラグメントの使い方をざっくり書くと

・共通化したいViewをフラグメント化する(th:fragment)

・そのViewを呼び出す(th:replace, th:insert, th:include)

となります!

今回はreplaceで要素をまるっと上書きしていますが、仕様によっては一部分だけ共通化したものを呼び出したい(例えばボタンとかですかね)とかあると思うので、その際はinsertかincludeを使ってみましょう!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Javaに初めて触れてみた③

Javaに触れてみた

自己満の備忘録になりますのでご容赦ください。

いろいろ作ってみる

今までの知識を活かして、私が大ファンの広島東洋カープについて機能を作っていきたいと思います。

①監督の在任期間を表示する

番号を選択し、配列から取り出す機能にします。

Test.java
import java.util.Scanner;

class Test {
  public static void main(String args[]) {
    System.out.println("[1]達川監督\n[2]山本監督\n[3]ブラウン監督\n[4]野村監督\n[5]緒方監督\n[6]佐々岡監督\n1〜6の数字を選択してください。");
    Integer number = new Scanner(System.in).nextInt();

    String[] array = {"2000年", "2001年〜2005年", "2006年〜2009年", "2010年〜2014年", "2015年〜2019年", "2020年"};

    System.out.println(array[number - 1]);
  }
}
ターミナル
[1]達川監督
[2]山本監督
[3]ブラウン監督
[4]野村監督
[5]緒方監督
[6]佐々岡監督
1〜6の数字を選択してください。
4
2010年〜2014年

import java.util.Scanner;を書き忘れてしまい、Scannerが使えませんでしたので、注意します。
↓エラー文

ターミナル
Test.java:4: エラー: シンボルを見つけられません
    Integer number = new Scanner(System.in).nextInt();
                         ^
  シンボル:   クラス Scanner
  場所: クラス Test

②4択クイズ

4択から数字を選んで簡単な条件分岐させます。

Test2.java
import java.util.Scanner;

class Test2 {
  public static void main(String args[]) {
    System.out.println("2019年ドラフト1位の森下選手の出身大学は?\n[1]明治大学\n[2]法政大学\n[3]早稲田大学\n[4]立教大学");
    Integer number = new Scanner(System.in).nextInt();

    if (number == 1) {
      System.out.println("正解です!");
    } else {
      System.out.println("間違いです");
    }
  }
}
ターミナル
2019年ドラフト1位の森下選手の出身大学は?
[1]明治大学
[2]法政大学
[3]早稲田大学
[4]立教大学
2
間違いです
ターミナル
2019年ドラフト1位の森下選手の出身大学は?
[1]明治大学
[2]法政大学
[3]早稲田大学
[4]立教大学
1
正解です!

③4択クイズの応用

間違えたらその選択肢を減らす。

Test3.java
import java.util.Scanner;

import java.util.ArrayList;
import java.util.List;

class Test3 {
  public static void main(String args[]) {
    List<String> array = new ArrayList<String>();
    array.add("[1]明治大学");
    array.add("[2]法政大学");
    array.add("[3]早稲田大学");
    array.add("[4]立教大学");

    System.out.println("2019年ドラフト1位の森下選手の出身大学は?");
    System.out.println(array);
    Integer number = new Scanner(System.in).nextInt();


    if (number == 1) {
      System.out.println("正解です!");
    } else {
      array.remove(number - 1);
      System.out.println("間違いです");
      System.out.println("番号を選び直してください。");
      System.out.println(array);
    }
  }
}

import java.util.ArrayList;
import java.util.List;
を記入していないとエラーが出るので注意したいと思います。

ターミナル
2019年ドラフト1位の森下選手の出身大学は?
[[1]明治大学, [2]法政大学, [3]早稲田大学, [4]立教大学]
1
正解です!

↑正解のパターン

ターミナル
2019年ドラフト1位の森下選手の出身大学は?
[[1]明治大学, [2]法政大学, [3]早稲田大学, [4]立教大学]
2
間違いです
番号を選び直してください。
[[1]明治大学, [3]早稲田大学, [4]立教大学]

↑間違えたパターン
2の法政大学が表示されなくなりましたね。

とりあえず今回の記事はこんな感じで終わります。
この機能の改善点
①[[1]明治大学, [2]法政大学, [3]早稲田大学, [4]立教大学]みたいにターミナルに配列の中身を表示させる時に[]を表示支えないようにする。
②選び直してください。の表示が出たら数字を入力できるようにする。多分繰り返し処理でできると思う。
③問題を増やす。
④問題をランダムで出題できるようにする。
⑤答えをランダム表示にできるようにする。
今思いつく限りではこのような感じですね。次回の記事で書いていきたいと思います。

まだまだJavaのことはわかりませんので、頑張っていきたいと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初めてのjava.util.logging

本記事の結論

  • java.util.loggingとは、Javaに搭載されたロギングを担うモジュールのこと

ロギングとは

  • 出来事を時系列に沿って記録した履歴のこと
  • ログレベルが存在する

ログレベルとは

  • 文字通りログのレベルのこと
  • java.util.loggingでは、下記のようにレベル分けされている。なお、デフォルトでは、INFO以上のログが出力されるよう設定されている。
    • FINEST - 非常に詳細なトレースメッセージ
    • FINER - かなり詳細なトレースメッセージ
    • FINE - 詳細なトレースメッセージ
    • CONFIG - 静的な構成メッセージ
    • INFO - 情報メッセージ
    • WARNING - 警告メッセージ
    • SEVERE - 重大なメッセージ

java.util.loggingの機能

  • Logger - ログの出力
  • Handler - ログの出力先を制御
  • Formatter - ログのフォーマットを制御

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Spring Boot で Ajax を使用したファイルのアップロード、ダウンロード(JQuery未使用)

作るもの2つ

  1. Ajaxで画像をアップロードして、サーバーに格納後に表示する
  2. 表示する画像を選択して画像表示ボタンを押すと、Ajaxでダウンロードして画像が表示される

デモ画面

image.png

1. ファイルアップロード

  • 画面側
imageajax.html(一部)
<body>
    <h3>ファイルアップロード</h3>

    <form id="imageFormId">
        <input type="file" name="inputFileName" />
        <button type="button" onclick="uploadImage('imageFormId', 'myUploadedImageId')">画像アップロード</button>
    </form>
    <img id="myUploadedImageId" style="width: 400px">

    <script>
        function uploadImage(imgForm, imgCtrl) {
            const formData = new FormData(document.getElementById(imgForm));
            const request = new XMLHttpRequest();
            request.onreadystatechange = function() {
                if (this.readyState == 4 && this.status == 200) {
                    const img = document.getElementById(imgCtrl);
                    const url = window.URL || window.webkitURL;
                    // レスポンスを画像表示する
                    img.src = url.createObjectURL(this.response);
                }
            }
            request.open("POST", "/uploadimage");
            request.responseType = 'blob';
            request.send(formData);
        }
    </script>
</body>
  • コントローラ側
ImageAjaxController(一部)
    /**
     * ファイルアップロード
     */
    @PostMapping("/uploadimage")
    public ResponseEntity<byte[]> uploadImage(@RequestParam("inputFileName") final MultipartFile uploadFile) {

        if (uploadFile.isEmpty()) {
            return ResponseEntity.of(Optional.empty());
        }
        // アップロードされたファイルを格納するためのPathを取得
        final Path path = Paths.get("c:/uploaddir", uploadFile.getOriginalFilename());

        final byte[] bytes;
        try {
            // アップロードされたファイルのバイナリを取得
            bytes = uploadFile.getBytes();
            // ファイルの格納
            Files.write(path, bytes);
        } catch (IOException e) {
            return ResponseEntity.of(Optional.empty());
        }
        return ResponseEntity.ok(bytes);
    }
  • アップロードするファイルは上限1MBなので、それ以上のサイズをアップロードするには、以下のように設定する
application.yml
spring:
  servlet:
    multipart:
      max-file-size: 30MB
      max-request-size: 30MB

2. ファイル選択

  • 画面側
imageajax.html(一部)
<body>
    <h3>ファイル選択</h3>

    <select id="selectImageId">
        <option value="a">a</option>
        <option value="b">b</option>
    </select>
    <button type="button" onclick="displayImage('selectImageId', 'mySelectedImageId')">画像表示</button>
    <img id="mySelectedImageId" style="width: 400px">

    <script>
        function displayImage(selectCtrl, imgCtrl) {
            const select = document.getElementById(selectCtrl);
            const request = new XMLHttpRequest();
            request.onreadystatechange = function() {
                if (this.readyState == 4 && this.status == 200) {
                    const img = document.getElementById(imgCtrl);
                    const url = window.URL || window.webkitURL;
                    // レスポンスを画像表示する
                    img.src = url.createObjectURL(this.response);
                }
            }
            // 選択肢を送って、ファイルを取得する
            request.open("GET", "/selectimage/" + select.value);
            request.responseType = 'blob';
            request.send();
        }
    </script>
</body>
  • コントローラ側
ImageAjaxController(一部)
    /**
     * ファイル選択
     */
    @GetMapping("/selectimage/{selectNum}")
    public ResponseEntity<byte[]> getImage(@PathVariable final String selectNum) {

        // 選択肢からファイル名取得
        final String fileName;
        switch (selectNum) {
        case "a":
            fileName = "a.jpg";
            break;
        case "b":
            fileName = "b.jpg";
            break;
        default:
            return ResponseEntity.of(Optional.empty());
        }

        // ファイルのPath
        final Path path = Paths.get("c:/selectdir", fileName);

        final byte[] bytes;
        try {
            // ファイルを読み込む
            bytes = Files.readAllBytes(path);
        } catch (IOException e) {
            return ResponseEntity.of(Optional.empty());
        }
        return ResponseEntity.ok(bytes);
    }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【java】【javaSliver】インタフェースと抽象クラスがややこしいのでまとめ

この記事の目的

来週javaシルバーを受験するので、曖昧な部分を潰していくことに。
ちょっと混乱していたインタフェース抽象クラスについて整理しておこうかな。というのがこの記事の目的です。

混乱していた理由

「抽象メソッドはabstractをつけて宣言する。つけないとコンパイルエラー」と書かれていたり、
「省略した場合、暗黙的にpublic abstractが付与される」と書かれていたり・・・

「あれ?勘違い?」と混乱していました。
ので、一旦しっかり整理して混乱から脱出したかったんです。
今思えば、抽象メソッドと抽象クラス、インタフェースがごっちゃになっていたんだろうな。って思ってます。

ルール比較

インタフェースと抽象クラスで定義できるものを一覧でまとめました。整理整理。
メソッドとフィールド変数に分けて整理しました。

メソッドについて

まずはメソッドから。
インタフェース抽象クラスそれぞれでテーブルにまとめました。

インタフェースのメソッド達

定義できるもの アクセス修飾子 省略できる? その他
抽象メソッド public 省略してもpublic インタフェースだとabstractも省略できるよ。
defaultメソッド public 省略できちゃう ※SE8以降定義できるようになりました。
staticメソッド public or private 省略するとpublic ※SE8以降定義できるようになりました。

ポイント

  • インタフェースの場合で宣言する抽象メソッドはabstract修飾子も省略できるみたいです。
  • アクセス修飾子を記述しないとpublicになるってのが共通ルールですかね?
  • staticメソッドについては、アクセス修飾子がpublicに限らずprivateの指定もできちゃうのがちょっと厄介ですね。
なんで、インタフェースだとabstract修飾子を省略できるんだろ?

これは個人的にたどり着いた推測ですが・・・。
インタフェースでは基本的に抽象メソッドしか定義できない(SE8までは)ので、abstractを明示的に宣言しなくても、コンパイラが空気読んでabstractつけてくれるんだな。と考えることにしました。

抽象クラスのメソッド達

定義できるもの アクセス修飾子 省略できる? その他
抽象メソッド なんでもあれ abstractは必須。 アクセス修飾子は省略可。省略してもpublicとかにはならないよ
具象メソッド なんでもあれ 省略できちゃう アクセス修飾子は省略可。省略してもpublicとかにはならないよ

ポイント

  • インタフェースと違って、アクセス修飾子は自由に定義できちゃう。
  • アクセス修飾子を省略すると、具象クラスと一緒で、修飾子なしのメソッドとして扱っちゃう。
  • 抽象メソッドのabstract修飾子必須!!!
なんで、抽象クラスだとabstract修飾子を省略できないんだろう?

抽象クラスの中には、具象メソッドと抽象メソッドが混在するから、混乱しないように明示的にabstractをつけないといけないって考えることにしました。
(これも個人的にたどり着いた推測です。)

続いて、変数

インタフェース

フィールドの種類 定義できる?できない? staticを省略できる? その他
非static変数(インスタンス変数) 定義できない - インタフェースはインスタンス化できないからね。
static変数 定義できるけど、定数になるよ 省略できる 省略してもpublic static finalになっちゃう。

ポイント

  • インスタンス変数は定義できない
  • static変数は何がなんでもpublic stati finalになる

インタフェースはインスタンス化できないから、インスタンスした時の変数のことなんて考える必要ないですね。
だから定義できないってことですね。うん。納得。
インスタンス化はできないけど、型としては扱えちゃうからこれまたややこしポイントですね。

抽象クラス

フィールドの種類 定義できる?できない? staticを省略できる? その他
非static変数(インスタンス変数) 定義できる そもそもいらない アクセス修飾子はなんでも定義できちゃう。
static変数 定義できる 省略できない staticを省略したらインスタンス変数になってしまうよ。アクセス修飾子はなんでも定義できちゃう。

ポイント

  • 具象メソッドとなんら変わらない!

まとめ

  • インタフェース

    • 抽象メソッド → abstractは省略可能
    • defaultメソッド
    • staticメソッド → staticメソッドだけは privateでも扱える
    • static変数 → static 変数は定数としてしか使えない
  • 抽象クラス

    • 抽象クラス → abstractは絶対書く
    • 具象メソッド
    • static変数
    • 非static変数

さいごに

結構自分ようのメモになっちゃいましたが、やっぱりこうやって整理する時間は必要ですね。
もし、解釈が違う、間違っているなあればお手数をおかけしますが、ご指摘いただけますと幸いですm(_ _)m

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

swingにおける日本語入力時にUIが崩れる不具合と解決方法

はじめに

ここ最近、swing関連のバグ?に悩まされていました。

1.png

例えば、このようにJTextFieldやJTextAreaなどのJTextComponent系コンポーネントを用意したとします。
半角英数字を入力する分には特に変わりはないのですが、日本語入力をした途端に、

2.png
$\Huge{な~ん~な~ん~す~か~こ~れ}$

バグにも程があります。

ググってみるとInputMethodListenerを用いてウィンドウのrepaint()を叩き続ければ良いという解決法を見つけたので、フレームを最小化していない間はコンポーネントへの全角文字入力以降にwhileでフレームのrepaint()を叩き続けるためのバグ修正用クラスを用意。

バグ修正用クラス
final class SwingPaintBugFix extends Thread {
    private Window w;
    private boolean running = true;

    private static SwingPaintBugFix bugfix = new SwingPaintBugFix();

    private SwingPaintBugFix() {}

    private SwingPaintBugFix(Window w) {
        this.w = w;
    }

    public static void of(JTextComponent txtComp, Window w) {
        w.addWindowListener(new WindowAdapter() {
            @Override public void windowIconified(WindowEvent e) {
                bugfix.stopRunning();
            }
            @Override public void windowDeiconified(WindowEvent e) {
                bugfix = new SwingPaintBugFix(w);
                bugfix.start();
            }
        });
        txtComp.addInputMethodListener(new InputMethodListener() {
            @Override public void inputMethodTextChanged(InputMethodEvent e) {
                // 全角文字を受け取る
                bugfix.stopRunning();
                bugfix = new SwingPaintBugFix(w);
                bugfix.start();
            }
            @Override public void caretPositionChanged(InputMethodEvent e) {}
        });
        txtComp.addKeyListener(new KeyAdapter() {
            // 半角文字を受け取る
            @Override public void keyTyped(KeyEvent e) {}
        });
    }

    @Override public void start() {
        new Thread(this).start();
    }

    @Override public void run() {
        while (running) {
            ((Component) w).repaint();
        }
    }

    private void stopRunning() {
        running = false;
    }
}

しかしながら、常にwhileで処理を実行し続けるという行為に抵抗感が否めず、すべてのJTextComponent系コンポーネントにバグ修正用クラスを適用するのも手間がかかるため、抜本的な解決策を探ることとしました。

原因究明

とりあえず数回再起動しても治らず。

Windows Updateで不具合がでないかどうか確認しながら最新版に更新しても駄目。

「あーめんど」と思いながらも、仕方がないのでセーフモードに。セーフモードでは先程のバグが発生しないことを確認したので、「バッググラウンドプロセス」と「サービス」を控えて通常起動に戻す。

「これでもない、これでもない・・・」と、タスクマネージャーからセーフモード以外のタスクを一つづつ終了させ、ついに原因を特定!

3.png

「Nahimic Service」というサウンドユーティリティが犯人だったようです。こいつを試しに停止させてみたら見事にバグが直りました。ググってみると、こいつはUI関連以外にもいろいろと競合を起こしているようです。

・・・これならバグ修正クラスなんて作る意味なかったじゃんか。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Java】文字列と文字列の比較をする時の注意点

結論:Javaで変数に格納した文字列を比較する時はequals()を使おう。

やりたい事

  • String str1 = "hoge"
  • String str2 = "hoge"
  • str1とstr2が等しい時はif文の処理に入れる

参考

文字列と文字列の比較
https://www.javadrive.jp/start/string/index4.html

【Java入門】文字列(String)を比較する方法(「==」と「equals」)
https://www.sejuku.net/blog/14621

変数に数値を代入して比較する場合

int num = 10

if (num == 10) {
 System.out.println("numは10です。");
} else {
 System.out.println("numは10ではありません。");
}

この式では、変数numに格納しているのは10なので処理結果は

numは10です

変数に文字列型を代入して比較する場合

String str1 = "hoge"
String str2 = "hoge"

if (str1 == str2) {
 System.out.println("str1とstr2は同じです。");
} else {
 System.out.println("str1とstr2は違います。");
}

この式では変数str1と変数str2は同じ文字列hogeが格納されているので処理結果は「str1とstr2は同じです。」と返ってくると思っていました。

str1とstr2は違います

。。。!?ほげ!?なんで!?
ここでドツボにはまり、300回くらいデバックしました。

文字列がメモリ上で格納されている場所が一緒かどうか比較するメソッド「==」

「==」で比較しても違う文字列として扱われてしまいます。これはJavaの参照といいます

文字列は生成されるとメモリ上に文字列を保存しておくための場所(領域)が確保されます。その確保した「場所」をhogeは保持しています

つまり値を変数に保存しているわけではなく、場所を覚えておきhogeを使う際にその場所をたどって値を引っ張り出してくるという仕組みになっています

この参照という仕組みがあるため「==」で比較した場合、文字列の値が一緒かどうかではなく文字列がメモリ上で格納されている場所が一緒かどうかという比較になります

同じメモリ領域に保存されていればtrueそうでなければfalseを返します
先程の式でいうと、str1とstr2は違うメモリ領域に保存されている為、falseを返します

equals()メソッドを使って文字列を比較する

String str1 = "hoge"
String str2 = "hoge"

if (str1.equals(str2)) {
 System.out.println("str1とstr2は同じです。");
} else {
 System.out.println("str1とstr2は違います。");
}

処理結果

str1とstr2は同じです

想定していた結果が返ってきました。

ちなみにrubyで書くと「==」でも同じ結果が返ってきます

str1 = "hoge"
str2 = "hoge"

if str1 == str2  then
 puts "str1とstr2は同じです。"
else
 puts "str1とstr2は違います。"
end

# => str1とstr2は同じです。

まとめ

rubyやJavaScriptを使っている時は数値型文字列型も「==」を使って比較できるのでJavaでも疑いなくプログラムを書いていましたが、Javaは違うんですねー

画面に穴が空くくらい文法ミスが無いかチェックしましたが、「参照」を知らないと私みたく余計な時間を使うので注意しましょう

おわり
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

離散対数問題 (Discrete Logarithm Problem) を任意 mod で解く

離散対数問題 (Discrete Logarithm Problem) を任意 mod で解く

離散対数問題 (Discrete Logarithm Problem) を解くアルゴリズムとして,Baby-step Giant-step を知っている方は多いと思います.しかし,任意 mod を取り扱っている記事がほとんど見当たらなかったので,その辺りを書いていきます.

まず,離散対数問題とは以下のような問題を指します.(Baby-step Giant-step による解法を知っている方は読み飛ばして下さい)

正整数 $M$ と $ 0 \leq X, Y \lt M $ なる整数 $X, Y$ が与えられる.
$$ X^K\equiv Y \pmod M \tag{1} $$ を満たす (最小の) 非負整数 $K$ を求めよ.

M が素数の場合

以下のようにこの問題を解くことが出来ます.


まず,簡単な $X=0$ のケースを処理してしまいましょう.$Y=0$ なら $K=1$,$Y\neq 0$ なら $K=-1$ (存在しない) です.但し,ここでは $0^0=1$ としています.

続いて,$X\neq 0$ のケースを考えます.$p=\left\lceil\sqrt{M}\right\rceil$ として,$K=p*i+q\ (0\leq q\lt p)$ が $(1)$ を満たすような非負整数 $i, q$ を探索することにします.Fermat の小定理より,$0\leq i\lt p$ の範囲を調べれば十分です.

いま,$M$ が素数かつ $0\lt X\lt M$ なので $\gcd(X, M)=1$ が成り立ちます.従って,$X*X^{-1}\equiv 1 \pmod M$ を満たす $X^{-1}$ が $0\lt X^{-1} \lt M$ の範囲にただ一つだけ存在します.この $X^{-1}$ を用いて $(1)$ 式を以下のように変形します.

\begin{align}
X^K&\equiv Y\pmod M\\
X^{p*i+q}&\equiv Y\pmod M\\
X^q&\equiv Y*A^i\pmod M\tag{2}\\
\end{align}

2 行目から 3 行目の変形では $A=(X^{-1})^p$ と置きました.

$(2)$ の左辺がとりうる値は $X^0 \bmod M,X^1\bmod M,\dots,X^{p-1}\bmod M$ の高々 $p=\left\lceil\sqrt{M}\right\rceil$ 種類です.そこで,$X^j\bmod M\mapsto j$ に写すような連想配列 (unordered_mapHashMap) を前もって計算しておくことにします.このとき,key が既に挿入済みの場合は value は上書きしないようにします.

以上より,$(2)$ の右辺で $i=0,1,\dots,p-1$ を全て調べれば,目的の $i, q$ を得ることが出来ます (見つからなかった場合は $K=-1$).連想配列の value を上書きせず,また, $i$ を昇順に走査することで,最初に見つかった $i, q$ は $(1)$ を満たす最小の $K$ を構成することも分かります.


上の解法の計算量ですが,

  • $p$ の計算は二分探索で $O(\log M)$
  • $X^{-1}$ の計算はユークリッドの互除法で $O(\log M)$
  • $A=(X^{-1})^p$ の計算は $O(p)=O(\sqrt{M})$
  • 連想配列の前計算は $O(p)=O(\sqrt{M})$
  • $i=0,\dots,p-1$ の全探索は $O(p)=O(\sqrt{M})$

より,全体 $O(\sqrt{M})$ です.

M が任意の場合

この記事の本題です.$M$ が合成数になると上のアルゴリズムをそのまま適用することは出来ません.$X^{-1}$ が存在しない可能性があるからです.しかし,$M$ が合成数の場合も ほとんど同じアルゴリズムで問題を解くことができます.

以下に解法を示します.


先に $M=1$ の場合を処理しておきます.この場合,任意の $X, Y$ に対して $K=0$ は $(1)$ を満たし,最小です.

以下,$M>1$ とします.

$g:=\gcd{(X,M)}$ とします.$g=1$ であれば,$X^{-1}$ が存在するので先に述べたアルゴリズムをそのまま適用することが出来るのでここで終了します.$g\neq 1$ だとそうはいかないので少し工夫します.

$X=g*X'$,$M=g*M'$ のように $X, M$ を積に分解します.

これらを $(1)$ に代入すると,$(g*X')^K\equiv Y \pmod{(g*M')}$ となります.従って,ある整数 $n$ が存在して,
$$(g*X')^K-Y=n*g*M'\tag{3}$$
が成立します.$K=0$ となるのは $Y=1$ の場合に限られるので,このケースも別で処理しておくことにします.

$K>0$ のとき,$Y$ は $g$ の倍数である必要があるので,$Y$ も $Y=g*Y'$ のように分解しておきます ( $Y$ が $g$ の倍数でなければ,$(1)$ を満たす $K$ は存在しません ).$(3)$ 式の計算を進めることが出来て,式 $(4)$ を得ます.

\begin{align}
g*X'*(g*X')^{K-1}-g*Y'&=n*g*M'\\
X'*(g*X')^{K-1}-Y'&=n*M'\\
X'*(g*X')^{K-1}&\equiv Y'\pmod{M'}\tag{4}
\end{align}

さらに,$\gcd{(X',M')}=1$ が成り立つので $X'*{X'}^{-1}\equiv 1 \pmod{M'}$,$0\lt {X'}^{-1}\lt M'$ なる ${X'}^{-1}$ がただ一つ存在します.これを用いることで $(5)$ 式を得ます.

\begin{align}
X'*(g*X')^{K-1}&\equiv Y'\pmod{M'}\\
(g*X')^{K-1}&\equiv Y'*{X'}^{-1}\pmod{M'}\\
X^L&\equiv Z\pmod{M'}\tag{5}\\
\end{align}

2 行目から 3 行目の変形では $L=K-1,\ Z=Y'*{X'}^{-1}$ と置きました.

$(5)$ は元の問題よりもサイズの小さい離散対数問題になっているので,再帰的にこの問題を解くことが出来ます.また,後述の計算量解析を考えるとこの再帰が必ず終了することも分かります.$(5)$ を満たす最小の非負整数 $L$ が存在すれば $L+1$ が答えとなり,$L$ が存在しなければ元の問題の解も存在しません.

一つ注意すべきなのは,$\gcd{(X,M')}=1$ とは限らないということです.

例えば $(X, M)=(20, 24)$ とすると,$g=\gcd{(X, M)}=4$ なので,$M'=M/g=6$ です.この時,$\gcd{(X,M')}=2\neq 1$ となっています.


この解法の計算量は

( $\gcd{(X,M)}=1$ となるまでの再帰の計算量 ) + ( Baby-step Giant-step の計算量 )

です.再帰する必要があるのは $\gcd{(X,M)}\neq 1$ の場合で,$(X, M)$ は $(X, M/\gcd{(X, M)})$ へと移ります.この時,$\gcd{(X, M)} >= 2$ なので再帰の度に $M$ は半分以下になります.従って,再帰の回数は $O(\log M)$ 回で,再帰の度にユークリッドの互除法を定数回だけ行うので再帰の計算量は $O((\log M)^2)$ となります.

以上より,計算量は全体で $O(\sqrt M)$ で任意 mod での離散対数問題を解くことが出来ました.

実装例

最後に,任意 mod での離散対数問題を解く Java プログラムを掲載して終わりにしたいと思います.

verify に使った問題: Discrete Logarithm
提出: Discrete Logarithm

DiscreteLogarithm.java
public class DiscreteLogarithm {
    /**
     * 任意 mod で離散対数問題を解く.つまり,x^k=y mod m を満たす最小の非負整数 k を求める.
     * @param x log の底
     * @param y {@code x} の冪
     * @param m mod
     * @return x^k=y mod m を満たす最小の非負整数 k.存在しない場合は -1 を返す.
     */
    public static long log(long x, long y, long m) {
        if ((x %= m) < 0) x += m;
        if ((y %= m) < 0) y += m;
        // m = 1 のケースを処理
        if (m == 1) return 0;
        // y = 1 (k = 0) のケースを処理
        if (y == 1) return 0;
        long[] gi = gcdInv(x, m);
        // g=gcd(x,m), ix*x=g mod m
        long g = gi[0], ix = gi[1];
        if (g != 1) {
            // y は gcd(x, g) の倍数である必要がある
            if (y % g != 0) return -1;
            m /= g;
            y = (y / g) * ix;
            // 小さいサイズの問題を解く
            long log = log(x, y, m);
            // 小さいサイズの問題に解が存在しなければ,元の問題にも解は存在しない
            if (log < 0) return -1;
            // 小さいサイズの問題の答えを l とすると,元の問題の答えは l + 1
            return log + 1;
        }
        // gcd(x, g) = 1 であれば Baby-step Giant-step で解ける
        return coprimeLog(x, y, m, ix);
    }
    /**
     * x と m が互いに素な場合に,Baby-step Giant-step により離散対数問題を解く.
     * @param x log の底
     * @param y {@code x} の冪
     * @param m mod
     * @param ix mod {@code m} での {@code x} の乗法逆元 x^{-1}
     * @return x^k=y mod m を満たす最小の非負整数 k.存在しない場合は -1 を返す.
     */
    private static long coprimeLog(long x, long y, long m, long ix) {
        long p = ceilSqrt(m);
        java.util.HashMap<Long, Long> memo = new java.util.HashMap<>();
        for (long i = 0, powx = 1; i < p; i++) {
            memo.putIfAbsent(powx, i);
            powx = powx * x % m;
        }
        long a = pow(ix, p, m);
        for (long i = 0, ypowa = y; i < p; i++) {
            long q = memo.getOrDefault(ypowa, -1l);
            if (q >= 0) return p * i + q;
            ypowa = ypowa * a % m;
        }
        return -1l;
    }
    /**
     * (x-1)^2 < n <= x^2 を満たす x を二分探索する.
     * @param n sqrt を取る整数
     * @return (x-1)^2 < n <= x^2 を満たす x
     */
    private static long ceilSqrt(long n) {
        long l = -1, r = n;
        while (r - l > 1) {
            long m = (r + l) >> 1;
            if (m * m >= n) r = m;
            else l = m;
        }
        return r;
    }
    /**
     * a^b mod m を二分累乗法により計算する.
     * @param a 底
     * @param b 指数
     * @param m mod
     * @return a^b mod m
     */
    private static long pow(long a, long b, long m) {
        if (m == 1) return 0;
        if ((a %= m) < 0) a += m;
        long pow = 1;
        for (long p = a, c = 1; b > 0;) {
            long lsb = b & -b;
            while (lsb != c) {
                c <<= 1;
                p = (p * p) % m;
            }
            pow = (pow * p) % m;
            b ^= lsb;
        }
        return pow;
    }
    /**
     * g = gcd(x, m), a * x = g mod m を満たす組 (g, x) をユークリッドの互除法により計算する.
     * @param a
     * @param m mod
     * @return g = gcd(x, m), a * x = g mod m を満たす組 (g, x)
     */
    private static long[] gcdInv(long a, long m) {
        if ((a %= m) < 0) a += m;
        if (a == 0) return new long[]{m, 0};
        long s = m, t = a;
        long m0 = 0, m1 = 1;
        while (t > 0) {
            long u = s / t;
            s -= t * u;
            m0 -= m1 * u;
            long tmp;
            tmp = s; s = t; t = tmp;
            tmp = m0; m0 = m1; m1 = tmp;
        }
        if (m0 < 0) m0 += m / s;
        return new long[]{s, m0};
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む