20200708のJavaに関する記事は18件です。

Minecraft Forge 1.15.2でのcoremodの書き方

Minecraft Forge 1.15.2環境でのcoremodの書き方を簡単に解説します。
ASMって何? とかバイトコード何? という説明はここではしません。すいません。
あとJavascriptについても説明しません。すいません。

ただ、coremodを使うレベルのmodderの方々なら、フィーリングで書けると思います。

Forgeの1.13以降ならたぶん同じ書き方で動くと思いますが検証はしていません。

このページとサンプルコードを非常に参考にさせていただきました。先達に深く感謝します。
99.99 - Coremod

coremodって? ASMって?

それでも一応簡単に。"coremod"というのは、Forgeが用意している仕組みのひとつで、これを使うとMinecraftのJavaコードそのものを書き換えることができます(多少表現に語弊があります)。
そのために使うライブラリがASMです。また、コードそのものと言っても、普段書いている人間用のコードではなくて、コンパイルされたあとのバイトコードを書き替えます。
いずれにしても、ASMが書ければ割となんでもできるようになります。ただ他のmodとの競合は増えてくるかもしれません。

ASMについての勉強はこのpdfが非常にお勧めです(が長いです)。
ASM 4.0 A Java bytecode engineering library

検証環境

Minecraft: 1.15.2
Minecraft Forge: 1.15.2-31.2.0

1.13より前からの変更点

以前はJavaのコードでcoremodを記述していましたが、1.13からはJavascriptで記述します。
ASM自体の書き方は、おおむね前と同じと思ってもらって大丈夫です(たぶん)。

ファイル構成

必要なファイルは最低ふたつです。

  • coremods.json
  • 実際にASMを書いたJavascriptファイル

coremods.json

main/java/resources/META-INF/フォルダ直下に置きます。
ファイル名は、たぶん"coremods.json"じゃなきゃダメです。

中身は下のような感じです。

coremods.js
{
  "TestMod Transformer": "testmod-transformer.js"
}

配列内のキー("TestMod Transformer")はたぶんなんでもいいです。大文字小文字も何でもいいと思います。
値は、これから書くことになるjsファイルの場所を書いてください。

上のように単にファイル名だけ書くと、main/java/resources/直下にあるファイルを指定していることになります。絶対パスとか相対パスが機能するかは試してないですが相対パスはいけそうな気がします。

coremods.js
{
  "TestMod Transformer": "testmod-transformer.js",
  "TestMod Transformer2": "testmod-transformer2.js"
}

一度に複数のjavascriptファイルを指定することもできます。

ASMを書くJavascriptファイル

分かりやすさのために、main/java/resources/直下 に置くことにしましょう。場所を変える場合は上のcoremods.jsonの中身の値も変えてください。
実際にASMを書くjsは以下のような感じです。

testmod-transformer.js
function initializeCoreMod() {
    return {
        'coremodmethod': {
            'target': {
                'type': 'METHOD',
                'class': 'your.target.class',
                'methodName': 'targetMethodName',
                'methodDesc': 'targetMethodDescriptor'
            },
            'transformer': function(method) {
                //ここにASMの処理を書く。
                return method;
            }
        }
    }
}

jsファイルの中には、"initializeCoreMod()"という名前で、jsonを返り値にする関数を書いておく必要があります。ぶっちゃけ上をコピペすればOKです。
上の「ここにASMの処理を書く」というところにいろいろ書いていけば完成です。

以上、お疲れ様でした。
 
 
 
 
 
 
 
とすると、はぁ、おちょくるのもたいがいしにしろよ? と言われそうなので、もう少し書いておきます。上でわかる人はもうあとは自由にやってください。

transformerの種類

targetとして、"FIELD"、"METHOD"、"CLASS"の3種類を指定できます。それぞれ、フィールド、メソッド、クラスに干渉します。つまり、以下の3つの書き方ができます。

testmod-transformer.js
function initializeCoreMod() {
    return {
        'coremodmethod': {
            'target': {
                'type': 'FIELD',
                'class': 'your.target.class',
                'fieldName': 'targetFieldName'
            },
            'transformer': function(field) {
                //ここにASMの処理を書く。
                return field;
            }
        }
    }
}
testmod-transformer.js
function initializeCoreMod() {
    return {
        'coremodmethod': {
            'target': {
                'type': 'METHOD',
                'class': 'your.target.class',
                'methodName': 'targetMethodName',
                'methodDesc': 'targetMethodDescriptor'
            },
            'transformer': function(method) {
                //ここにASMの処理を書く。
                return method;
            }
        }
    }
}
testmod-transformer.js
function initializeCoreMod() {
    return {
        'coremodmethod': {
            'target': {
                'type': 'CLASS',
                'class': 'your.target.class'
            },
            'transformer': function(class) {
                //ここにASMの処理を書く。
                return class;
            }
        }
    }
}

"target"内に必要な値は上記のとおりです。
ただ、フィールドはリフレクションヘルパーでJavaから干渉できますし、クラス自体に干渉しなくても結局メソッドへの干渉で事足りることが多い気がする、という個人的な理由で、以下FIELDとCLASSの改変には触れません。誰かお願いします。

具体的な書き方

function initializeCoreMod() {
    var ASMAPI = Java.type("net.minecraftforge.coremod.api.ASMAPI");
    var Opcodes = Java.type('org.objectweb.asm.Opcodes');
    var InsnList = Java.type("org.objectweb.asm.tree.InsnList");
    var InsnNode = Java.type("org.objectweb.asm.tree.InsnNode");

    var mappedMethodName = ASMAPI.mapMethod("target_srg_func_name");

    return {
        'coremodmethod': {
            'target': {
                'type': 'METHOD',
                'class': 'your.target.class',
                'methodName': mappedMethodName,
                'methodDesc':'targetMethodDescriptor'
            },
            'transformer': function(method) {
                print("Enter transformer.");

                var arrayLength = method.instructions.size();
                var target_instruction = null;

                //search the target instruction from the entire instructions.
                for(var i = 0; i < arrayLength; ++i) {
                    var instruction = method.instructions.get(i);
                    if(instruction.name === "targetMethodName") {
                        target_instruction = instruction;
                        break;
                    }
                }

                if(target_instruction == null) {
                    print("Failed to detect target.");
                    return method;
                }

                var toInject = new InsnList();
//              toInject.add(new InsnNode(Opcodes.POP));
                toInject.add(new InsnNode(Opcodes.RETURN));

                // Inject new instructions just after the target.
                method.instructions.insert(target_instruction, toInject);

                return method;
            }
        }
    }
}

だいぶ見通しが良くなったかと思います。順に解説します。

Javaクラスの呼び出し

    var ASMAPI = Java.type("net.minecraftforge.coremod.api.ASMAPI");
    var Opcodes = Java.type('org.objectweb.asm.Opcodes');
    var InsnList = Java.type("org.objectweb.asm.tree.InsnList");
    var InsnNode = Java.type("org.objectweb.asm.tree.InsnNode");

どんな魔法かわかりませんが、"Java.type()"の引数にいつものimport先のクラス名を入れると、あたかもJavaみたいに使えます(説明雑)。Opcodesなんかが来ればもう百人力な気がします。でもコード補完が効かないのが困ります。補完のやり方知っている人がいたら教えてください。
ASMAPIとOpcodesはどんな改変でも確実に使う気がしますが、他のクラスを呼び出すかどうかはお好みだと思います。いまのcoremodはいわゆるtree APIを使っているので、どんなクラスがあるかは別途そっちで調べてください。

干渉するメソッドの指定

    var mappedMethodName = ASMAPI.mapMethod("target_srg_func_name");

    return {
        'coremodmethod': {
            'target': {
                'type': 'METHOD',
                'class': 'your.target.class',
                'methodName': mappedMethodName,
                'methodDesc':'targetMethodDescriptor'
            },
            'transformer': function(method) {
                return method;
            }
        }
    }

"coremodmethod"と名付けていますが、これは任意の文字列でいいみたいです。他の"target"とか"transformer"とかはこれじゃないとだめです。

ASMAPIにはmapMethod()というメソッドが定義されていて、難読化後のメソッド名から、マップされたメソッド名を返してくれます。
例えば"net.minecraft.util.ScreenShotHelper"クラスの"saveScreenshotRaw"メソッドが、"func_228051_b_"であるとかそういうやつです。このmapMethod()を使ってメソッド名を取得しておかないと、開発環境では動くのに実環境では動かない(またはその逆)が起こります。

実際に書く時は、"ASMAPI.mapMethod("func_228051_b_")"などと、引数に難読化後のメソッド名を入れます。

難読化後のメソッド名は、いつものMCP Botから、開発環境にあったマッピングを選んで探してください。

ちなみにMCPのマッピングで探しても見つからないやつがたまにいます。そういうやつらは、開発環境でも実環境でもすでにそのわかりやすい方の名前で動いてるので、mapMethod()でメソッド名を取ってくる必要はないです。

"methodDesc"はいつものdescriptorです。

void hogehoge(int i, float f)

っていうメソッドだったらdescriptorは"(IF)V"になるっていういつものあれです。

IDEAでもEclipseでもJavaコードをバイトコードで表示するプラグインがありますので、それを使うとコピペが捗ると思います。

実際の改編

'transformer': function(method) {
    print("Enter transformer.");

    var arrayLength = method.instructions.size();
    var target_instruction = null;

    //search the target instruction from the entire instructions.
    for(var i = 0; i < arrayLength; ++i) {
        var instruction = method.instructions.get(i);
        if(instruction.name === "targetMethodName") {
            target_instruction = instruction;
            break;
        }
    }

    if(target_instruction == null) {
        print("Failed to detect target.");
        return method;
    }

    var toInject = new InsnList();
//  toInject.add(new InsnNode(Opcodes.POP));
    toInject.add(new InsnNode(Opcodes.RETURN));

    // Inject new instructions just after the target.
    method.instructions.insert(target_instruction, toInject);

    return method;
}

実際に改変する部分は、個人的には以前より書きやすくなったと思います。

引数に渡ってくるmethodにはinstructionsというフィールドがあり、ここにバイトコードが配列で一行ずつ入ってます。メソッドのバイトコードそのものがです。
あとは、そのバイトコードの好きな位置を、削除したり、変えたり、returnを入れたりすることになります。

上では、「あるメソッドが呼び出されたらその場でreturnする」という改変を施しています。
原始的に、instructionsをひとつずつforで回して、対象のメソッドが呼ばれてる行(つまりinstruction)を探します。

おめあてのinstructionが見つかったら、新しいinstructionを挿入してやります。

InsnList()は、一連のinstructionの組を作るためのクラスで、これにそれぞれのinstructionをadd()してやります。
ここでは、"new InsnNode(Opcodes.RETURN)"として、"return"を意味するinstructionだけを、InsnListに追加しています。

最後に、instructionsにはinsert(引数1, 引数2)というメソッドが用意されていて、引数1に指定したinstructionの直後に、引数2に指定したinsnListの中身のinstruction達を追加します。

ここでは、特定のメソッドの直後に"return"を追加する処理となります。

注意点ですが、coremodを触ったことのある方ならわかるかと思いますが、好き放題にreturnすると、フレームの整合性が取れなくなってたぶんいまのバージョンでもクラッシュすると思います。
フレームに積んであるけどもう使わない値は、正しい回数だけPOPしないとまずいと思います。

その他

  • 理由はわかりませんが、変数宣言時にletとかconstは使えません。エラーになります。varを使ってください。
  • print()は開発環境でマイクラをデバッグ起動すると、コンソールに出力してくれますが、実環境ではどこにも出てこないみたいです。
  • 一枚のjavascriptファイルで複数のtransformerを返せるみたいですがやり方はよくわかりません。
  • ForgeのASMAPIクラス(これは普通にJavaです)には、あるメソッドが一番最初にコールされる場所を探すとかそういうメソッドも用意されてるみたいなので、一度中身を見てみると良いと思います。

終わりに

例示とか書こうと思いましたが力付きました。また今度余力があったら書きます。

いろいろ書き間違いとか勘違いとかある気がするので気が付いた方は教えてください。

お付き合いいただきありがとうございました。

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

【じゃんけん(っぽい)ゲームで入門】Java

1. はじめに

自作した じゃんけん(っぽい)ゲーム を題材にして Java の文法や仕様を確認していきます。

1-1. ゲームの仕様

ルールは一般的なじゃんけんのルールに準じます。ただし、プレイヤーとCOMには5点の体力が用意されており、じゃんけんに勝利すると相手に1点のダメージを与えることができます。先に相手の体力を0点以下に減らしたほうの勝利となります。引き分けは発生せず、プレイヤーかCOMのどちらかが勝利するまで処理を続行します。

処理内容はコンソールに表示し、コンソールに表示された内容はログファイルにも吐き出すようにします。

1-2. Javaとは?

Javaは、かつて存在していた米国のIT企業 Sun Microsystems(現・Oracle America) によって開発された言語です。1996年1月にJDK 1.0が発表されて以来、継続的にバージョンアップを重ねており、2020年3月に発表されたJava SE 14が最新版となります。

言語の出自を辿ると、元々はC++をベースにした家電製品向け組み込み言語として開発が始まったようです。ただ、開発途中で組み込み対象が家電製品からPDAにシフトしたり、Web開発向けの言語としての側面なども持つようになりました。そのように色々な分野に手を出してきたためか、今日では幅広い業界で使用される極めて汎用性の高い言語にまで成長しました。身近なところだとAndroidアプリの開発などでも使われていますね。 Kotlin「(もうJavaパイセンの時代では)ないです」

20年以上にわたり第一線で活躍し続けてきた実績から、高い信頼性を築き上げてきたJavaですが、JVM言語の台頭や Oracleの発表方法が悪かったため 「Java SE 11より有償化される!?」という誤解が広まったことで、その人気に冷や水を浴びせられた感は否めません。

とはいえ、今なお需要は全言語中でもトップクラスであり、仮に新規開発案件の選択肢から外れるような事態になっても、Javaで書かれたシステムの保守改修案件は残るでしょうから、今後とも数十年というスパンで使われ続けるのは間違いありません。

1-3. 開発環境構築手順

Javaの開発環境を構築するには JDK (Java Development Kit) と呼ばれる、Javaの開発に必要なプログラムを1つにまとめたパッケージを利用をするか、JDKを内包したIDE( EclipseIntelliJ IDEA がメジャーですね)を用いるのが一般的です。

当記事では色々な団体から発表されているJDKの1つである OpenJDK(厳密にはOpenJDK 14x系) を用いた開発環境構築手順を説明します。

※Windows 10 64bit版向けの解説です。他環境での動作は確認していません。

1-3a. OpenJDKをダウンロード

まずはOpenJDKをダウンロードしましょう。OpenJDKを取り扱っているサイトに行きます。

OpenJDK

当該サイト中央の Download という見出しのブロックのなかに、OpenJDK 14x系のダウンロードページへ飛ぶリンクが貼ってあります。リンクをクリックして対象ページへ移動しましょう。

JDK 14.0.1 GA Release

ダウンロードページ中央付近にLinux・macOS・Windowsの、それぞれのOSに対応したJDKのダウンロードリンクが貼ってあります。Windows用JDKのリンクをクリックして、ダウンロードを始めてください。我が家のクソ雑魚Wi-Fiでも1分くらいでダウンロードできましたので、平均水準のWi-Fiや有線ならば即座に完了するはずです。

ダウンロードされたファイルは.zip形式で圧縮されているはずなので解凍してください。解凍されたディレクトリには jdk-14.0.1 といった感じの名前のディレクトリが置いてあるはずです。これがOpenJDKの本体であり、このなかにJavaのコンパイラなどが入っています。

他のプログラミング言語における開発環境構築作業では、ここからさらにインストーラーなどを立ち上げて、開発環境の構築を進める必要があったりしますが、Javaではひとまずこれで一旦完了となります。

1-3b. 動作確認

OpenJDKが正しくダウンロード・解凍されたか確認します。

まずはOpenJDKの本体( jdk-14.0.1 といった名前のディレクトリ)をCドライブ直下などに置いてください。

続いて、当該ディレクトリ内に格納された bin というディレクトリのなかに java.exejavac.exe という実行ファイルが入っていることを確認してください。前者がJavaで書かれたプログラムを実行するためのアプリ、後者がJavaのコンパイラとなります。

動作確認を行うため、コマンドプロンプトやPowerShellなどのシェルを立ち上げて、以下のようにコマンドを叩いてみてください。下記のように表示されたらOKです。

このように表示されたらOKです
C:\jdk-14.0.1\bin> .\java.exe -version
openjdk version "14.0.1" 2020-04-14
OpenJDK Runtime Environment (build 14.0.1+7)
OpenJDK 64-Bit Server VM (build 14.0.1+7, mixed mode, sharing)

C:\jdk-14.0.1\bin>.\javac.exe -version
javac 14.0.1

C:\jdk-14.0.1\bin>

1-3c. Pathを通しておきましょう

先の状態でもJavaの開発はできるのですが、開発しやすいように先の bin というディレクトリにPathを通しておきましょう。

ここまでできたらパーフェクトです。お疲れさまでした。

2. 特記事項

基本的な文法などは記事末尾のソースコードを流し読んで頂くとして、ソースコードからは読み取れない事柄や注意すべきことなどを特記します。

2-1. 拡張子

拡張子は .java です。

2-2. 文字コード

(開発環境ごとに異なるようなのですが)当記事に掲載している手順で開発環境を構築した場合、標準文字コードは MS932 となります。MS932は windows-31j とも呼ばれる文字コードで Shift JIS を拡張したものです。つまるところ、ソースコードに日本語を含まない場合はShift JISかUTF-8、ソースコードに日本語を含む場合はShift JISで書けば問題ありません。

自身の環境の標準文字コードを知りたい場合は以下プログラムを実行してください。

Test.java
class Test {
    public static void main(String[] args) {
        System.out.println(System.getProperty("file.encoding"));
    }
}

ところで(これは私がそうなのですが)皆様のなかには「ソースファイルに日本語を含めたいが、文字コードはUTF-8で書きたい」という人が少なからずいらっしゃるのではないでしょうか。Javaのコンパイラはコンパイル時に文字コードを指定することができますので、以下コマンドを参考にしてください。

UTF-8でコンパイル
C:\Users\AGadget> javac .\Test.java -encoding UTF-8

2-3. コーディング規約

Javaのコーディング規約は色々な団体から発表されているようです。

いずれかのコーディング規約を参考にするのが良いのではないでしょうか。

2-4. コンパイルから実行までの流れ

コンパイルから実行するまでの手順を説明します。

まずはソースファイルを用意します。Javaでは1ファイルに、1クラスを格納し、ファイル名をクラス名に合わせるのがコーディング上でのマナーのようです(注・投稿者調べ)。以下サンプルファイルも、これに倣います。

Test.java
class Test {
    public static void main(String[] args) {
        System.out.println("Hello world.");
    }
}

ソースファイルを用意できましたら、コンパイルして中間ファイル(.classファイル)を生成します。

コンパイル
C:\Users\AGadget> javac .\Test.java

C:\Users\AGadget>

これで Test.class という中間ファイルが作成されたはずです。これをjava.exeで実行します。

実行
C:\Users\AGadget> java Test
Hello world.

C:\Users\AGadget>

Javaのコンパイル・実行手順はちょっとややこしいので、必要に応じて別の解説記事等を参照してください。

2-5. 文字コードを指定してコンパイル

当記事掲載の手順で構築した開発環境では、コンパイラの標準文字コードは Shift JIS になっています。任意の文字コードでコンパイルしたいときは以下のようにコマンドを叩いてください。

UTF-8でコンパイル
C:\Users\AGadget> javac .\Test.javaa -encoding UTF-8

C:\Users\AGadget>

2-6. エントリポイント

Javaのエントリポイントは以下のようなメソッドです。

public static void main(String[] args) {
    // ここがエントリポイント
}

原則として、上記書式を変更する――例えば publicprivate に変更したり、メソッド名を main から Main に変更したりすると、エントリポイントとして認識されなくなりますので注意してください。

2-7. 全ての処理はクラス内に収める

クラスの使用が任意の言語や、そもそもクラスがない言語などとは異なり、Javaでは全ての処理をいずれかのクラス内に格納する必要があります。これはJavaのエントリポイントとなるmainメソッドにも言えることで、mainメソッドを格納するためのクラスを定義する必要があります。

Main.java
/**
 * メインメソッドを格納したメインクラス
 */
class Main {

    /**
     * メインメソッド
     */
    public static void main(String[] args) {
        System.out.println("Hello world.");
    }
}

余談ですが――私が初めてJavaに触ったとき、混乱した仕様の1つがこれでした。それまでC・JavaScriptぐらいしか触ったことが無かったので、この仕様は随分奇妙に感じられました。 正直今でも慣れない。

2-8. ==演算子とequalsメソッド

Javaでは ==演算子 で参照先が等しいかどうか、 equalsメソッド で同じ値かどうかを比較判定します。

Test.java
class Test {
    public static void main(String[] args) {
        StringBuilder a = new StringBuilder("Hello world.");
        StringBuilder b = new StringBuilder("Hello world.");
        System.out.println(a.toString() == b.toString());
        System.out.println(a.toString().equals(b.toString()));
    }
}
実行してみる
C:\Users\AGadget> javac .\Test.java

C:\Users\AGadget> java Test
false
true

C:\Users\AGadget>

2-9. 戻り値があるメソッドでは全ての条件分岐でreturn文が必要

以下例をご覧ください。

Test.java
class Test {

    public static void main(String[] args) {
        System.out.println(testMethod("Windows Mobile"));
    }

    private static String testMethod(String OS) {
        if (OS.equals("AndroidOS")) {
            return "AndroidOSこそ志向";
        }
        if (OS.equals("iOS")) {
            return "iOSこそ志向";
        }
        if (OS.equals("BlackBerry")) {
            return "BlackBerryこそ志向";
        }
        System.out.println("[Error] ウィンモはNG . . . ");
        System.exit(0);
    }

}

このプログラム、一見すると問題無くコンパイルできそうですが、実際には構文エラーとして弾かれます。

C:\Users\AGadget> javac .\Test.java
.\Test.java:19: エラー: return文が指定されていません
    }
    ^
エラー1個

C:\Users\AGadget>

Javaではメソッドが戻り値を返すとき 発生しうる分岐の全て でreturn文が書かれていなければならないようです。

次のように修正するとコンパイルできます。

Test.java
class Test {

    public static void main(String[] args) {
        System.out.println(testMethod("Windows Mobile"));
    }

    private static String testMethod(String OS) {
        if (OS.equals("AndroidOS")) {
            return "AndroidOSこそ志向";
        }
        if (OS.equals("iOS")) {
            return "iOSこそ志向";
        }
        if (OS.equals("BlackBerry OS")) {
            return "BlackBerry OSこそ志向";
        }
        System.out.println("[Error] ウィンモはNG . . . ");
        System.exit(0);
        return "";    // 構文上必要なため実装
    }

}

3. ソースコード

Main.java
import java.util.Scanner;

/**
 * じゃんけん(っぽい)ゲームのメインクラスです
 * @author AGadget
 */
class Main {

    /**
     * メインメソッドです
     */
    public static void main(String[] args) {
        StandardIO standardIO = StandardIO.getInstance();
        Player player = new Player(5, "Qii太郎");
        COM com = new COM(5, "COM");
        TurnCount turnCount = new TurnCount();
        while (player.canGame() && com.canGame()) {
            turnCount.countUp();
            turnCount.printTurnCountMessage();
            player.printStatusMessage(com.getNameWidth());
            com.printStatusMessage(player.getNameWidth());
            standardIO.printlnEmptyLine(1);
            player.choiceAction();
            com.choiceAction();
            standardIO.printlnEmptyLine(1);
            standardIO.println(generateChoicedActionMessage(player.getAction(), com.getAction()));
            player.damageProcess(com.getAction());
            com.damageProcess(player.getAction());
            standardIO.printlnEmptyLine(5);
        }
        standardIO.println(generateGameResultMessage(player.canGame(), com.canGame(), player.getName(), com.getName()));
        standardIO.printlnEmptyLine(1);
        pause();
    }

    /**
     * 双方のゲームキャラクターが選択した行動を表すメッセージを作成して返すメソッドです
     * @param action1 ゲームキャラクター1が選択した行動です
     * @param action2 ゲームキャラクター2が選択した行動です
     * @return メッセージです
     */
    private static String generateChoicedActionMessage(String action1, String action2) {
        return "> " + action1 + " vs " + action2;
    }

    /**
     * ゲームの結果を表すメッセージを作成して返すメソッドです
     * @param canGame1 ゲームキャラクター1がゲーム続行可能であるかのフラグです
     * @param canGame2 ゲームキャラクター2がゲーム続行可能であるかのフラグです
     * @param name1 ゲームキャラクター1の名前です
     * @param name2 ゲームキャラクター2の名前です
     * @return メッセージです
     */
    private static String generateGameResultMessage(boolean canGame1, boolean canGame2, String name1, String name2) {
        if (canGame1 && !canGame2) {
            return "> " + name1 + "の勝利ですっ!";
        }
        if (!canGame1 && canGame2) {
            return "> " + name2 + "の勝利ですっ!";
        }
        System.out.println("[Error] 引き分けが発生しました");
        System.exit(0);
        return "";    // 文法上のエラーになるため、処理上では不要なreturn文を書いています
    }

    /**
     * Enterキーが押されるまで処理を止めるメソッドです
     */
    private static void pause() {
        System.out.print("Enterキーを押してください . . . ");
        Scanner scanner = new Scanner(System.in);
        scanner.nextLine();
    }
}
StandardIO.java
import java.util.Calendar;
import java.text.SimpleDateFormat;
import java.io.IOException;
import java.io.FileWriter;
import java.io.BufferedWriter;
import java.io.PrintWriter;
import java.util.Scanner;

/**
 * コンソール・ログファイルへの入出力を一手に担うクラスです
 * Singletonデザインパターンを採用しています
 * @author AGadget
 */
class StandardIO {
    private static StandardIO instanceStandardIO = new StandardIO();    // 当オブジェクトのインスタンスです
    private String logFilePath;    // ログファイルのパスです

    /**
     * コンストラクタです
     * Calendarオブジェクトを用い、現在日時からログファイル名を作成しています
     */
    private StandardIO() {
        Calendar calendar = Calendar.getInstance();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyMMddHHmmss");
        this.logFilePath = "log_" + simpleDateFormat.format(calendar.getTime()) + ".txt";
    }

    /**
     * 当オブジェクトのインスタンスを返すメソッドです
     * Singletonデザインパターンを採用しているため必要となるメソッドです
     * @return 当オブジェクトのインスタンスです
     */
    public static StandardIO getInstance() {
        return instanceStandardIO;
    }

    /**
     * 引数に指定された文字列をコンソールとログファイルに出力するメソッドです
     * 出力する値の末尾に改行文字が付きます
     * @param message 出力する文字列です
     */
    void println(String message) {
        System.out.println(message);
        try {
            FileWriter fileWriter = new FileWriter(this.logFilePath, true);
            PrintWriter printWriter = new PrintWriter(new BufferedWriter(fileWriter));
            printWriter.println(message);
            printWriter.close();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    /**
     * 任意の数の空行をコンソールとログファイルに出力するメソッドです
     * 行数は引数で指定することができます
     * @param numberOfEmptyLine
     */
    void printlnEmptyLine(int numberOfEmptyLine) {
        for (int i = 0; i < numberOfEmptyLine; i += 1) {
            this.println("");
        }
    }

    /**
     * 引数に指定された文字列をコンソールとログファイルに出力するメソッドです
     * 出力する価の末尾に改行文字は付きません
     * @param message 出力する文字列です
     */
    void print(String message) {
        System.out.print(message);
        try {
            FileWriter fileWriter = new FileWriter(this.logFilePath, true);
            PrintWriter printWriter = new PrintWriter(new BufferedWriter(fileWriter));
            printWriter.print(message);
            printWriter.close();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    /**
     * 標準入力を受け付け、入力された値をログファイルに出力するとともに、呼び出し元に返すメソッドです
     * @return 標準入力された値です
     */
    String readLine() {
        Scanner scanner = new Scanner(System.in);
        String input = scanner.nextLine();
        this.printlnOnlyToLogFile(input);
        return input;
    }

    /**
     * 引数に指定された文字列をログファイルに「だけ」出力するメソッドです
     * → 標準入力処理を担当するメソッドとセットで使うことを想定しています
     * 出力する値の末尾に改行文字が付きます
     * @param message 出力する文字列です
     */
    private void printlnOnlyToLogFile(String message) {
        try {
            FileWriter fileWriter = new FileWriter(this.logFilePath, true);
            PrintWriter printWriter = new PrintWriter(new BufferedWriter(fileWriter));
            printWriter.println(message);
            printWriter.close();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
}
GameCharacter.java
/**
 * ゲームキャラクターを表す抽象クラスです
 * @author AGadget
 */
abstract class GameCharacter {
    protected StandardIO standardIO = StandardIO.getInstance();    // 出力処理を担当するオブジェクトです
    private final int MAX_LIFE = 5;    // 全ゲームキャラクター共通となる体力の上限値です
    private int life;    // 体力です
    private String name;    // 名前です
    protected final String[] ACTIONS = {"グー", "チョキ", "パー"};    // 選択できる行動の一覧です
    protected String action;    // 選択した行動です

    /**
     * 行動を選択する抽象メソッドです
     * 何らかの方法で行動を選択し、選択した行動を保持するフィールドに選択した行動を代入する処理を実装してください
     */
    abstract void choiceAction();

    /**
     * コンストラクタです
     * @param initialLife ゲーム開始時点の体力です
     * @param name ゲームキャラクターの名前です
     */
    GameCharacter(int initialLife, String name) {
        if (initialLife < 0 && initialLife > this.MAX_LIFE ? true : false) {
            System.out.println("[Error] ゲーム開始時点の初期値力値が不正です(" + initialLife + ")");
            System.exit(0);
        }
        this.life = initialLife;
        this.name = name;
    }

    /**
     * ゲームの続行が可能か返答するメソッドです
     * @return 続行可能ならtrueを返します
     */
    boolean canGame() {
        return this.life > 0 ? true : false;
    }

    /**
     * 名前の横幅を返すメソッドです
     * String.length()などで取得できる値とは異なり、半角文字を1、全角文字を2としてカウントした値を返します
     */
    int getNameWidth() {
        char[] nameToCharArray = this.name.toCharArray();
        int nameWidth = 0;
        for (int i = 0; i < nameToCharArray.length; i += 1) {
            nameWidth += String.valueOf(nameToCharArray[i]).getBytes().length == 1 ? 1 : 2;
        }
        return nameWidth;
    }

    /**
     * 自身の状態をメッセージにして返すメソッドです
     * メッセージは名前部分と体力部分から成ります
     * → 名前部分は相手の名前の長さと横幅を揃えるようにします
     * → 体力部分は記号を使って体力ゲージっぽく表現します
     * @param opponentNameWidth 対戦相手の名前の横幅(半角文字を1、全角文字を2としてカウントした特殊な値)です
     */
    void printStatusMessage(int opponentNameWidth) {
        StringBuilder namePart = new StringBuilder(this.name);
        for (int i = 0; i < opponentNameWidth - this.getNameWidth(); i += 1) {
            namePart.append(" ");
        }
        StringBuilder lifePart = new StringBuilder();
        for (int i = 0; i < this.life; i += 1) {
            lifePart.append("■");
        }
        for (int i = 0; i < this.MAX_LIFE - this.life; i += 1) {
            lifePart.append("□");
        }
        standardIO.println(namePart.toString() + ": " + lifePart.toString());
    }

    /**
     * 選択した行動を返すメソッドです
     * @return 選択した行動です
     */
    String getAction() {
        return this.action;
    }

    /**
     * ダメージ処理となるメソッドです
     * 自身と相手の行動の組み合わせから、ダメージが発生するか確認し、ダメージが発生するならば体力を減らします
     * @param opponentAction 対戦相手の行動です
     */
    void damageProcess(String opponentAction) {
        if (this.action.equals(this.ACTIONS[0]) && opponentAction.equals(this.ACTIONS[2])
            || this.action.equals(this.ACTIONS[1]) && opponentAction.equals(this.ACTIONS[0])
            || this.action.equals(this.ACTIONS[2]) && opponentAction.equals(this.ACTIONS[1])
        ) {
            this.life -= 1;
            return;
        }
    }

    /**
     * 名前を返すメソッドです
     * @return 名前です
     */
    String getName() {
        return this.name;
    }
}
Player.java
/**
 * プレイヤーを表すクラスです
 * @author AGadget
 */
class Player extends GameCharacter {

    /**
     * コンストラクタです
     * @param initialLife ゲーム開始時点の体力です
     * @param name ゲームキャラクターの名前です
     */
    Player(int initialLife, String name) {
        super(initialLife, name);
    }

    /**
     * 行動を選択するメソッドです
     * プレイヤー用の処理なので標準入力を使った処理を実装しました
     */
    @Override
    void choiceAction() {
        String prompt = this.generatePrompt();
        while (true) {
            standardIO.print(prompt);
            String input = standardIO.readLine();
            try {
                this.action = this.ACTIONS[Integer.parseInt(input) - 1];
                return;
            } catch (Exception error) {
                standardIO.printlnEmptyLine(1);
                standardIO.println("> 不正な入力です");
                standardIO.println("> 再入力してください");
                standardIO.printlnEmptyLine(1);
            }
        }
    }

    /**
     * 行動選択処理で必要になるプロンプトを作成して返すメソッドです
     * @return プロンプトとなる文字列です
     */
    private String generatePrompt() {
        StringBuilder prompt = new StringBuilder();
        for (int i = 0; i < this.ACTIONS.length; i += 1) {
            prompt.append("[" + (i + 1) + "] " + this.ACTIONS[i] + "    ");
        }
        prompt.append(": ");
        return prompt.toString();
    }
}
COM.java
import java.util.Random;

/**
 * COMを表すクラスです
 * @author AGadget
 */
class COM extends GameCharacter {

    /**
     * コンストラクタです
     * @param initialLife ゲーム開始時点の体力です
     * @param name ゲームキャラクターの名前です
     */
    COM(int initialLife, String name) {
        super(initialLife, name);
    }

    /**
     * 行動を選択するメソッドです
     * COM用の処理なので乱数を使った処理を実装しました
     */
    @Override
    void choiceAction() {
        Random random = new Random();
        this.action = ACTIONS[random.nextInt(ACTIONS.length)];
    }
}
TurnCount.java
/**
 * ターン数を表すクラスです
 * @author AGadget
 */
class TurnCount {
    private StandardIO standardIO = StandardIO.getInstance();    // 出力処理を担当するオブジェクトです
    private int turnCount;    // ターン数です

    /**
     * コンストラクタです
     */
    TurnCount() {
        turnCount = 0;
    }

    /**
     * ターン数のカウントを進めるメソッドです
     */
    void countUp() {
        this.turnCount += 1;
    }

    /**
     * 現在のターン数をメッセージとして出力するメソッドです
     */
    void printTurnCountMessage() {
        standardIO.println("【第" + this.turnCount + "ターン】");
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【覚えておくと便利!!!】LocalDate型 から 文字列 、 文字列 から LocalDate型 に変換する方法

1.事前知識

事前知識として、上記リンクの内容が必要です。

2.LocalDate型 から 文字列 、 文字列 から LocalDate型 に変換する方法

01.png
01.png
1. Eclipseを起動し、 [ファイル(F)]→[新規(N)]→[Java プロジェクト] を選択する。
02.png
2. プロジェクト名(P)に LocalDateTest と入力し、 実行JREの使用(V)を JavaSE-1.8 を選択して 完了(F) > ボタンをクリックする。
03.png
3. [src] を右クリックして [新規(N)]→[クラス] を選択する。   
04.png
4. 名前に Test と入力し、 public static void main(String[] args)(V) にチェックをつけて 完了(F) ボタンをクリックする。

Test.java

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

public class Test {

    public static void main(String[] args) {

        // 変換する文字列
        String date1 = "2020/07/07";

        // Stringからjava.time.LocalDateに変換する
        LocalDate test1 = LocalDate.parse(date1, DateTimeFormatter.ofPattern("yyyy/MM/dd"));

        // java.time.LocalDateからStringに変換する
        String test2 = test1.format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));

        // 変換内容を表示
        System.out.println(date1 + " → "  + test1);
        System.out.println(test1 + " → "  + test2);

    }

}

5.上記のソースコードを Test.java に入力する。
06.png
6.Test.java を右クリックして [実行(R)]→[2 Javaアプリケーション] を選択する。   
05.png
7.画像のように表示されれば成功。 

3.関連

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

Eclipse2020を使っていたらビルドエラーが止まらなくなった話

先日JavaのSpringチーム開発を行っていたところ私を含むチームメンバーの2人に同じエラーが発生しほとんど操作不能になりました。
根本的な原因は分かりませんが結論からいうと最新版のeclipseだけに発生する現象だったようです。

環境

  • IDE
    • Eclipse 2020-6
    • SpringToolSuite4(Eclipse4.16)
  • Java
    • 11
  • フレームワーク
    • Spring Boot
  • OS
    • Windows / Mac共に発生

現象

スコープ外のオブジェクトへの参照やstaticメンバの参照エラーなどのコンパイルエラーが発生する状況で自動ビルドが行われると以下のようにビルドエラーが繰り返されてしまう。
Eclipseの再起動もしくはプロジェクトのプロパティ→builderからJava builderを外す1まで止まらない。
スクリーンショット 2020-07-07 17.13.52.png

エラー詳細には
'Errors running builder 'Java Builder' on project 'なんとかプロジェクト'. java.lang.NullPointerException'
つまり「Javaビルダーの実行中にヌルポが発生した」とのこと。

試した処置

  • プロジェクトをワークスペースから完全に削除後再度 git clone
  • JDKバージョンの変更
  • 依存関係の再ビルド&再インストール

どれも全てダメでした。

解決方法

Eclipseのバージョンを2019_12に落とした。

たったこれだけですが全く同じプロジェクトなのにビルドエラーが出なくなりました。
メンバー二人とも同じ状況なのでおそらくIDEのバグだと思います。
同じようなエラーを解決した記事が見つからなかったので共有します。


  1. これをするとIDEがただのメモ帳と化す。 

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

Eclipse 2020を使っていたらビルドエラーが止まらなくなった話

先日JavaのSpringチーム開発を行っていたところ私を含むチームメンバーの2人に同じエラーが発生しほとんど操作不能になりました。
根本的な原因は分かりませんが結論からいうと最新版のeclipseだけに発生する現象だったようです。

環境

  • IDE
    • Eclipse 2020-6
    • SpringToolSuite4(Eclipse4.16)
  • Java
    • 11
  • フレームワーク
    • Spring Boot
  • OS
    • Windows / Mac共に発生

現象

スコープ外のオブジェクトへの参照やstaticメンバの参照エラーなどのコンパイルエラーが発生する状況で自動ビルドが行われると以下のようにビルドエラーが繰り返されてしまう。
Eclipseの再起動もしくはプロジェクトのプロパティ→builderからJava builderを外す1まで止まらない。
スクリーンショット 2020-07-07 17.13.52.png

エラー詳細には
'Errors running builder 'Java Builder' on project 'なんとかプロジェクト'. java.lang.NullPointerException'
つまり「Javaビルダーの実行中にヌルポが発生した」とのこと。

試した処置

  • プロジェクトをワークスペースから完全に削除後再度 git clone
  • JDKバージョンの変更
  • 依存関係の再ビルド&再インストール

どれも全てダメでした。

解決方法

Eclipseのバージョンを2019_12に落とした。

たったこれだけですが全く同じプロジェクトなのにビルドエラーが出なくなりました。
メンバー二人とも同じ状況なのでおそらくIDEのバグだと思います。
同じようなエラーを解決した記事が見つからなかったので共有します。


  1. これをするとIDEがただのメモ帳と化す。 

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

Apache JMeterをインストールする

最終更新日

2020年7月8日

検証を行ったOS/JDKのバージョンは下記です。

  • macOS Catalina 10.15.5
    • 未検証ですが、Windowsでも手順は同様のはずです
  • AdoptOpenJDK 11.0.7

インストールするもののバージョンは下記です。

  • Apache JMeter 5.3

この記事が古くなった場合、下記の手順は最新のインストール手順とは異なっている可能性があります。その場合は公式ドキュメントをご確認ください。

JMeterのインストール

(1) http://jmeter.apache.org/download_jmeter.cgi にアクセスして、apache-jmeter-5.3.zipをダウンロードしてください。

スクリーンショット 2020-07-08 17.10.36.png

(2) ダウンロードしたZIPファイルを、適当なフォルダに展開してください。

(3) 環境変数 PATHJMeterを展開したフォルダ/bin を追加してください(例: /Users/user01/apache-jmeter-5.3/bin )。

(4) ターミナル(orコマンドプロンプト)で jmeter --version を実行してください。

jmeter --version
WARNING: package sun.awt.X11 not in java.desktop
    _    ____   _    ____ _   _ _____       _ __  __ _____ _____ _____ ____
   / \  |  _ \ / \  / ___| | | | ____|     | |  \/  | ____|_   _| ____|  _ \
  / _ \ | |_) / _ \| |   | |_| |  _|    _  | | |\/| |  _|   | | |  _| | |_) |
 / ___ \|  __/ ___ \ |___|  _  | |___  | |_| | |  | | |___  | | | |___|  _ <
/_/   \_\_| /_/   \_\____|_| |_|_____|  \___/|_|  |_|_____| |_| |_____|_| \_\ 5.3

Copyright (c) 1999-2020 The Apache Software Foundation
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Zipkinのダウンロードと起動

最終更新日

2020年7月8日

検証を行ったOS/JDKのバージョンは下記です。

  • macOS Catalina 10.15.5
    • 未検証ですが、Windowsでも手順は同様のはずです(curlとbashを利用する場合、事前にGit BashまたはWSLのインストールが必要なはずです)
  • AdoptOpenJDK 11.0.7

インストールするもののバージョンは下記です。

  • Zipkin 2.21.5

この記事が古くなった場合、下記の手順は最新のインストール手順とは異なっている可能性があります。その場合は公式ドキュメントをご確認ください。

JARをダウンロードする方法

(1) ブラウザで https://search.maven.org にアクセスして、[Search]に「zipkin-server」と入力してください。 io.zipkinzipkin-server にある[2.21.5]の部分をクリックしてください。

この部分には、その時点での最新バージョンが表示されます。 2.21.5 と違っていても、表示されているバージョンを使って構いません。

スクリーンショット 2020-07-10 9.42.46.png

(2) [Download]をクリックして、[exec.jar]をクリックしてください。

スクリーンショット 2020-07-10 9.44.57.png

(2) ターミナル(orコマンドプロンプト)でJARをダウンロードしたフォルダに移動して、 java -jar zipkin-server-2.21.5-exec.jar を実行してください。

$ java -jar zipkin-server-2.21.5-exec.jar

                  oo
                 oooo
                oooooo
               oooooooo
              oooooooooo
             oooooooooooo
           ooooooo  ooooooo
          oooooo     ooooooo
         oooooo       ooooooo
        oooooo   o  o   oooooo
       oooooo   oo  oo   oooooo
     ooooooo  oooo  oooo  ooooooo
    oooooo   ooooo  ooooo  ooooooo
   oooooo   oooooo  oooooo  ooooooo
  oooooooo      oo  oo      oooooooo
  ooooooooooooo oo  oo ooooooooooooo
      oooooooooooo  oooooooooooo
          oooooooo  oooooooo
              oooo  oooo

     ________ ____  _  _____ _   _
    |__  /_ _|  _ \| |/ /_ _| \ | |
      / / | || |_) | ' / | ||  \| |
     / /_ | ||  __/| . \ | || |\  |
    |____|___|_|   |_|\_\___|_| \_|

:: version 2.21.5 :: commit 7f4f274 ::

2020-07-10 09:47:26.743  INFO 1588 --- [           main] z.s.ZipkinServer                         : Starting ZipkinServer on tadanoMacBook-Pro.local with PID 1588 (/Users/tada/Downloads/zipkin-server-2.21.5-exec.jar started by tada in /Users/tada/Downloads)
2020-07-10 09:47:26.746  INFO 1588 --- [           main] z.s.ZipkinServer                         : The following profiles are active: shared
2020-07-10 09:47:27.795  INFO 1588 --- [           main] c.l.a.c.u.SystemInfo                     : hostname: tadanomacbook-pro.local (from 'hostname' command)
2020-07-10 09:47:28.191  INFO 1588 --- [oss-http-*:9411] c.l.a.s.Server                           : Serving HTTP at /0:0:0:0:0:0:0:0:9411 - http://127.0.0.1:9411/
2020-07-10 09:47:28.193  INFO 1588 --- [           main] c.l.a.s.ArmeriaAutoConfiguration         : Armeria server started at ports: {/0:0:0:0:0:0:0:0:9411=ServerPort(/0:0:0:0:0:0:0:0:9411, [http])}
2020-07-10 09:47:28.211  INFO 1588 --- [           main] z.s.ZipkinServer                         : Started ZipkinServer in 2.428 seconds (JVM running for 3.746)

Ctrl + Cで停止します。

Dockerを利用する方法

$ docker run -d -p 9411:9411 openzipkin/zipkin

curl + bashを利用する方法

$ curl -sSL https://zipkin.io/quickstart.sh | bash -s
$ java -jar zipkin.jar
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

substringで指定範囲だけ大文字にする。(substringの使い方)

/*
substringは文字を抜き出す。
引数には文字のインデックスが入る。
※インデックスは0から
*/

/*
引数が二つの場合
例:
String str = "012345";
substring(開始位置, 終了位置);
substring(0, 3)
抜き出される文字は[012]
※終了位置は3であるが抜き出される文字には含まれない。

引数が1つの場合は開始位置とする。
例:
String str = "012345";
substring(開始位置);
substring(3);
抜き出される文字は[345]
*/

/*
※空白はインデックスに含まれる
String str = "0 12345";(半角の空白)
substring(開始位置);
substring(3);
抜き出される文字は[2345]になる
*/

import java.util.*;
public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        int a = sc.nextInt();
        int b = sc.nextInt();
        sc.nextLine();
        String str = sc.nextLine();

        System.out.println(str.substring(0, a - 1) + str.substring(a - 1, b).toUpperCase() + str.substring(b));
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

substringで指定範囲だけ大文字にする。

/*
substringは文字を抜き出す。
引数には文字のインデックスが入る。
※インデックスは0から
*/

/*
引数が二つの場合
例:
String str = "012345";
substring(開始位置, 終了位置);
substring(0, 3)
抜き出される文字は[012]
※終了位置は3であるが抜き出される文字には含まれない。

引数が1つの場合は開始位置とする。
例:
String str = "012345";
substring(開始位置);
substring(3);
抜き出される文字は[345]
*/

/*
※空白はインデックスに含まれる
String str = "0 12345";(半角の空白)
substring(開始位置);
substring(3);
抜き出される文字は[2345]になる
*/

import java.util.*;
public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        int a = sc.nextInt();
        int b = sc.nextInt();
        sc.nextLine();
        String str = sc.nextLine();

        System.out.println(str.substring(0, a - 1) + str.substring(a - 1, b).toUpperCase() + str.substring(b));
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【SpringBoot入門】thymeleafを使ってフォーム送信

目的

Spring Quickstart Guideを取り組み終えた方、SpringBootを学び始めた方、復習をしたい方に向けて、

公式ガイドHandling Form Submissionを実際に取り組み学んだことを共有します。

完成形はこちらになります。

Form画面で入力された値をResult画面で表示させるという処理を実装していきます。

スクリーンショット 2020-07-08 13.44.23.png

スクリーンショット 2020-07-08 13.44.30.png

開発環境、これまでのおさらいは以下になります。

開発環境
OS: macOS Mojave バージョン10.14.6
テキストエディタ: Visual Studio Code(以下VSCode)
Java: 11.0.2

QuickstartGuideのおさらいはこちらから
Building a RESTful Web Service編のおさらいはこちらから
Consuming a RESTful Web Service編のおさらいはこちらから
Accessing Data with JPA編のおさらいはこちらから

1.SpringBoot projectを始めよう!

まずは、spring initializrにアクセスします。

1.ADD DEPENDENCIESボタンをクリックして、Spring WebThymeleafを追加。
2.Artifact, Nameは、handling-form-submissionに変更。
3.Javaを11に変更。

そしてGENERATEボタンをクリックしてZipファイルをダウンロードします。

スクリーンショット 2020-07-08 9.35.18.png

ダウンロードしたZipファイルを展開したら準備完了です。

2.コードを追加しよう!

先ほどのフォルダをVSCodeで開きます。
拡張機能のJava Extension Packのインストールの推奨します。と言われるのでインストールしておきましょう。

スクリーンショット 2020-06-30 10.08.25.png

GreetingController.javaを作成しよう!

src/main/java/com/example/handlingformsubmission/ にGreetingController.javaファイルを作成します。

スクリーンショット 2020-07-08 9.55.33.png

公式を参考にコードを追加します。

GreetingController.java
package com.example.handlingformsubmission;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;

@Controller
public class GreetingController {

  @GetMapping("/greeting")
  public String greetingForm(Model model) {
    model.addAttribute("greeting", new Greeting());
    return "greeting";
  }

  @PostMapping("/greeting")
  public String greetingSubmit(@ModelAttribute Greeting greeting) {
    return "result";
  }

}

追加したコードを深掘りしていきます。

@Controller

GreetingController.java
@Controller
public class GreetingController {
  // 省略
}

@Controllerというアノテーションをつける事によってSpringBootは、このクラスをコントローラとして扱ってくれます。
Viewに遷移してHTMLを作成するので、後述するメソッドの戻り値にViewを指定しなければいけません。

今回は登場しませんが、@RestControllerというアノテーションも同様にコントローラとして扱ってくれますが、Viewに遷移せずメソッドの戻り値がレスポンスのコンテンツになります。

②greetingFormメソッド

GreetingController.java
@GetMapping("/greeting")
public String greetingForm(Model model) {
  model.addAttribute("greeting", new Greeting());
  return "greeting";
}

1.@GetMapping
このアノテーションを付与する事で、()内に記述したURLでGETリクエストがあった時に付与されたメソッドが呼び出されるようになります。
今回は@GetMapping("/greeting")です。したがって、http://localhost8080/greetingでGETリクエストがあった時、greetingFormメソッドが呼ばれます。

2.model.addAttribute
メソッドの引数にModelクラスの引数を受け取っています。これは、View側に渡すデータを設定するためです。
addAttribute(第一引数、第二引数)メソッドを用いる事でView側にデータを渡しており、
第二引数にデータを設定して、第一引数の名前でView側でそのデータを使用することが出来ます。

今回は、インスタンス化されたGreetingオブジェクトgreetingという名前でViewに渡しています。
Greetingの実装は後ほど。

3.メソッドの戻り値
メソッドの戻り値にViewを指定しています。今回はgreetingをreturnしています。
という事は、greeting.htmlが必要であるという事になります。実装は後ほど。

②greetingSubmitメソッド

GreetingController.java
@PostMapping("/greeting")
public String greetingSubmit(@ModelAttribute Greeting greeting) {
  return "result";
}

1.@PostMapping
このアノテーションを付与する事で、()内に記述したURLでPOSTリクエストがあった時に付与されたメソッドが呼び出されるようになります。
今回は@PostMapping("/greeting")です。したがって、http://localhost8080/greetingでPOSTリクエストがあった時、greetingSubmitメソッドが呼ばれます。

2.@ModelAttribute
メソッドの引数にあるこのアノテーションは、指定したクラスにPOSTで送られてきた値をセットするためのものです。
今回は、greeting変数に送られてきた値が格納されており、Greetingクラスの各変数に値がセットされます。

3.メソッドの戻り値
メソッドの戻り値にViewを指定しています。今回はresultをreturnしています。
という事は、result.htmlが必要であるという事になります。実装は後ほど。

Greeting.javaを作成しよう!

src/main/java/com/example/handlingformsubmission/ にGreeting.javaファイルを作成します。

スクリーンショット 2020-07-08 13.23.40.png

公式を参考にコードを追加します。

Greeting.java
package com.example.handlingformsubmission;

public class Greeting {

  private long id;
  private String content;

  public long getId() {
    return id;
  }
  public void setId(long id) {
    this.id = id;
  }

  public String getContent() {
    return content;
  }
  public void setContent(String content) {
    this.content = content;
  }

}

追加したコードを深掘りしていきます。

1.変数の宣言

Greeting.java
private long id;
private String content;

long型のidString型のcontentと2つの変数を宣言しています。
アクセス修飾子はprivateなので同一クラス内からしかアクセス出来ません。

2.ゲッター、セッターメソッドの定義

Greeting.java
public long getId() {
  return id;
}
public void setId(long id) {
  this.id = id;
}

public String getContent() {
  return content;
}
public void setContent(String content) {
  this.content = content;
}

formで送信された値を格納したり、取り出したりするためのゲッター、セッターメソッドを用意します。

greeting.htmlを作成しよう!

src/main/resources/templates/ にgreeting.htmlファイルを作成します。
このhtmlファイルはフォーム画面を表示するためのものです。

スクリーンショット 2020-07-08 13.52.37.png

公式を参考にコードを追加します。

greeting.html
<!DOCTYPE HTML>
<html xmlns:th="https://www.thymeleaf.org">
<head>
    <title>Getting Started: Handling Form Submission</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
    <h1>Form</h1>
    <form action="#" th:action="@{/greeting}" th:object="${greeting}" method="post">
      <p>Id: <input type="text" th:field="*{id}" /></p>
      <p>Message: <input type="text" th:field="*{content}" /></p>
      <p><input type="submit" value="Submit" /> <input type="reset" value="Reset" /></p>
    </form>
</body>
</html>

追加したコードのthymeleafの記述について深掘りしていきます。

thymeleafとは、springbootで扱う事が出来るテンプレートエンジンです。th:〇〇と記述します。
日本語で書かれたthymeleafチュートリアルもあります!

th:action

formタグのaction属性の内容を置換しています。記述の仕方は、th:action="@{}"です。
method="post"となっているので、Submitボタンが押された時にGreetingControllerのgreetingSubmitメソッドが呼ばれます。

th:object

th:objectでオブジェクトを指定しています。これにより、オブジェクト内の変数の参照の仕方がgreeting.idではなく、*{id}のような記述方法が可能になります。

th:field

th:objectで指定したオブジェクト内の変数を表示するためにth:field="*{変数名}"と記述します。
今回は、Greetingクラスの中にid、contentがあるので、th:field="*{id}"th:field="*{content}"となります。
また、th:field="*{変数名}"の中に記述した変数名がinputのid属性とname属性になります。

result.htmlを作成しよう!

src/main/resources/templates/ にresult.htmlファイルを作成します。
このhtmlファイルはフォーム画面から送られた結果を表示するためのものです。

スクリーンショット 2020-07-08 14.57.32.png

公式を参考にコードを追加します。

result.html
<!DOCTYPE HTML>
<html xmlns:th="https://www.thymeleaf.org">
<head>
    <title>Getting Started: Handling Form Submission</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
    <h1>Result</h1>
    <p th:text="'id: ' + ${greeting.id}" />
    <p th:text="'content: ' + ${greeting.content}" />
    <a href="/greeting">Submit another message</a>
</body>
</html>

追加したコードのthymeleafの記述について深掘りしていきます。

th:text

th:text="{変数名}"とする事で変数をテキストとして表示する事が出来ます。
文字列も混ぜて表示する際は、''で文字列を囲い、で連結させる事で文字列と変数を結合して表示出来ます。

今回は、フォームで送られてきたGreetingクラスのidとcontentを表示しているので、
th:text="'id: ' + ${greeting.id}"th:text="'content: ' + ${greeting.content}"となります。

3.実行してみよう!

アプリケーション実行の準備が出来たので確認しましょう。

ターミナルで以下のコマンドを入力してEnterしてください。

ターミナル
$ ./mvnw spring-boot:run

そして、http://localhost:8080/greetingにアクセスすると以下のフォーム画面が表示されるはずです。(greeting.htmlが表示される)

スクリーンショット 2020-07-08 15.23.43.png

IdとMessageに任意の値を入力して、Submitボタンをクリックしてください。
そうするとResult画面が表示されるはずです。(result.htmlが表示される)

スクリーンショット 2020-07-08 15.25.36.png

完成です!お疲れ様でした。

参考サイト

@ModelAttribute を使う
Thymeleafの使い方
Spring Boot で Thymeleaf 使い方メモ

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

nextIntとnextLineとsubstringを使うととエラーになる。

import java.util.Scanner;

/*
原因はnextInt
nextIntで123と入力すると
Java側では123\nに変換され(末尾に\nが入る。)
nextIntは123だけを返す
次にnextLineを入力すると先ほどの残った\nが読み込まれ
エラーが発生する。

解決法は
変数への代入を伴わないnextLineを記述する。(以下のソースのscanner.nextLine())
これをすることで\nが消去され次にnextLineが正常に動く

※nextIntは\nを消す機能がないためnextLineを入れる必要がある。

*/
public class Hello {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        int a = 0;
        int b = 0;
        String str = "";

        a = scanner.nextInt();
        b = scanner.nextInt();
        scanner.nextLine();
        str = scanner.nextLine();
        System.out.println(str.substring(a - 1, b));
    }
 }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

nextIntとnextLineとsubstringを使うとエラーになる。

import java.util.Scanner;

/*
原因はnextInt
nextIntで123と入力すると
Java側では123\nに変換され(末尾に\nが入る。)
nextIntは123だけを返す
次にnextLineを入力すると先ほどの残った\nが読み込まれ
エラーが発生する。

解決法は
変数への代入を伴わないnextLineを記述する。(以下のソースのscanner.nextLine())
これをすることで\nが消去され次にnextLineが正常に動く

※nextIntは\nを消す機能がないためnextLineを入れる必要がある。
※バックスラッシュとnで改行を表す。

*/
public class Hello {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        int a = 0;
        int b = 0;
        String str = "";

        a = scanner.nextInt();
        b = scanner.nextInt();
        scanner.nextLine();
        str = scanner.nextLine();
        System.out.println(str.substring(a - 1, b));
    }
 }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

nextIntとnextLineとsubstringを使うとエラーになった。

import java.util.Scanner;

/*
原因はnextInt
nextIntで123と入力すると
Java側では123\nに変換され(末尾に\nが入る。)
nextIntは123だけを返す
次にnextLineを入力すると先ほどの残った\nが読み込まれ
エラーが発生する。

解決法は
変数への代入を伴わないnextLineを記述する。(以下のソースのscanner.nextLine())
これをすることで\nが消去され次にnextLineが正常に動く

※nextIntは\nを消す機能がないためnextLineを入れる必要がある。
※バックスラッシュとnで改行を表す。

*/
public class Hello {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        int a = 0;
        int b = 0;
        String str = "";

        a = scanner.nextInt();
        b = scanner.nextInt();
        scanner.nextLine();
        str = scanner.nextLine();
        System.out.println(str.substring(a - 1, b));
    }
 }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Javaのエントリーポイントについて

エントリーポイントとは

プログラムの実行を開始する場所のことで、一番最初に呼ばれるメソッドになります。メソッドは、メソッドからメソッドを呼び、さらにその次のメソッドを呼ぶ、という流れで実行されていきます。その中で一番最初に呼ばれるメソッドをエントリーポイント(開始点)と言います。
Javaではmainメソッドがプログラム全体のエントリーポイントに設定されます(C、C++、Objective-C等の多言語でもmainメソッドがエントリーポイントとして使われます)。
なのでJavaでの エントリーポイント = mainメソッドと覚えておけば良さそうです。
プログラム全体のエントリーポイントとなる場所を含むルーチンをメインルーチンといい、mainメソッドを含むクラスがメインルーチンということになります。

mainメソッドの5つの条件

Javaではmainメソッドで使う修飾子や名前、引数などのルールが厳密に決められています。

1.アクセス修飾子はpublic
2.staticメソッド
3.メソッドの戻り値はvoid
4.メソッド名はmain(小文字)
5.メソッドの引数はStringの配列(またはStringの可変長引数1)のみ

また、Javaのメソッドは必ず何かのクラスに属してないといけないという決まりがあります。

以上の条件を踏まえた一般的なmainメソッドは以下のようになります。

main.java
class Main {

    public static void main (String[] args) { 
        // 処理
    }
}

ちなみに、よくある引数「args」は、引数を表すargumentsを省略したものになります。

また、次のように上記の5つの条件を外れたものは、mainメソッドとしては認識されません

・引数にString型の配列を渡してない
・String型の配列以外に別の引数を渡している
・メソッド名がmainじゃない
・publicやstatic、voidを使ってない

参考サイト:

以下のサイトがとても勉強になりました。mainメソッドについてさらに詳しい解説もあります。
Javaの実行に欠かせないmainメソッド、その仕組みからしっかり解説


  1. ... 個数が不定(可変)である引数のこと。 

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

ArrayListを使ったバブルソート(JAVA)

import java.util.*;
public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        //ArrayListのlistを宣言
        ArrayList<Integer> list = new ArrayList<Integer>();
        //countは繰り返し回数
        int count = 0;
        count = scanner.nextInt();

        //listに入力値を格納
        for (int i = 0; i < count; i++) {
            list.add(scanner.nextInt());
        }
         //ArrayListのバブルソート
         for (int i = 0; i < list.size() - 1; i++) {
            for (int j = list.size() - 1; j > i; j--) {
                if (array.get(j - 1) > list.get(j)) {
                    // 入れ替え
                    int tmp = list.get(j - 1);
                    list.set(j -1, list.get(j));
                    list.set(j, tmp);
                }
             }
        }
        //拡張for文
        for(int a: list) {
            System.out.println(a);
        }      
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Java リテラルについて

リテラルとは

値の表記のことをリテラルといいます。変更されないことを前提とした値で、数値や文字列を直接記述した定数のことです。
リテラル(literal)とは英語で、「もじどおりの、言葉本来の意味通りの」といった意味を持ちます。

リテラルは全部で6種類あります。

文字リテラル

値をシングルクォート(')で囲んだもの。値が1文字の時に使用します。一般的には文字列リテラルを使用することが多く、文字リテラルはあまり使用しません。
ただし、特殊記号を使う時にはこの文字リテラルを使います。

文字列リテラル

値をダブルクォート(")で囲んだもの。値が1文字でも使用できます。

整数リテラル

小数点のない値を指します。
2,8,10,16進数の表現ができます。

浮動小数点リテラル

小数点を持つ値を指します。10進数と指数を表現できます。

論理値リテラル

論理値の値を指します。

真=true
偽=false

nullリテラル

参照型のデータを参照する時に、何も参照してないということを指します。

以下の例では、int型の変数nに数値の6を、String型の変数nameに文字列のmojiを代入しています。
代入した「6」と「moji」が、それぞれ「数値リテラル」「文字列リテラル」
になります。

int n = 6;
String name = moji;

まとめ

リテラルは特別な意味を持つわけではなく、ただ「そこに書かれてあるままの文字」のことをいうと理解しています。

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

SeleniumでMacのEdge(Chromium版)を動かす

はじめに

2020/06 ついにChromium版のEdgeがWindowsアップデートでできるようになりましたね。
最近、E2E自動テストにおいて、IEを含める依頼はめっきり減ってきました。
一日でも早く
「自動テストをやりたい(E2Eね)」 → 「InternetExplorerで 」
という流れが世の中からなくなれば良いと思っています。
代わり増えてきたのが、Edgeです。

ではChromium版のEdgeをMacOSで動かします

開発環境とか

OS

% sw_vers
ProductName:    Mac OS X
ProductVersion: 10.15.5
BuildVersion:   19F101

java

% java -version
openjdk version "11.0.7" 2020-04-14 LTS
OpenJDK Runtime Environment Corretto-11.0.7.10.1 (build 11.0.7+10-LTS)
OpenJDK 64-Bit Server VM Corretto-11.0.7.10.1 (build 11.0.7+10-LTS, mixed mode)

Edge

スクリーンショット 2020-07-08 2.53.58.png

build.gradle

// https://mvnrepository.com/artifact/org.seleniumhq.selenium/selenium-java
compile group: 'org.seleniumhq.selenium', name: 'selenium-java', version: '3.141.59'
// https://mvnrepository.com/artifact/io.github.bonigarcia/webdrivermanager
compile group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '4.0.0'

コード

ローカル環境

public class SeleniumTestEnv {

    private WebDriver driver;

    @BeforeClass
    public static void beforeClass() throws Exception {
        WebDriverManager.edgedriver().setup();
    }

    @Before
    public void setUp() throws Exception {
        driver = new EdgeDriver();
    }

    @After
    public void teardown() {
        if (driver != null) {
            driver.quit();
        }
    }

    @Test
    public void test001() {
        driver.get("https://www.humancrest.co.jp");
        //Test Code
    }

}

よく目にするソースですがなにも考えず、これで動きます。楽勝ですね。
Windowsでも当然動きます

Selenium Grid

public class SeleniumTestEnvRemote {

    private RemoteWebDriver driver;

    @Before
    public void setUp() throws Exception {
//        Capabilities capabilities = DesiredCapabilities.edge();
        MutableCapabilities option = new EdgeOptions();
        driver = new RemoteWebDriver(new URL("http://localhost:4444/wd/hub"), option);
    }

    @After
    public void teardown() {
        if (driver != null) {
            driver.quit();
        }
    }

    @Test
    public void test001() {
        driver.get("https://www.humancrest.co.jp");
        String title = driver.getTitle();
        String browserName = driver.getCapabilities().getBrowserName();
        assertThat(title,is( "ヒューマンクレストグループ"));
        assertThat(browserName,is("msedge"));
    }
}

解説

ソース上で言えば、Chromium版になったからと言って、特別に書き換える必要はありません。
今までEdge用に書いたソースは動きますし、MacOSにインストールしたEdgeでも同様に動きます。

注意点を以下に。。

注意点1

RemoteWebDriverで、Edgeを指定する時、

DesiredCapabilities capabilities = DesiredCapabilities.edge();

と書いた場合、Windows用になります。
MacOSに立てたSelenium Grid/hubのLogには、

Error forwarding the new session cannot find : Capabilities {browserName: MicrosoftEdge, platform: WINDOWS, version: }

というエラーを吐きます
どうやら無条件で、platform: WINDOWSと設定されるようです。
ですので、Mac側のEdgeでテストする場合は、

DesiredCapabilities capabilities = DesiredCapabilities.edge();
capabilities.setPlatform(Platform.MAC);

と書き足しましょう。

注意点2

driver.getCapabilities().getBrowserName();

の戻り値は msedge です。

DesiredCapabilities.edge().getBrowserName()

の戻り値は、MicrosoftEdgeです。

そう、違う!のです
それ必要??という情報かもしれませんが、、
いままでブラウザ毎の特殊処理を入れてる箇所の判定で、

if (ブラウザ名) then

などど書いてませんか??
私の場合、少なからずそういう過去の遺産があり、どうしても動かない理由が、、これでした。

補足

当たり前の事なのですが、Selenium Grid のノードを立ち上げる際、必ずwebdriverの指定が必要です。つまりこんな感じ

forMacOS
java -jar -Dwebdriver.edge.driver="msedgedriver" selenium-server-standalone-3.141.59.jar -role node -nodeConfig node_config.json

私の場合、横着してselenium-server-standalone-xxx.jarと同じフォルダ内に各ブラウザのwebdriverを配置。
そうすればwebdriver指定しなくても問題なくNodeは役割を果たしてくれました。
しかし、Chromium版Edgeは、webdriverの指定は必須です。
(おそらく、MicrosoftWebDriver.exe をデフォルトで参照しに行くような動きをしています。ブラウザだけ起動して、URLが入らず止まってしまう)

最後に

これで、MacOSにインストールしたEdgeもテスト可能になるはずです。
(大したこと書いてませんがww)

Windowsの話になりますが、
Microsoft Edge Legacyは、IEまでとはいいませんが、やはり少し癖があり、100%同じソースで動かすことはできませんでした。
しかし、Chromium版は、Chromeとほぼ同じソースで動いています。

E2Eのテスト自動化エンジニアにとって、Chrome・Firefox・EdgeがOS問わず動かせる環境ができるということは素晴らしいですね。

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

[Processing×Java] クラスの使いかた

この記事はプログラムの構造をProcessingを通じて理解していくための記事です。
今回はクラスについて書いていきます。

目次
0.クラスとは
1.あらかじめ用意されているクラス
2.自分で定義してつくるクラス

0.クラスとは

0-0. クラスとは

関連するフィールド(変数)とメソッド(関数)が入ったテンプレート(型)のことです。
よくレシピや設計図などに例えられます。

0-1. なぜクラスを使うのか

主な理由は、
プログラムが読みやすくなるから。
再利用できるようになるから。
(再利用については後ほど詳しく)

0-2. クラスの種類

クラスには、"あらかじめ用意されているクラス"と、"自分で定義してつくるクラス"があります。

あらかじめ用意されているクラスは、具体的には、String,PImage,PFont,PShape,PVector...などです。

1.あらかじめ用意されているクラス

Processingでは、たくさんのクラスがあらかじめ用意されています
String,PImage,PFont,PShape,PVector...など。

1-0. あらかじめ用意されているクラスの使い方

クラスはただのテンプレート(型)なので、そのままでは使うことができません。
なので、

①クラスを具体化してオブジェクトをつくる
②オブジェクトに中身を入れる
③オブジェクトを変数として扱う

という手順で、クラス(のなかの情報)を使っていきます。

簡単な例で見てみましょう。

string.java
//オブジェクトの宣言
String s1;
//オブジェクトの定義
s1 = "breakfast"
//オブジェクトの使用
println(s1.length());
9

このプログラムは、文字の長さを取得して表示するプログラムです。
手順ごとに詳しくみてみます。

①クラスを具体化してオブジェクトをつくる

//オブジェクトの宣言
String s1;

②オブジェクトに中身を入れる。

//オブジェクトの定義
s1 = "breakfast"

③オブジェクトを変数として扱う

//オブジェクトの使用
println(s1.length());

全てのオブジェクトはクラスが持つフィールドとメソッドを持っています。

オブジェクトの持つフィールドやメソッドにアクセスするためにはドット(.)を使ってアクセスします。
オブジェクト自体は、変数と同じように扱います。

変数と同じように使うとは
定義(宣言→初期設定)→使用
のようにして使うということです。

2.自分で定義してつくるクラス

プログラミングでは自分のオリジナルのクラスを作ってプログラムをシンプルにすることができます。
最初は、以下の跳ねるボールを表示するプログラムを簡略化していきます。

bouncingball.java
float x = 30;
float y = 30;
float xspeed = 5;
float yspeed = 3;

void setup(){
  size(398,198);
}

void draw(){
  background(255);

  noStroke();
  fill(234,159,217);
  ellipse(x,y,50,50);

  x += xspeed;
  y += yspeed;

  if(x > width-20 || x < 20){
    xspeed *= -1;
  }

  if(y > height-20 || y < 20){
    yspeed *= -1;
  }
}

ezgif.com-gif-maker (5).gif

プログラムを簡略化するために、
モジュール化する
再利用可能にする
ということをしていきます。

①モジュール化する

bouncingball_module.java
float x = 30;
float y = 30;
float xspeed = 5;
float yspeed = 3;

void setup(){
  size(398,198);
}

void draw(){
  background(255);

  display();
  move();
  edges();
}


//ボールを表示する関数
void display(){
  fill(234,159,217);
  noStroke();
  ellipse(x,y,30,30);
}

//ボールを動かす関数
void move(){
  x += xspeed;
  y += yspeed;
}

//ボールを跳ね返らせる関数
void edges(){
  if(x > width-15 || x < 15){
    xspeed *= -1;
  }
  if(y > height-15 || y < 15){
    yspeed *= -1;
  }
}

Point :
まとめて1つの関数とすることで、draw()関数の中がすっきりしてプログラムがわかりやすいものになります。

②再利用可能にする

再利用が可能とは、複数の出力が可能ということです。

bouncingball_reuse.java
//オブジェクトの宣言
Ball b1;
Ball b2;

void setup(){
  size(600,400);
  //オブジェクトを定義する
  b1 = new Ball(50,50,7,5);
  b2 = new Ball(400,50,-7,-5);
}

void draw(){
  background(255);
  //オブジェクトb1をつかう
  b1.display();
  b1.move();
  b1.edges();

  //オブジェクトb2をつかう
  b2.display();
  b2.move();
  b2.edges();
}

//クラスを定義する
class Ball{
  //使う変数(フィールド)を宣言する
  int x;
  int y;
  float xspeed;
  float yspeed;

  //Ballクラスのコンストラクタ
  Ball(int xpos,int ypos,float xvelocity,float yvelocity){
    x = xpos;
    y = ypos;
    xspeed = xvelocity;
    yspeed = yvelocity;
  }

  //ボールを表示する関数(メソッド)
  void display(){
    fill(234,159,217);
    noStroke();
    ellipse(x,y,30,30);
  }

  //ボールを動かす関数(メソッド)
  void move(){
    x += xspeed;
    y += yspeed;
  }

  //ボールを跳ね返らせる関数(メソッド)
  void edges(){
    if(x > width-15 || x < 15){
      xspeed *= -1;
    }
    if(y > height-15 || y < 15){
      yspeed *= -1;
    }
  }
}

再利用可能にする手順

①クラスの枠組みをつくる

class Ball{

}

②モジュール化した関数を入れる

(構造がわかりやすいように中身は抜いています。)

class Ball{

  //ボールを表示する関数(メソッド)
  void display(){
  }
  //ボールを動かす関数(メソッド)
  void move(){
  }
  //ボールを跳ね返らせる関数(メソッド)
  void edges(){
  }
}

③関数(メソッド)に使う変数(フィールド)を準備する。

//使う変数(フィールド)を宣言する
int x;
int y;
float xspeed;
float yspeed;

//クラスを定義する
class Ball{

  //ボールを表示する関数(メソッド)
  void display(){
  }
  //ボールを動かす関数(メソッド)
  void move(){
  }
  //ボールを跳ね返らせる関数(メソッド)
  void edges(){
  }
}

④オブジェクトを宣言する。

//使うオブジェクトb1を宣言する。
Ball b1;
//使うオブジェクトb2を宣言する。
Ball b2;

void setup(){
}
void draw(){
}

class Ball{
}

⑤オブジェクトに中身を入れる

オブジェクトの宣言
Ball b1;
Ball b2;

void setup(){
  //オブジェクトb1にBallクラスのインスタンスを代入する。
  //インスタンスはクラスを具体化したもの。
  //オブジェクトb1は変数として扱われる。
  b1 = new Ball();
  b2 = new Ball();
}
void draw(){
}

class Ball{
}

Point :b1⬅︎new Ball()
BallクラスのインスタンスBall()をオブジェクトb1に代入しています。

⑥オブジェクトを使う。

※全てのオブジェクトは、クラスの持つフィールド(変数)とメソッド(関数)を持っています。
自身の持つフィールドやメソッドにアクセスする場合は、ドット(.)を使います。

Ball b1;
Ball b2;

void setup(){
  b1 = new Ball();
  b2 = new Ball();
}
void draw(){

  //ドット(.)を使って、オブジェクトの中のメソッドにアクセスする。
  b1.display();
  b1.move();
  b1.edges();

  //ドット(.)を使って、オブジェクトの中のメソッドにアクセスする。
  b2.display();
  b2.move();
  b2.edges();
}

class Ball{
}

⑦クラスのコンストラクタをつくる

コンストラクタは、クラスから複数のオブジェクトを生み出すために必要なものです。
オブジェクトが、クラスの中の関数にアクセスするためのものです。

//オブジェクトの宣言
//まだ中身は何も入っていない
Ball b1;
Ball b2;

void setup(){
  //オブジェクトの生成(定義)
  //クラスを具体化したインスタンスを、つくったオブジェクトに代入する
  b1 = new Ball(50,50,7,5);
  b2 = new Ball(400,50,-7,-5);
}
void draw(){

  //ドット(.)を使って、オブジェクトの中のメソッドにアクセスする。
  b1.display();
  b1.move();
  b1.edges();

  //ドット(.)を使って、オブジェクトの中のメソッドにアクセスする。
  b2.display();
  b2.move();
  b2.edges();
}

class Ball{
  //使う変数(フィールド)を宣言する
  int x;
  int y;
  float xspeed;
  float yspeed;

  //Ballクラスのコンストラクタ
  //データ型 変数 という形
  Ball(int xpos,int ypos,float xvelocity,float yvelocity){
    x = xpos;
    y = ypos;
    xspeed = xvelocity;
    yspeed = yvelocity;
  }

  void display(){}
  void move(){}
  void edges(){}
}

Point :コンストラクタ
コンストラクタは、インスタンスとフィールドをつなげるものです。

(今回の場合)
7 = float xvelocity = float xspeed
プログラムが実行されたとき、xspeedには7が代入されます。

Point :インスタンス
オブジェクトが具体化したもの。
今回でいうと、Ball(50,50,7,5)がクラスのインスタンスの1つ。
Ball(400,50,-7,-5)もクラスのインスタンスの1つ。
同じクラスからインスタンスは複数つくることができる。

最後に

読んでいただきありがとうございました。
より良い記事にしていくために御意見、ご指摘のほどよろしくお願いいたします。

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