- 投稿日:2020-08-07T23:31:58+09:00
UbuntuでJava8とJava11のSDKを使えるようにする
OpenJDKの8と11をインストールする
$ sudo apt install -y openjdk-8-jdk openjdk-11-jdk環境変数JAVA_HOMEを設定する
$ vi .bashrc
JAVA_HOME=$(readlink -f /usr/bin/javac | sed "s:/bin/javac::") export JAVA_HOME PATH=$PATH:$JAVA_HOME/bin export PATHbashrc更新
$ source ~/.bashrc環境変数JAVA_HOMEを確認
$ echo $JAVA_HOMEjavaコマンドのパージョンを切り替える
sudo update-alternatives --config javajavacコマンドのバージョンとを切り替える
sudo update-alternatives --config javac
- 投稿日:2020-08-07T17:50:55+09:00
Spring Boot で Mustache テンプレートエンジンを使うサンプルコード
概要
- Spring Boot で Mustache テンプレートエンジンを使う
- 今回の動作確認環境: Java 14 (AdoptOpenJDK 14.0.2+12) + Spring Boot 2.3.2 + Gradle 6.5.1 + macOS Catalina
サンプルコード
ソースコード一覧
├── build.gradle └── src └── main ├── java │ └── com │ └── example │ ├── SampleController.java │ └── SampleData.java └── resources ├── application.properties └── templates ├── error │ ├── 4xx.html │ └── 5xx.html └── my_template.htmlbuild.gradle
Spring Boot Starter Mustache を導入する。
plugins { id 'org.springframework.boot' version '2.3.2.RELEASE' id 'io.spring.dependency-management' version '1.0.9.RELEASE' id 'java' } group = 'com.example' version = '0.0.1' sourceCompatibility = '14' repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter-mustache' implementation 'org.springframework.boot:spring-boot-starter-web' }spring-boot-starter-mustache-2.3.2.RELEASE.pom の中身を見ると Spring Boot Starter Mustache が Mustache ライブラリの JMustache 1.15 を利用していることがわかる。
src/main/resources/application.properties
Mustache に関する設定を行う。
ここでは拡張子が html のファイルをテンプレートファイルとして扱うように spring.mustache.suffix を設定する。# テンプレートのキャッシュを有効にするかどうか (デフォルト値: false) spring.mustache.cache=false # テンプレートのエンコード (デフォルト値: UTF-8) spring.mustache.charset=UTF-8 # テンプレート名に適用するプレフィックス (デフォルト値: classpath:/templates/) spring.mustache.prefix=classpath:/templates/ # テンプレート名に適用するサフィックス (デフォルト値: .mustache) spring.mustache.suffix=.html他にも設定項目があるので必要に応じて Spring Boot アプリケーションプロパティ一覧 - ドキュメント などを参照すると良い。
src/main/java/com/example/SampleController.java
コントローラークラス。
package com.example; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.servlet.ModelAndView; @SpringBootApplication @Controller public class SampleController { public static void main(String[] args) { SpringApplication.run(SampleController.class, args); } @GetMapping("/mypage") public ModelAndView mypage() { SampleData sampleData = new SampleData(); ModelAndView mav = new ModelAndView(); mav.setViewName("my_template"); // テンプレートファイル名 mav.addObject("mydata", sampleData); // データオブジェクトをセット return mav; } @GetMapping("/myerror") public ModelAndView myerror() { throw new RuntimeException("エラーを発生させる"); } }src/main/java/com/example/SampleData.java
Mustache テンプレートに埋め込むデータオブジェクト。
package com.example; import java.util.List; import java.util.Map; public class SampleData { public String foo = "foo"; public String getBar() { return "bar"; } public String[] strings = {"S1", "S2", "S3"}; public List list = List.of("L1", "L2", "L3"); public Map map = Map.of("key1", "value1", "key2", "value2", "key3", "value3"); }src/main/resources/templates/my_template.html
データオブジェクトの内容を表示するための Mustache テンプレートファイル。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> {{#mydata}} foo: {{foo}}<br> getBar(): {{bar}}<br> {{/mydata}} <br> strings:<br> {{#mydata.strings}} value: {{.}}<br> {{/mydata.strings}} <br> list:<br> {{#mydata.list}} value: {{.}}<br> {{/mydata.list}} <br> map:<br> {{#mydata.map}} {{key1}}, {{key2}}, {{key3}} {{/mydata.map}} </body> </html>src/main/resources/templates/error/4xx.html
4xx 系エラー発生時のための Mustache テンプレートファイル。
必要に応じてエラー情報等のデータを埋め込むことができる。<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>4xx</title> </head> <body> <h1>4xx</h1> <div>timestamp: {{#timestamp}}{{.}}{{/timestamp}}</div> <div>status: {{#status}}{{.}}{{/status}}</div> <div>error: {{#error}}{{.}}{{/error}}</div> <div>exception: {{#exception}}{{.}}{{/exception}}</div> <div>message: {{#message}}{{.}}{{/message}}</div> <div>errors: {{#errors}}{{.}}{{/errors}}</div> <div>trace: {{#trace}}{{.}}{{/trace}}</div> <div>path: {{#path}}{{.}}{{/path}}</div> </body> </html>src/main/resources/templates/error/5xx.html
5xx 系エラー発生時のための Mustache テンプレートファイル。
必要に応じてエラー情報等のデータを埋め込むことができる。<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>5xx</title> </head> <body> <h1>5xx</h1> <div>timestamp: {{#timestamp}}{{.}}{{/timestamp}}</div> <div>status: {{#status}}{{.}}{{/status}}</div> <div>error: {{#error}}{{.}}{{/error}}</div> <div>exception: {{#exception}}{{.}}{{/exception}}</div> <div>message: {{#message}}{{.}}{{/message}}</div> <div>errors: {{#errors}}{{.}}{{/errors}}</div> <div>trace: {{#trace}}{{.}}{{/trace}}</div> <div>path: {{#path}}{{.}}{{/path}}</div> </body> </html>出力結果例
curl でアクセスしてレスポンス結果を確認する。
正常レスポンスのとき
データオブジェクトが Mustache テンプレートに埋め込まれて出力されている。
$ curl http://localhost:8080/mypage <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> foo: foo<br> getBar(): bar<br> <br> strings:<br> value: S1<br> value: S2<br> value: S3<br> <br> list:<br> value: L1<br> value: L2<br> value: L3<br> <br> map:<br> value1, value2, value3 </body> </html>404 Not Found のとき
4xx 系エラー発生時のための Mustache テンプレートファイルにエラー情報が埋め込まれて出力されている。
$ curl --include -H "accept: text/html" http://localhost:8080/ HTTP/1.1 404 Vary: Origin Vary: Access-Control-Request-Method Vary: Access-Control-Request-Headers Content-Type: text/html;charset=UTF-8 Content-Language: ja-JP Content-Length: 321 Date: Fri, 07 Aug 2020 07:55:53 GMT <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>4xx</title> </head> <body> <h1>4xx</h1> <div>timestamp: Fri Aug 07 16:55:53 JST 2020</div> <div>status: 404</div> <div>error: Not Found</div> <div>exception: </div> <div>message: </div> <div>errors: </div> <div>trace: </div> <div>path: /</div> </body> </html>エラー発生時
5xx 系エラー発生時のための Mustache テンプレートファイルにエラー情報が埋め込まれて出力されている。
$ curl --include -H "accept: text/html" http://localhost:8080/myerror HTTP/1.1 500 Content-Type: text/html;charset=UTF-8 Content-Language: ja-JP Content-Length: 340 Date: Fri, 07 Aug 2020 07:55:47 GMT Connection: close <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>5xx</title> </head> <body> <h1>5xx</h1> <div>timestamp: Fri Aug 07 16:55:47 JST 2020</div> <div>status: 500</div> <div>error: Internal Server Error</div> <div>exception: </div> <div>message: </div> <div>errors: </div> <div>trace: </div> <div>path: /myerror</div> </body> </html>参考資料
Spring Boot 「使い方」ガイド - 公式ドキュメントの日本語訳
Mustache を使用する場合、「mustacheViewResolver」という名前の MustacheViewResolver もあります。ビュー名をプレフィックスとサフィックスで囲むことにより、リソースを探します。プレフィックスは spring.mustache.prefix で、サフィックスは spring.mustache.suffix です。接頭辞と接尾辞の値は、デフォルトでそれぞれ「classpath:/templates/」と「.mustache」になります。同じ名前の Bean を提供することにより、MustacheViewResolver をオーバーライドできます。
- Spring Boot の機能 - 公式ドキュメントの日本語訳
- Spring Boot アプリケーションプロパティ一覧 - ドキュメント
- spring-boot/MustacheView.java at v2.3.2.RELEASE · spring-projects/spring-boot · GitHub
- spring-boot/MustacheViewResolver.java at v2.3.2.RELEASE · spring-projects/spring-boot · GitHub
- spring-boot/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mustache at v2.3.2.RELEASE · spring-projects/spring-boot · GitHub
- 7.2.6. エラー処理 - Spring Boot の機能 - 公式ドキュメントの日本語訳
- DefaultErrorAttributes (Spring Boot 2.3.2.RELEASE API)
- Guide to Mustache with Spring Boot | Baeldung
- 投稿日:2020-08-07T16:42:36+09:00
ボタン連打防止用コード
自分用メモ
・ボタン連打防止用コード
※参考にする場合は、ご自身で検証してください。(検証不十分なため)qiita.javapublic class TestActivity { // !!!自分用メモでテスト不十分なことをご承知ください!!! // バックキー連打防止用フラグ private boolean backKeyEnabled = true; @Override public boolean dispatchKeyEvent(KeyEvent event) { // 連打しようとしたらボタン無効 if(!backKeyEnabled) { return false; } // ボタンを押したら backKeyEnabled = false; // ボタンをしばらく押せないようにする new Handler().postDelayed(new Runnable() { @Override public void run() { backKeyEnabled = true; // 1sec後に押せるようにする } }, 1000); // 以後処理 } }
- 投稿日:2020-08-07T16:22:06+09:00
等価と等値の使い方(equalsの使い方)
等価と等値の違い
先日受けたJavaSilverの試験結果にて不正解と思われる箇所として
・「==とequals()を使用して文字列と他のオブジェクトが等しいかどうかをテストする」
が挙げられていたため、復習として記そうと思う。参考スッキリわかるJava入門 (2020/8/7時点)
)public class Human { String name; int hp; public String toString(){ return "name:" + this.name + "/age:" + this.age; } }上記のとき、
等値
(==)の判定
「完全に同一の存在」=「同じアドレス値を指している」same.javaHuman h1 = new Human("Taro") Human h2 = h1;h2にh1を代入しているため、同じインスタンスA「name:Taro」をもち
参照先も同じ2112番地のアドレスを参照している。
このとき「h1 == h2」が成り立つ等価
equals.javaHuman h1 = new Human("Ziro"); Human h2 = new Human("Ziro");それぞれインスタンスを生成→参照している
インスタンスA「name:Ziro」 → 3322番地
インスタンスB「name:Ziro」 → 9191番地
「h1 != h2」が成り立つ
然し、文字列の内容は同じ
そのため「h1.equalsa(h2)」が成り立つうん、理解している。
が、
スッキリわかるJava入門を進めていて後半のAPI活用の章に入ると
equals()は「何をもって同じとするか」という評価基準を指定しないと正しく動かない、と記載があるのだがちょっと混乱する。以下は正しく動作しない
例1)二人の人物を比べるpublic class Main { public static void main(String[] args) { Human h1 = new Human(); h1.name = "Taro"; h1.age = 10; Human h2 = new Human(); h2.name = "Taro"; h2.age = 10; if ( h1.equals(h2) == true ) { System.out.println("Same"); } else { System.out.println("different") ; } } }Objectクラスから継承されるequals()の処理内容は以下の様に
「とりあえず等値ならtrueを返しとく」
という形になるpublic boolean equals(Object o) { if (this == o) { return true; } else { return false; } }「何を持って同じ内容とみなすか」はそれぞれのクラスにより異なるので、
それぞれで定義しなければならない
つまり「equalsをオーバーライド」して指定する条件が「名前が同じなら、同じHumanとみなす」
なら下記のようになるpublic class Human { String name; int age; public boolean equals(Object o ) { if (this == o) { return true; } if (o instanceof Human) { // 以下追加オーバーライド ①oをHuman型にキャストできるか判定 Human h = (Human)o; // ②Object型o をダウンキャストしてHuman型にし、hに代入 if ( this.name.equals(h.name)) { return true; } } return false; } }「SAME」と表示される
- 投稿日:2020-08-07T15:18:20+09:00
JVM(Java Virtual Machine)とは?
JVM(Java Virtual Machine)とは
- JVMとはJava Virtual Machineの略で、Javaのプログラムを動かすために必要なソフトウェアです。
- Java bite codeを実行できる主体です。
CPUやOSの種類と関係なく実行できます。
つまり、OS上で動くプロセスで、Javaコードをコンパイルして得られたバイトコードを該当OS(Windows、OS X、Linuxなど)が理解(解析)できる機械語に変換し、実行してくれます。様々なOS用のJVM
JVMの構成
大きく見てみると以下の4つです。
- Class Loader
- Execution Engine
- Garbage Collector
- Runtime Data Area
JVMの構成
Class Loader
Javaではソースコードを作成するとTest.javaのように.javaファイルが生成されます。
javaソースをJavaコンパイラがコンパイルを行うとTest.classのように.classファイル(バイトコード)が生成されます。
このように生成されたClassファイルを組んでJVMがOSから与えられたメモリ領域であるRuntime Data Areaに積載を行う役割をClass Loaderがします。Execution Engine
- Execution EngineはClass Loaderによって積載されたクラス(バイトコード)を機械語に変換し命令語の単位で実行させます。
- 命令語を一つ一つ実行するインタープリター式とJIT(Just-In-Time)コンパイラを利用する方法があります。
※JITコンパイラは適切な時間に全体のバイトコードをネイティブコードに変更してExecution Engineがネイティブにコンパイルされたコードを実行することで性能を高める方法です。
Garbage Collector
- Garbage Collector(GC)はHeapメモリ領域に生成(積載)されたオブジェクトの中で参照されていないオブジェクトの探索を行い、削除します。
- GCが行われる時間は正確にいつなのかわかりません。 (参照がなくなってすぐ解除するとは保証できないため)
- GCが行われる間はGCを行うスレッド以外のスレッドは止まります。
※Full GCが行われる数秒間はすべてのスレッドが停止したら障害にもつながる致命的な問題が生じます。
Runtime Data Area
- JVMのメモリ領域でJavaアプリケーションを実行する際、使われるデータの積載を行う領域です。
- Method Area, Heap Area, Stack Area, PC Register, Native Method Stackに分けられています。(代表的に他にもあります。)
Runtime Data Areaの構造
Method area
- メソッド領域とは、プログラムで利用されるクラス毎にクラスの情報を管理される領域です。
- クラスメンバーの変数名、データタイプ、アクセス制御子の情報のようなフィールド情報とメソッド名、戻り値の型、パラメータ、Type情報(Interfaceかclassか)、Constant Pool、static変数、final class変数などが管理されます。
Heap area
ヒープ領域は、インスタンスとインスタンスのメンバーフィールドを管理する領域です。
- newキーワードで生成されたオブジェクトと配列を管理する領域です。
メソッド領域にロードされているクラスのみ、生成が可能でGarbage Collectorが参照されていないメモリをスキャンし解除を行う領域です。
Stack area
- スレッド(Thread)毎の領域です。
- ローカル変数、パラメータ、戻り値、演算に使われる任意の値などを管理する領域です。
- スタック領域は共有リソースではないため、スレッドセーフです。
- スタックフレームは3つのサブエンティティに分割されています。
- ローカル変数配列(Local Variable Array) メソッドに関連するローカル変数の数と、対応する値が格納されます。
- オペランドスタック(Operand stack) 実行するために中間のオペレーションが必要な場合、オペランドスタックは オペレーションを実行するためにランタイムワークスペース(作業領域)として機能します。
- フレームデータ(FrameData) メソッドに対応するすべてのシンボルがここに格納されます。 例外の場合、キャッチブロック情報はフレームデータ内で維持される。
PC Register
- Thread(スレッド)が生成されるたびに生成される領域でProgram Counter、つまりスレッド内で実行されている現在のステートメントへのポインタを保持します。 (*CPUのレジスタとは違う)
- 現在実行中のメソッドが 'native'の場合、プログラムカウンタレジスタの値は不定になります。
Native method stack
- Java以外のプログラミング言語で作成されたネイティブコードのためのメモリ領域です。
- 普通はC/C++などのコードを使用するためのスタックです。(JNI)
※JNI:JNIはネイティブメソッドライブラリと対話し、実行エンジンに必要なネイティブライブラリを提供します。
スレッドが作成された時
メソッド領域とヒープ領域をすべてのスレッドが共有し、
スタック領域とPCレジスタ、ネイティブメソッドスタックはそれぞれスレッドごとに作成され、共有はされないです。Heap area & Garbage Collector
この項目ではHeap areaはGCの重要対象なのでもうちょっと詳しく見ていきましょう。
(Stack領域とMethod領域もGCの対象になります。)Heap areaは5つの領域(eden, survivor1, survivor2, old, permanent)となっています。
JDK7まではpermanent領域がheapに存在していました。JDK8からはpermanent領域はなくなり、その一部が"meta space領域"に変更されました。(上の図はJDK7基準です。) meta space領域はNative stack領域に含まれるようになりました。
(survivor領域の数字は意味がなく2つに分けられていることが重要です。)
Heap領域をわざわざ5つに分けた理由は効率的にGCを行わせるためです。
詳しいことはGCが行われるプロセスを見ながら説明していきたいと思います。GCはMinor GCとMajor GCに分けられている
※Minor GC : New領域で行われるGC
- 最初にオブジェクトが生成されたらEden領域に生成されます。
- Eden領域にオブジェクトがいっぱいになると最初のGCが行われます。
- survivor1領域にEden領域のメモリがそのままコピーされます。そしてsurvivor1領域を除く他の領域のオブジェクトを削除します。
- Eden領域もsurvivor1領域もいっぱいになったらEden領域に生成されたオブジェクトとsurvivor1領域に生成されたオブジェクトの中で参照されているオブジェクトがあるかを検索します。
- 参照されていないオブジェクトはそのまま置いて、参照されているオブジェクトのみsurvivor2領域にコピーしていきます。
- survivor2領域を除く他の領域のオブジェクトを削除していきます。
- 上記の流れで一定の回数以上に参照されているオブジェクトはsurvivor2からOld領域に移動させます。
※上記の流れを繰り返し、survivor2領域までいっぱいになる前に続けてOldに移動させます。
※Major GC(Full GC) : Old領域で行われるGC
- Old領域にあるすべてのオブジェクトを検査し、参照されているかを確認します。
- 参照されていないオブジェクトは収集して一気に削除を行います。
※Minor GCより所要時間が長いし、GCが行われている間はGC以外の全てのスレッドは停止されます。
Major GC(Full GC)が行われたら??
Old領域の参照されていないオブジェクトをチェックし、該当オブジェクトは全部削除されます。
そうなるとHeapメモリ領域に削除されて空きメモリ空間ができますが、この空きメモリをなくすために再構成を行います。(メモリ整理)
なので、メモリを再構成している際に他のスレッドがメモリを使えないようにするため、すべてのスレッドが停止されることです。参考
- 投稿日:2020-08-07T14:57:15+09:00
java_アノテーション_メモ_20200807
アノテーション
1.アノテーションとは
アノテーションとは「この部分は警告を出さなくてもよい」という指示をソースコード内に記述できることです。
アノテーションは"@"から始まる記述でコメント内ではなくソースコードにそのまま記述します。
以下で標準で準備されている3つのアノテーションを例にどのようなものか実際に確認しましょう。>違う記事で記述済みのためこれくらいの説明にとどめさせていただきます。
2.アノテーションを利用したコード
3.@SuppressWarning - 警告を抑制する
クラス、メソッド、フィールドなどに対して、一定の種類の警告をしないように指示するのが
@SuppressWarningアノテーションです。このアノテーションは、パラメータとして「抑制、制御したい警告の種類」を指定します。
たとえば、「2.アノテーションを利用したコード」にあった、
@SuppressWarning("Serial")は、serialVersionUIDを宣言していないことに対する警告を出さないようにします。
そのほかこのアノテーションには以下の表1のようなパラメータがあります。表1 @SuppressWarning で指定可能な代表的なパラメータ
4.@Override - オーバーライド宣言
メソッド宣言の先頭に、@Overrideアノテーションをつけると、そのメソッドが親クラスの同名のメソッドを
オーバーライドすることを明示的に宣言することができます。なんでこんなものが必要なのか?
>「オーバーライドしたつもりで、実はできていない」場合、それに気づくためたとえば、親クラスで宣言されたメソッド transfer() をオーバーライドするつもりで、
子クラスで誤ったスペルのメソッド transfer() を宣言してしまったとしましょう。
これではオーバーライドになっていないため、何度 transfer() を呼び出しても親クラスの transfer()が
動作してしまい、子クラスの transfer() は動作しません。
そのため「メソッドは呼びだせて一応動くが、動作内容がおかしい」という原因の特定が難しい不具合が
発生してしまいます。
そこで、オーバーライドを行うことを前提としているメソッド宣言の先頭には@Overrideアノテーションを付ける
ようにすることでコンパイラが警告を出してくれます5.@Deprecated - 非推奨の宣言
クラス、フィールド、メソッドなどの宣言の先頭に@Deprecatedアノテーションを付けると、
それが非推奨であることをコンパイラに伝えることができます。
たとえば、あるクラスに手を加えてより良いクラスを作成した結果、古いほうのクラスを使ってほしくない
時などに記述します。長々と記載が続きましたが、未来の自分用のメモですのでご一読いただいた中で間違っているところもあると思います。
間違っているところなどございましたらご指摘願います。
- 投稿日:2020-08-07T13:47:57+09:00
Java Silver に落ちた(泣)
先日Java Silver の試験を受けた。
結果は 「64%」...
(65%以上なら合格)
ふぁ!!? ショック!! 1%差とかマジカヨ...
あと1問正解していたら合格w
受験代も馬鹿にならないし、死にたくなりましたwそこまで自信ないけど65〜70%でなんとかいけるかなと思ってました。
因みに当方
・30代前半
・Javaの実務経験なし
・勉強期間3週間強
・異業種からSESに転職したばかり
・html、css、JS、PHPはちょっといじれる程度
という条件転職直後にコロナ禍発生、
例年なら入社後2〜3週間で案件に入れていたそうだが
しばらく自宅待機の後、休業中
という状況勉強できる時間は豊富にあるので尚更めっちゃ悔しい
この世界向いてないんじゃないか...と思ったが
意地でも取りたい資格取得自体に価値は無いと仰る方もおられるだろうが、
自分としては、大学受験という壁を乗り越えてきた経験もなく
「継続的に勉強する」という行為そのものとほぼ無縁に生きてきたので
せめて資格取得する過程で得られる
・業務以前の基本的な知見
・継続学習する習慣
を補おうという魂胆試験の内容的には丁度苦手な所がいやらしい感じで出ていたな、という印象
評判通り、後述する黒本の内容がほぼそのまま出題されている問題も多かったため、隅々まで解きつつ
それらの正解・不正解の理由を頭に入れておけば普通に受かると思われるでは何故今回落ちたのか?仮設をたてて分析せねばならんだろう
原因:
対策
1.ケアレスミス:
問題をよく読むこと。黒本の模擬試験でも毎回1〜2問のケアレスミスがあった。そういう問題に限って内容は単純で、間違える確率自体は低かったりする。2.苦手箇所の復習が甘い:
構造をざっくり把握して通用するのは模擬試験であって問題に慣れているから正解しやすいだけ。
反復して理解度を上げる。例)多次元配列でfor文ネストして取り出すような問題
arrayRoup.javapublic class Main { public static void main(String[] args) { int[] array = new int[][] { {1, 2}, {2, 3, 4} }; int[] total = 0; for(int i = 0; i < array.length; i++;) { for ( ? ) { // insert code total += array[i][j]; } } System.out.println(total); } } // コンソールに「10」と表示するには?にどんなコードを入れるか3.そもそもの勉強の仕方が間違っている:
使用した書籍
・紫本
教科書的存在、各機能と試験範囲が簡潔に記されている。
JavaプログラマSilver SE 8 試験番号:1Z0-808 (オラクル認定資格教科書) [ 山本道子(プログラミング) ]
価格:4180円(税込、送料無料) (2020/8/7時点)
・黒本
言わずとしれた最強なやつ。
これをやれと他の方もn億回言っている。
特に正答率の低い所を繰り返しやって構造の理解を深めるべき。
自分くらいの入門者には解説文の言い回しがやや難解に感じた。
徹底攻略Java SE 8 Silver「1Z0-808」対応問題集 試験番号1Z0-808 [ 志賀澄人 ]
価格:3520円(税込、送料無料) (2020/8/7時点)
・スッキリわかるJava 入門
とにかく分かりやすい。他の言語含め色々読んだがこのシリーズは入門者には良さげ
超重要な
・継承
・ポリモーフィズム
・カプセル化
とそもそもの「オブジェクト指向」の入り口に立てます。
自分のような雑魚にはこの「オブジェクト指向」というのが感動しましたね
因みにラムダ式はこちらではなく実践編に登場する
スッキリわかるJava入門第3版 [ 中山清喬 ]
価格:2860円(税込、送料無料) (2020/8/7時点)
勉強はそれなりにやったつもりだ。
だがアウトプットが足らなかった。
というか座学で紙とペンを使いすぎた自分がその日勉強した(更新した)情報があるのであれば発信すべきなので、
仮想対象として自分のような入門者を想定して、解説してあげるようなテキストを書く
だけでも違ってくるはずだ
インプット→アウトプット→昇華
これを繰り返すことで知識が「結晶化」していく
はず自己啓発系の本とかで腐るほど言われてきたことを実践していなかっただけ
大体2〜3週間サイクルで勉強する内容変えていて
SQL 不合格 (難易度低めたっだらしいBronz 基礎は廃止だったので12c基礎を受験)
↓
Java 不合格
↓
Lpic
↓
CCNAという感じ
スペック低すぎww参考になる記事にはならなそうだが
備忘録、成長記録として活用しようJava silverは来週中に受け直してご合格したい
- 投稿日:2020-08-07T13:32:49+09:00
java簡素なブロック崩し作ってみた
はじめに
きょうで始めてQiitaに投稿することになりました。正直に何をすれば以下わからず、困惑していますが、とりあえずプログラミング言語のスキルアップの向上のためにもできる限り毎日投稿していきたいと思います。よろしくお願いします
なぜQiitaに投稿することになったのか
正直に言えば就活でアピールポイントとして加えたいからです。今のままだと何もできないままエンジニア目指したいですと言われても落ちるのが分かるのでせめて、Qiitaに投稿して、自分の実力がどれくらいなのかを公開したく思いました。これから自分の興味のある分野だけ進捗が生まれるように投稿をし続けたいです
なぜブロック崩しを作ることになったのか
学校の課題で自由に作りなさいとの事で過去作ったゲームをここに投稿したいと思いました。あとは自分はブロック崩しやテトリスやぷよぷよが大好きなのでレトロゲームが作りたく思いました。
ブロック崩しのルール
ブロック崩しはやったことがある人ならわかりますが、あるボールをラケットやバーで打ち返し画面の下に落ちないように工夫する必要がある。打ち返した際にボールをブロックにあてて、全部のブロックを消すか、ブロックを消した際に得点などを加えたりする。非常にシンプルなゲームですが作ってみるととても大変でした。
画像の通りブロック崩しの例です。一つだけではつまらないので、ボールの大きさ、速さ、ボールの数も変えました。この時点で逸脱していますが、とりあえずそんな感じで。ブロックもボールの当たる回数で消えるようにしています。画像の通りならば黄色ならば3回、青ならば1かい、緑ならば二回黒は何度当たっても消えないようにしています制限時間も任意に設定できます。ただし、課題の提出期限もあり、ゲームの制限時間後、ブロックの数などをカウントすればよかったのですが間に合わず中途半端な出来になってしまいました。必ずgameclearと表示され失敗の条件ができませんでした。そのため、何回か投稿して改善していきます参考サイト
http://aidiary.hatenablog.com/entry/20040918/1251373370
このサイトによってブロック崩しが作れました。根幹のアルゴリズムはこちらを参考にして下さい。
一部はほとんど同じですが、本当にわかりやすいです。ありがとうございました。javaの環境
java version "12.0.1" 2019-04-16
Java(TM) SE Runtime Environment (build 12.0.1+12)
Java HotSpot(TM) 64-Bit Server VM (build 12.0.1+12, mixed mode, sharing)プログラムコード
900行近くなりましたので別々に記事を分けて説明をしていきたいと思います。そうした方が飽きないと思うので...
import javax.swing.Timer; import java.awt.Color; import javax.swing.*; import java.awt.*; import java.text.ParseException; public class TimerTest1 extends JFrame { static int x=855; static int y=800; private Timer timer; private int countdown_sec = 5; private CardLayout card = new CardLayout(0, 0); public static void main(String[] args) throws ParseException { TimerTest1 frame = new TimerTest1(); frame.setSize(x,y); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.getContentPane().setBackground(new Color(0, 0, 0)); frame.setVisible(true); } TimerTest1() { setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setTitle("タイトル画面"); setLayout(card); JPanel panel = new Draw(); //JPanel labelPanel = new JPanel(); //JLabel label = new JLabel(); JPanel game_over = new breakout(); game_over.setOpaque(false); add(panel,"文字の描画"); // labelPanel.add(label); timer = new Timer(1000, (e) -> { if (countdown_sec ==0){ add(game_over, "gameover_window"); timer.stop(); showPanel("gameover_window"); return; } countdown_sec--; }); timer.start(); } public void showPanel(String name) { card.show(getContentPane(), name); } public static class Draw extends JPanel{ public void paintComponent(Graphics g){ draw(g); } public static void draw(Graphics g){ g.setFont(new Font("TimeRoman", Font.CENTER_BASELINE, 30)); g.setColor(Color.red); g.drawString("ブロック崩し",300 , 200); g.setColor(Color.red); g.drawString("五秒後に始まるよー!", 250, 300); g.drawString("制限時間は30秒です", 250, 400); g.setColor(Color.red); g.setColor(Color.red); g.drawString("マウスを横に動かしてボールを跳ね返そう", 125, 500); g.setColor(Color.red); g.drawString("マウスは下の矢印の先っぽがが初期位置でつ", 100, 600); g.setColor(Color.red); g.drawString("↓",425 , 700); } } }import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.util.*; import java.util.Timer; import java.util.TimerTask; public class breakout extends JPanel implements MouseMotionListener{ static int countball =15; Racket racket; Ball[] ball; Block[] block; Unbreakblock[] block1; Twobreakblock[] block2; Threebreakblock[] block3; double time; int remain=37; boolean gameover=false; static int x=855; static int y=800; static int size =15; static final int NUM_BLOCK_ROW = 4; // ブロックの列数 static final int NUM_BLOCK_COL = 9; // ブロック数 static final int NUM_BLOCK = NUM_BLOCK_ROW * NUM_BLOCK_COL; static final int NUM_BLOCK_ROW1 = 8; static final int NUM_BLOCK_COL1 = 1; static final int NUM_BLOCK1 = NUM_BLOCK_ROW1 * NUM_BLOCK_COL1; static final int NUM_BLOCK_ROW2 = 8; static final int NUM_BLOCK_COL2 = 3; static final int NUM_BLOCK2 = NUM_BLOCK_ROW2 * NUM_BLOCK_COL2; static final int NUM_BLOCK_ROW3 = 8; static final int NUM_BLOCK_COL3 = 4; static final int NUM_BLOCK3 = NUM_BLOCK_ROW3 * NUM_BLOCK_COL3; static final int SumBlock= NUM_BLOCK+NUM_BLOCK2+NUM_BLOCK3; //二次元マップの作成 private char[][] map; private int MX = 20, MY = 18; private String[] map_str = { " B", " B", " B", " B", " B", " B", " B", " B", " B", " B", " B", " B", " B", " B", " B", " B", " B", " B", " B", }; public breakout(){ time = System.currentTimeMillis() * 0.001 + remain; addMouseMotionListener(this); racket =new Racket(); ball = new Ball[countball]; Random rand = new Random(); int n=countball; int num[] = new int [n]; for(int i=0;i<n;i++){ num[i]= 40+rand.nextInt(700); ball[0]=new Ball(num[0],250,5,-6,7); ball[1]=new Ball(num[1],260,-5,-3,10); ball[2]=new Ball(num[2],420,4,6,8); ball[3]=new Ball(num[3],480,-5,2,10); ball[4]=new Ball(num[4],590,5,-6,11); ball[5]=new Ball(num[5],550,-5,-3,12); ball[6]=new Ball(num[6],570,4,6,13); ball[7]=new Ball(num[7],480,-5,2,14); ball[8]=new Ball(num[8],490,5,-6,8); ball[9]=new Ball(num[9],400,-5,-3,8); ball[10]=new Ball(num[10], 350,4,6,9); ball[11]=new Ball(num[11],400,-5,2,10); ball[12]=new Ball(num[12],390,-5,-3,10); ball[13]=new Ball(num[13],500,4,6,10); ball[14]=new Ball(num[14],530,-5,2,7); } block = new Block[NUM_BLOCK]; block1 = new Unbreakblock[NUM_BLOCK1]; block2 = new Twobreakblock[NUM_BLOCK2]; block3 =new Threebreakblock[NUM_BLOCK3]; for (int i = 0; i < NUM_BLOCK_ROW; i++) { for (int j = 0; j < NUM_BLOCK_COL; j++) { int x =2* j * Block.WIDTH + Block.WIDTH+50; int y =2* i * Block.HEIGHT + Block.HEIGHT+600; block[i * NUM_BLOCK_COL + j] = new Block(x, y); } } for ( int c = 0; c < NUM_BLOCK_ROW1; c++) { for (int d = 0; d < NUM_BLOCK_COL1; d++) { int a = 2*c * Unbreakblock.WIDTH1 + Unbreakblock.WIDTH1+50; int b = d * Unbreakblock.HEIGHT1 + Unbreakblock.HEIGHT1+450; block1[d * NUM_BLOCK_COL1 + c] = new Unbreakblock(a, b); } } for ( int c = 0; c < NUM_BLOCK_ROW2; c++) { for (int d = 0; d < NUM_BLOCK_COL2; d++) { int a = 2*c * Twobreakblock.WIDTH2 + Twobreakblock.WIDTH2+50; int b = 2*d * Twobreakblock.HEIGHT2 + Twobreakblock.HEIGHT2+300; block2[c* NUM_BLOCK_COL2 + d] = new Twobreakblock(a, b); } } for ( int c = 0; c < NUM_BLOCK_ROW3; c++) { for (int d = 0; d < NUM_BLOCK_COL3; d++) { int a =2* c * Threebreakblock.WIDTH3 + Threebreakblock.WIDTH3+50; int b = 5*d * Threebreakblock.HEIGHT3 + Threebreakblock.HEIGHT3+60; block3[c * NUM_BLOCK_COL3 + d] = new Threebreakblock(a, b); } } TimeBomb timeBomb = new TimeBomb(); Timer timer = new Timer(); timer.schedule(timeBomb, 5000); map = new char[MY+2][MX+2]; for (int x = 0; x <= MX+1; x++) { map[0][x] = 'B'; map[MY+1][x] = 'B'; } for (int y = 0; y <= MY+1; y++) { map[y][0] = 'B'; map[y][MX+1] = 'B'; } for (int y = 1; y <= MY; y++) { for (int x = 1; x <= MX; x++) { map[y][x] = map_str[y-1].charAt(x-1); } } } public void displayTime(Graphics g) { if ( gameover ) { g.setFont(new Font("TimeRoman", Font.BOLD, 50)); g.setColor(Color.YELLOW); g.drawString("GAME CLEAR", 250, 550); for(int j=0;j<countball;j++){ int ballsize =ball[j].Size(); ball[j].Size(); if(ballsize==0){ return; } } } else { int dt = (int) (time - System.currentTimeMillis() * 0.001); g.setFont(new Font("TimeRoman", Font.BOLD, 50)); g.setColor(Color.orange); g.drawString("Time: " + dt+" ",300, 550); if ( dt == 0 ) gameover = true; } } class TimeBomb extends TimerTask { public void run(){ while(true){ for(int j=0;j<countball;j++){ ball[j].move(); // ラケットとボールの衝突処理 int collidePos0 = racket.collideWith(ball[j]); // ラケットに当たっていたら if (collidePos0 != Racket.NO_COLLISION4) { // ボールの当たった位置に応じてボールの速度を変える switch (collidePos0) { case Racket.LEFT4: // ラケットの左側に当たったときは左に反射するようにしたい // もしボールが右に進んでいたら反転して左へ // 左に進んでいたらそのまま if (ball[j].getVX() > 0) ball[j].boundX(); ball[j].boundY(); break; case Racket.RIGHT4: // ラケットの右側に当たったときは右に反射するようにしたい // もしボールが左に進んでいたら反転して右へ // 右に進んでいたらそのまま if (ball[j].getVX() < 0) ball[j].boundX(); ball[j].boundY(); break; } } for (int i = 0; i < NUM_BLOCK; i++) { // すでに消えているブロックは無視 if (block[i].isDeleted()) continue; // ブロックの当たった位置を計算 int collidePos = block[i].collideWith(ball[j]); // ブロックに当たっていたら if (collidePos != Block.NO_COLLISION) { block[i].delete(); // ボールの当たった位置からボールの反射方向を計算 switch (collidePos) { case Block.DOWN : case Block.UP : ball[j].boundY(); break; case Block.LEFT : case Block.RIGHT : ball[j].boundX(); break; case Block.UP_LEFT : case Block.UP_RIGHT : case Block.DOWN_LEFT : case Block.DOWN_RIGHT : ball[j].boundXY(); break; } break; // 1回に壊せるブロックは1つ } } for (int i = 0; i < NUM_BLOCK1; i++) { int collidePos1 = block1[i].collideWith(ball[j]); if ( collidePos1 !=Unbreakblock.NO_COLLISION1) { block1[i].notdelete(); switch (collidePos1) { case Unbreakblock.DOWN1 : case Unbreakblock.UP1 : ball[j].boundY(); break; case Unbreakblock.LEFT1 : case Unbreakblock.RIGHT1 : ball[j].boundX(); break; case Unbreakblock.UP_LEFT1 : case Unbreakblock.UP_RIGHT1 : case Unbreakblock.DOWN_LEFT1 : case Unbreakblock.DOWN_RIGHT1 : ball[j].boundXY(); break; } break; } } for (int i = 0; i < NUM_BLOCK2; i++) { if (block2[i].isDeleted()){ continue; } int collidePos2 = block2[i].collideWith(ball[j]); if ( collidePos2 !=Twobreakblock.NO_COLLISION2) { block2[i].delete(); switch (collidePos2) { case Twobreakblock.DOWN2 : case Twobreakblock.UP2 : ball[j].boundY(); break; case Twobreakblock.LEFT2 : case Twobreakblock.RIGHT2 : ball[j].boundX(); break; case Twobreakblock.UP_LEFT2 : case Twobreakblock.UP_RIGHT2 : case Twobreakblock.DOWN_LEFT2 : case Twobreakblock.DOWN_RIGHT2 : ball[j].boundXY(); break; } break; } } for (int i = 0; i < NUM_BLOCK3; i++) { if (block3[i].isDeleted()){ continue; } int collidePos3 = block3[i].collideWith(ball[j]); if ( collidePos3 !=Threebreakblock.NO_COLLISION3) { block3[i].delete(); switch (collidePos3) { case Threebreakblock.DOWN3 : case Threebreakblock.UP3 : ball[j].boundY(); break; case Threebreakblock.LEFT3 : case Threebreakblock.RIGHT3 : ball[j].boundX(); break; case Threebreakblock.UP_LEFT3 : case Threebreakblock.UP_RIGHT3 : case Threebreakblock.DOWN_LEFT3 : case Threebreakblock.DOWN_RIGHT3 : ball[j].boundXY(); break; } break; } } } repaint(); try { Thread.sleep(20); }catch(InterruptedException e){ e.printStackTrace(); } } } } public void paintComponent(Graphics g){ racket.draw(g); displayTime(g); for(int i=0;i<countball;i++){ ball[i].draw(g); } for (int i=0;i<NUM_BLOCK;i++){ if(!block[i].isDeleted()){ block[i].draw(g); } } for (int i=0;i<NUM_BLOCK1;i++){ if(!block1[i].isDeleted()){ block1[i].draw(g); } } for (int i=0;i<NUM_BLOCK2;i++){ if(!block2[i].isDeleted()){ block2[i].draw(g); } } for (int i=0;i<NUM_BLOCK3;i++){ if(!block3[i].isDeleted()){ block3[i].draw(g); } } for (int y = 0; y <= MY; y++) { for (int x = 0; x <= MX; x++) { int xx = 40*x, yy = 40*y; switch ( map[y][x] ) { //ブロックの描画 case 'B': g.setColor(Color.green); g.fillRect(xx, yy, 26, 10); g.fillRect(xx+32, yy, 8, 10); g.fillRect(xx, yy+15, 10, 10); g.fillRect(xx+16, yy+15, 24, 10); g.fillRect(xx, yy+30, 18, 10); g.fillRect(xx+24, yy+30, 16, 10); break; } } } } @Override public void mouseMoved(MouseEvent e){ int x =e.getX(); racket.move(x); repaint(); } @Override public void mouseDragged(MouseEvent e){ } } class Racket { public int width =120; public static int height = 5; public static final int NO_COLLISION4 = 0; // 未衝突 public static final int LEFT4 = 1; public static final int RIGHT4 = 2; private int center; public Racket (){ center = breakout.x/2; } public int collideWith(Ball ball) { //ボールの衝突判定 Rectangle racketRectLeft = new Rectangle( center - width / 2, breakout.y - height-90, width/2, height); Rectangle racketRectRight = new Rectangle( center, breakout.y - height-90, width / 2, height); Rectangle ballRect = new Rectangle( ball.getX(), ball.getY(), ball.getSize(), ball.getSize()); if (racketRectLeft.intersects(ballRect)) { return LEFT4; } else if (racketRectRight.intersects(ballRect)) { return RIGHT4; } return NO_COLLISION4; } public void draw (Graphics g){ g.setColor(Color.WHITE); g.fillRect(center - width/2,breakout.y-height-90,width,height); } public void move (int x ){ center =x; if (center<(width/2)+40) center =(width/2)+40; else if (center > breakout.x - (width / 2)-55) center = breakout.x - (width / 2)-55; } } class Ball { private int x; private int y; private int vx; private int vy; private int size; double time; int remain=37; boolean gameover=false; int dt = (int) (time - System.currentTimeMillis() * 0.001); Random rand ; public Ball(int x,int y,int vx,int vy,int size ){ this.x=x; this.y=y; this.vx=vx; this.vy=vy; this.size =size; time = System.currentTimeMillis() * 0.001 + remain; } public void draw(Graphics g){ g.setColor(Color.RED); g.fillOval(x,y,size,size); } public int Size(){ return size=size-3; } public void move(){ if ( gameover ) { x=0; y=0; } else { int dt = (int) (time - System.currentTimeMillis() * 0.001); x += vx; y += vy; if ( dt == 0 ) gameover = true; } if (x < 40 || x > breakout.x - size-60) { boundX(); } if (y < 40 ) { boundY(); } } public void boundX(){ vx=-vx; } public void boundY(){ vy=-vy; } public int getX(){ return x; } public void boundXY(){ vx=-vx; vy=-vy; } public int getY(){ return y; } public int getSize(){ return size; } public int getVX(){ return vx; } public int getVY(){ return vy; } public void setVX(int v){ vx=v; } public void setVY(int v){ vy=v; } } class Block { public static final int WIDTH = 40; public static final int HEIGHT = 16; public static final int NO_COLLISION = 0; // 未衝突 public static final int DOWN = 1; public static final int LEFT = 2; public static final int RIGHT = 3; public static final int UP = 4; public static final int DOWN_LEFT = 5; public static final int DOWN_RIGHT = 6; public static final int UP_LEFT = 7; public static final int UP_RIGHT = 8; // 位置(左上隅の座標) private int x, y; // ボールが当たって消されたか private boolean isDeleted; public Block(int x, int y) { this.x = x; this.y = y; isDeleted = false; } public void draw(Graphics g) { g.setColor(Color.CYAN); g.fillRect(x, y, WIDTH, HEIGHT); // 枠線を描画 g.setColor(Color.BLACK); g.drawRect(x, y, WIDTH, HEIGHT); } public int collideWith(Ball ball) { Rectangle blockRect = new Rectangle(x, y, WIDTH, HEIGHT); int ballX = ball.getX(); int ballY = ball.getY(); int ballSize = ball.getSize(); if (blockRect.contains(ballX, ballY) && blockRect.contains(ballX + ballSize, ballY)) { // ブロックの下から衝突=ボールの左上・右上の点がブロック内 return DOWN; } else if (blockRect.contains(ballX + ballSize, ballY) && blockRect.contains(ballX + ballSize, ballY + ballSize)) { // ブロックの左から衝突=ボールの右上・右下の点がブロック内 return LEFT; } else if (blockRect.contains(ballX, ballY) && blockRect.contains(ballX, ballY + ballSize)) { // ブロックの右から衝突=ボールの左上・左下の点がブロック内 return RIGHT; } else if (blockRect.contains(ballX, ballY + ballSize) && blockRect.contains(ballX + ballSize, ballY + ballSize)) { // ブロックの上から衝突=ボールの左下・右下の点がブロック内 return UP; } else if (blockRect.contains(ballX + ballSize, ballY)) { // ブロックの左下から衝突=ボールの右上の点がブロック内 return DOWN_LEFT; } else if (blockRect.contains(ballX, ballY)) { // ブロックの右下から衝突=ボールの左上の点がブロック内 return DOWN_RIGHT; } else if (blockRect.contains(ballX + ballSize, ballY + ballSize)) { // ブロックの左上から衝突=ボールの右下の点がブロック内 return UP_LEFT; } else if (blockRect.contains(ballX, ballY + ballSize)) { // ブロックの右上から衝突=ボールの左下の点がブロック内 return UP_RIGHT; } return NO_COLLISION; } public void delete() { isDeleted = true; } public int getX(){ return x; } public int getY(){ return y; } public boolean isDeleted(){ return isDeleted; } } interface figure1 { public void draw(Graphics g); public int collideWith(Ball ball); public int getX(); public int getY(); public boolean isDeleted(); } class Unbreakblock implements figure1 { public static final int WIDTH1 = 40; public static final int HEIGHT1 = 16; public static final int NO_COLLISION1= 0; public static final int DOWN1= 1; public static final int LEFT1 = 2; public static final int RIGHT1 = 3; public static final int UP1 = 4; public static final int DOWN_LEFT1 = 5; public static final int DOWN_RIGHT1 = 6; public static final int UP_LEFT1 = 7; public static final int UP_RIGHT1 = 8; private int a, b; private boolean isDeleted; public Unbreakblock(int a, int b) { this.a = a; this.b = b; isDeleted = false; } public void draw(Graphics g) { g.setColor(Color.GRAY); g.fillRect(a, b, WIDTH1, HEIGHT1); g.setColor(Color.BLACK); g.drawRect(a, b, WIDTH1, HEIGHT1); } public int collideWith(Ball ball) { Rectangle blockRect = new Rectangle(a, b, WIDTH1, HEIGHT1); int ballX = ball.getX(); int ballY = ball.getY(); int ballSize = ball.getSize(); if (blockRect.contains(ballX, ballY) && blockRect.contains(ballX + ballSize, ballY)) { return DOWN1; } else if (blockRect.contains(ballX + ballSize, ballY) && blockRect.contains(ballX + ballSize, ballY + ballSize)) { return LEFT1; } else if (blockRect.contains(ballX, ballY) && blockRect.contains(ballX, ballY + ballSize)) { return RIGHT1; } else if (blockRect.contains(ballX, ballY + ballSize) && blockRect.contains(ballX + ballSize, ballY + ballSize)) { return UP1; } else if (blockRect.contains(ballX + ballSize, ballY)) { return DOWN_LEFT1; } else if (blockRect.contains(ballX, ballY)) { return DOWN_RIGHT1; } else if (blockRect.contains(ballX + ballSize, ballY + ballSize)) { return UP_LEFT1; } else if (blockRect.contains(ballX, ballY + ballSize)) { return UP_RIGHT1; } return NO_COLLISION1; } public void notdelete() { isDeleted = false ; } public int getX(){ return a; } public int getY(){ return b; } public boolean isDeleted(){ return isDeleted; } } class Twobreakblock implements figure1 { public static final int WIDTH2 = 40; public static final int HEIGHT2 = 16; public static final int NO_COLLISION2= 0; public static final int DOWN2= 21; public static final int LEFT2 = 22; public static final int RIGHT2 = 23; public static final int UP2 = 24; public static final int DOWN_LEFT2 = 25; public static final int DOWN_RIGHT2 = 26; public static final int UP_LEFT2 = 27; public static final int UP_RIGHT2 = 28; int a, b; int count =2; private boolean isDeleted; public Twobreakblock(int a, int b) { this.a = a; this.b = b; isDeleted = false; } public void draw(Graphics g) { g.setColor(Color.GREEN); g.fillRect(a, b, WIDTH2, HEIGHT2); g.setColor(Color.BLACK); g.drawRect(a, b, WIDTH2, HEIGHT2); } public int collideWith(Ball ball) { Rectangle blockRect = new Rectangle(a, b, WIDTH2, HEIGHT2); int ballX = ball.getX(); int ballY = ball.getY(); int ballSize = ball.getSize(); if (blockRect.contains(ballX, ballY) && blockRect.contains(ballX + ballSize, ballY)) { return DOWN2; } else if (blockRect.contains(ballX + ballSize, ballY) && blockRect.contains(ballX + ballSize, ballY + ballSize)) { return LEFT2; } else if (blockRect.contains(ballX, ballY) && blockRect.contains(ballX, ballY + ballSize)) { return RIGHT2; } else if (blockRect.contains(ballX, ballY + ballSize) && blockRect.contains(ballX + ballSize, ballY + ballSize)) { return UP2; } else if (blockRect.contains(ballX + ballSize, ballY)) { return DOWN_LEFT2; } else if (blockRect.contains(ballX, ballY)) { return DOWN_RIGHT2; } else if (blockRect.contains(ballX + ballSize, ballY + ballSize)) { return UP_LEFT2; } else if (blockRect.contains(ballX, ballY + ballSize)) { return UP_RIGHT2; } return NO_COLLISION2; } public void delete() { if(isDeleted==false) count--; if(count==0) isDeleted = true ; } public int getX(){ return a; } public int getY(){ return b; } public boolean isDeleted(){ return isDeleted; } } class Threebreakblock implements figure1{ public static final int WIDTH3 = 40; public static final int HEIGHT3 = 16; public static final int NO_COLLISION3= 0; public static final int DOWN3= 1; public static final int LEFT3 = 2; public static final int RIGHT3 = 3; public static final int UP3 = 4; public static final int DOWN_LEFT3 = 5; public static final int DOWN_RIGHT3 = 6; public static final int UP_LEFT3 = 7; public static final int UP_RIGHT3 = 8; private int a, b; int count =3; private boolean isDeleted; public Threebreakblock(int a, int b) { this.a = a; this.b = b; isDeleted = false; } public void draw(Graphics g) { g.setColor(Color.YELLOW); g.fillRect(a, b, WIDTH3, HEIGHT3); g.setColor(Color.BLACK); g.drawRect(a, b, WIDTH3, HEIGHT3); } public int collideWith(Ball ball) { Rectangle blockRect = new Rectangle(a, b, WIDTH3, HEIGHT3); int ballX = ball.getX(); int ballY = ball.getY(); int ballSize = ball.getSize(); if (blockRect.contains(ballX, ballY) && blockRect.contains(ballX + ballSize, ballY)) { return DOWN3; } else if (blockRect.contains(ballX + ballSize, ballY) && blockRect.contains(ballX + ballSize, ballY + ballSize)) { return LEFT3; } else if (blockRect.contains(ballX, ballY) && blockRect.contains(ballX, ballY + ballSize)) { return RIGHT3; } else if (blockRect.contains(ballX, ballY + ballSize) && blockRect.contains(ballX + ballSize, ballY + ballSize)) { return UP3; } else if (blockRect.contains(ballX + ballSize, ballY)) { return DOWN_LEFT3; } else if (blockRect.contains(ballX, ballY)) { return DOWN_RIGHT3; } else if (blockRect.contains(ballX + ballSize, ballY + ballSize)) { return UP_LEFT3; } else if (blockRect.contains(ballX, ballY + ballSize)) { return UP_RIGHT3; } return NO_COLLISION3; } public void delete() { if(isDeleted==false) count--; if(count==0) isDeleted = true ; } public int getX(){ return a; } public int getY(){ return b; } public boolean isDeleted(){ return isDeleted; } }インデントが汚いですね...課題が提出されたそのままの状態を書いております。
breakoutクラスにはそれぞれ、ブロックやボール、ボールを跳ね返すラケットのインスタンス変数を格納しております。
また、extendsでJpanelの機能を継承し、さらに、implementsでマウスイベントを扱えるようにしました。
制限時間は30秒と設定しておりますが。最初のスタート画面で5秒間待たないといけないようにしました.タイトル画面を含めて35秒に設定したのになぜか37秒でないとちょうどゲームがスタートされないようになっています。原因が分かりません。いざ実際にコードを説明すると途方もないくらい時間がかかりそう...今回は紹介程度コードを含めて記事を書きました。見てみるともっと省略できる部分もあったのではないか...例えばブロックの機能をほぼ同じコードで重複して書いていたりと、非常にわかりづらい...書けたはいいが相手にわからせなければ意味がないですよね。自己満足で終わらないように今後もわかりやすく相手に伝わるコードを書いていきたいと思います。次回は詳しくコードを説明します
- 投稿日:2020-08-07T13:19:43+09:00
[Java]MinecraftのModを作成しよう 1.14.4【7. 進捗の追加】
(この記事は一連の解説記事の一つになります)
先頭記事:入門編
前の記事:6. レシピの追加
次の記事:8. 鉱石の追加と生成進捗の追加
ここまでアイテム関連を広く浅く触れてきましたが、今度は少し趣向を変えて、進捗(advancements)の追加を行ってみます。
このようなツリー構造のいわゆるトロフィーのようなものです。進捗の追加は比較的簡単で、1.14.4ではjsonベースで管理されています。
\src\main\resources ├ assets └ data └ example_mod ├ advancements │ └ root.json ├ loot_tables └ recipes
\src\main\resources\data\example_mod\advancements
フォルダを作り、この中に配置していきます。
まずツリーの根にあたる進捗が1つ必要になりますので、これを作ります。
参考ページに詳しいので、これを参考にしながら書きましょう。root.json{ "display": { "icon": { "item": "example_mod:example_ingot" }, "title": { "translate": "advancements.root.title" }, "description": { "translate": "advancements.root.description" }, "frame": "task", "show_toast": true, "announce_to_chat": true, "hidden": false, "background": "minecraft:textures/block/stone.png" }, "criteria": { "get_example_ingot": { "trigger": "minecraft:inventory_changed", "conditions": { "items": [ { "item": "example_mod:example_ingot" } ] } } } }これは
example_ingot
を入手した際に達成される進捗の例です。
重ねてになりますが、参考ページに十分な説明がありますので、詳しくはそちらを確認してください。一部かいつまんで説明します。
frame
は進捗のタイルのフレームの指定です。challenge、goal、taskの3つから適当なものを設定しましょう。デフォルトtask。
show_toast
announce_to_chat
はそれぞれ「達成時に右上にメッセージを出すかどうか」「チャット欄にメッセージを出すかどうか」で、true/falseを与えます。デフォルトtrue。
hidden
は「一つ達成されるまでタブを表示しないかどうか」…と書かれていますが、falseを指定してもなぜか表示されないのでよくわかりません。デフォルトfalse。
criteria
は進捗の条件を定める項です。任意のuniqueな名前をタグ名とし(get_example_ingot
の部分)、trigger
に各種トリガー、conditions
にトリガーに対応した詳細な条件を記述します。タイトルと説明の
translate
について、langファイルに追記をします。en_us.json{ "advancements.root.title": "Example Title", "advancements.root.description": "Example description." }ja_jp.json{ "advancements.root.title": "例タイトル", "advancements.root.description": "説明の例。" }
もう一つくらい例を見てみましょう。
obtail_armor.json{ "parent": "example_mod:root", "display": { "icon": { "item": "example_mod:example_chestplate" }, "title": { "translate": "advancements.obtain_armor.title" }, "description": { "translate": "advancements.obtain_armor.description" }, "frame": "task", "show_toast": true, "announce_to_chat": true, "hidden": false }, "criteria": { "example_helmet": { "trigger": "minecraft:inventory_changed", "conditions": { "items": [ { "item": "example_mod:example_helmet" } ] } }, "example_chestplate": { "trigger": "minecraft:inventory_changed", "conditions": { "items": [ { "item": "example_mod:example_chestplate" } ] } }, "example_leggings": { "trigger": "minecraft:inventory_changed", "conditions": { "items": [ { "item": "example_mod:example_leggings" } ] } }, "example_boots": { "trigger": "minecraft:inventory_changed", "conditions": { "items": [ { "item": "example_mod:example_boots" } ] } } }, "requirements": [ [ "example_helmet", "example_chestplate", "example_leggings", "example_boots" ] ], "rewards": { "experience": 100 } }いずれかの防具を入手で達成される進捗の例です。
特に注目してほしいのは、前項でなかったparent
の要素が増えていることです。ここに先ほど定義したexample_mod:root
を指定することで、子の要素とすることができます。なお、進捗では親が子の前提条件とはならない(順不同で達成可能)ですが、親を達成した場合子の子まで(つまり2段先まで)がすべて表示されるようになるようです(逆に子を達成した場合最短の経路で根までの親が表示されます)。また、1つの親に対して子は複数作ることができます。
他に異なるのは、criteria
の要素が複数記述されていること、requirements
rewards
が増えていることです。この例のように、criteria
の要素は複数記述でき、requirements
ではこれらを用いてどのように達成判定を行うか記述します。[A,B]
はAまたはB、[A],[B]
はAかつBを示します。reward
には進捗達成時の報酬を設定できます。ここでは経験値を付与していますが、レシピの開放・アイテム付与・任意関数の実行が可能です。
きちんと経験値が取得できています。
進捗は自分の作るModがどう楽しめるか伝える良い手段ですので、効果的に活用しましょう。
追記
1.12より前は進捗ではなく実績という異なるものが存在したので、情報を探す際に混同しないように注意が必要です。
参考
進捗 - Minecraft Japan Wiki【8/4更新】 - アットウィキ
次の記事
- 投稿日:2020-08-07T12:44:51+09:00
Java練習問題 【基礎編】
Javaを勉強するために、練習問題を解き始めました。
間違っていたらすいません。基礎編
package com.company;
import java.util.Scanner;public class HundredKnocksBasic {
public static void main(String arg[]) {
// question0();
// question1();
// question2();
// question3();
// question4();
// question5();
// question6();
// question7();
// question8();
// question9();
// question10();
// question11();
// question12();
// question13();
// question14();
// question15();
// question16();
// question17();
// question18();
// question19();
}実行するとHello World!と表示するメソッドを作成せよ。
private static void question0() { System.out.println("問0"); System.out.println("Hello World!"); }12345+23456を計算して結果を表示するプログラムを作成せよ。
private static void question1() { System.out.println("問1"); int a = 12345; int b = 23456; System.out.println(a + b); }12345を7で割った余りを表示するプログラムを作成せよ
private static void question2() { System.out.println("問2"); int a = 12345; int b = 7; System.out.println(a / b); }整数値を入力させ、その入力値を表示するプログラムを作成せよ。
private static void question3() { System.out.println("問3"); Scanner scanner = new Scanner(System.in); //Scannerクラスを初期化 Scannerクラスのインスタンス化 //InputStream のオブジェクトで、標準入力(通常はキーボードからの入力) System.out.println("数字を入力してください"); int num = scanner.nextInt(); //入力を受け取る部分 scanner.close(); System.out.println(num); }整数値を入力させ、その入力値を3倍した計算結果を表示するプログラムを作成せよ。
private static void question4() { System.out.println("問4"); Scanner scanner = new Scanner(System.in); System.out.println("数字を入力してください"); int num = scanner.nextInt(); //入力を受け取る部分 scanner.close(); System.out.println(num * 3); }整数値を2つ入力させ、それらの値の和、差、積、商と余りを求めるプログラムを作成せよ。なお、差と商は1つ目の値から2つ目の値を引いた、あるいは割った結果とする。余りは無い場合も0と表示するのでよい
private static void question5() { System.out.println("問5"); Scanner scanner = new Scanner(System.in); System.out.println("1つ目の数字を入力してください"); int num1 = scanner.nextInt(); //1つめ入力を受け取る部分 System.out.println("2つ目の数字を入力してください"); int num2 = scanner.nextInt(); //2つめ入力を受け取る部分 scanner.close(); System.out.println(num1 + num2); System.out.println(num1 - num2); System.out.println(num1 * num2); System.out.println(num1 / num2); }整数値を入力させ、値が0ならzeroと表示するプログラムを作成せよ
private static void question6() { System.out.println("問6"); Scanner scanner = new Scanner(System.in); System.out.println("数字を入力してください(0)"); int num = scanner.nextInt(); //入力を受け取る部分 scanner.close(); if (num == 0) { //もし入力された値が0なら System.out.println("zero"); } }整数値を入力させ、値が0ならzero、0でなければnot zeroと表示するプログラムを作成せよ。
private static void question7() { System.out.println("問7"); Scanner scanner = new Scanner(System.in); System.out.println("数字を入力してください(0以外)"); int num = scanner.nextInt(); scanner.close(); if (num == 0) { //もし入力された値が0なら System.out.println("zero"); } else { System.out.println("not zero"); } }整数値を入力させ、値が正であればpositiveと表示するプログラムを作成せよ。ただし0は正には含まない。
private static void question8() { System.out.println("問8"); Scanner scanner = new Scanner(System.in); System.out.println("数字を入力してください(正)"); int num = scanner.nextInt(); scanner.close(); if (num > 0) { //値が正ならば System.out.println("positive"); } }整数値を入力させ、値が正であればpositive、負であればnegative、0であればzeroと表示するプログラムを作成せよ。
private static void question9() {//else ifで書く System.out.println("問9"); Scanner scanner = new Scanner(System.in); System.out.println("数字を入力してください"); int num = scanner.nextInt(); scanner.close(); if (num > 0) { //値が正ならば System.out.println("positive"); } else if (num < 0) { //値が負ならば System.out.println("negative"); } else { //もし入力された値が0なら System.out.println("zero"); } }整数値を入力させ、その値を絶対値にして表示するプログラムを作成せよ。(できれば変数の値を絶対値に変えるようにせよ
private static void question10() { System.out.println("問10"); Scanner scanner = new Scanner(System.in); System.out.println("数字を入力してください(絶対値)"); int num = scanner.nextInt(); scanner.close(); ###絶対値を取得するには、Mathクラスのabsメソッドを使います。absメソッドはstaticメソッドなのでそのまま呼び出せます。 num = Math.abs(num); System.out.println(num); }Hello World!を10回繰り返して表示するプログラムを作成せよ。
private static void question11() { System.out.println("問11"); //for (初期化式; 条件式; 更新式) { for (int i = 0; i < 10; i++) { System.out.println("Hello World!"); } }整数値を入力させ、その値の回数だけHello World!を繰り返して表示するプログラムを作成せよ。
private static void question12() { System.out.println("問12"); Scanner scanner = new Scanner(System.in); System.out.println("繰り返す回数を入力してください"); int num = scanner.nextInt(); scanner.close(); //入力された回数だけ繰り返す for (int i = 0; i < num; i++) { System.out.println("Hello World!"); } }整数値を入力させ、0から入力値まで数を1ずつ増やして表示するプログラムを作成せよ。
private static void question13() { System.out.println("問13"); Scanner scanner = new Scanner(System.in); System.out.println("数字を入力してください"); int num = scanner.nextInt(); scanner.close(); //入力された数字まで実行する for (int i = 0; i < num; i++) { System.out.println(i); } }整数値を入力させ、入力値から0まで数を1ずつ減らして表示するプログラムを作成せよ。
private static void question14() { System.out.println("問14"); Scanner scanner = new Scanner(System.in); System.out.println("数字を入力してください"); int num = scanner.nextInt(); scanner.close(); //入力された数字から1ずつ減らす for (int i = 0; i < num; num--) { System.out.println(num); } }整数値を入力させ、0から入力値を超えない値まで2ずつ増やして表示するプログラムを作成せよ。
private static void question15() { System.out.println("問15"); Scanner scanner = new Scanner(System.in); System.out.println("数字を入力してください"); int num = scanner.nextInt(); scanner.close(); //入力された数字まで2つ増やす for (int i = 0; i < num; i += 2) { System.out.println(i); } }整数値を入力させ、入力値が0でなければ再度入力させ、0であれば終了するプログラムを作成せよ。
private static void question16() {//while文 System.out.println("問16"); Scanner scanner = new Scanner(System.in); System.out.println("数字を入力してください"); int num = scanner.nextInt(); scanner.close(); while (num != 0) { System.out.println("もう一度数字を入力してください"); num = scanner.nextInt(); //入力値が0だったら止める } scanner.close(); System.out.println("終了します"); }要素数10の整数型の配列を宣言し、i番目の要素の初期値をiとし、順に値を表示するプログラムを作成せよ。
private static void question17() { System.out.println("問17"); /* 配列の宣言と要素数の指定 型名 配列変数名[] = new 型名[要素数]; */ int bar[] = new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; //要素数10の配列を宣言 /* 型名 変数名 = 初期値; i番目の要素の初期値をi */ for (int i = 0; i < bar.length; i++) { //配列の要素数を取得するには、lengthを使用 bar[i]のiのことを、インデックス System.out.println(bar[i]); } }要素数10の整数型の配列を宣言し、整数値を入力させ、すべての配列の要素を入力値として、すべての要素の値を表示するプログラムを作成せよ。
private static void question18() { System.out.println("問18"); Scanner scanner = new Scanner(System.in); //要素数10の配列をローカル変数のa宣言 int[] a = new int[10]; System.out.println("数字を入力してください"); //ローカル変数bに値を入力させる int b = scanner.nextInt(); //左辺に代入される配列の要素を記述し、右辺に代入する値を記述 //配列変数名[配列番号] := 代入する値 //aにbで取得した値を代入する //配列の長さ //配列の長さぶん繰り返し for (int i = 0; i < a.length; i++) { a[i] = b; //配列aを表示する System.out.println(a[i]); } }要素数5の整数型の配列を宣言し、すべての配列に対して順に入力された整数値を代入し、すべての要素の値を表示するプログラムを作成せよ。
private static void question19() { System.out.println("問19"); Scanner sc = new Scanner(System.in); //要素数10の配列を宣言 int bar[] = new int[10]; bar[0] = sc.nextInt(); bar[1] = sc.nextInt(); bar[2] = sc.nextInt(); bar[3] = sc.nextInt(); bar[4] = sc.nextInt(); bar[5] = sc.nextInt(); bar[6] = sc.nextInt(); bar[7] = sc.nextInt(); bar[8] = sc.nextInt(); bar[9] = sc.nextInt(); //要素の数を取得する for (int i = 0; i < bar.length; i++) { System.out.println("配列" + i + "番目の値" + bar[i]); } } }
- 投稿日:2020-08-07T10:02:53+09:00
Java: ストリームとループってどっちが速いの問題
やる前から予想は付いてるけど、形にしておくことも重要だと思います。
ストリームの処理速度ってどうなんでしょうか
Java言語におけるストリームは、流れにそった記述ができるということで、うまく使えば可読性に優れたコード、すなわち挙動の予測が付きやすいものへと移行できる可能性を持つ機能です。分岐の発生しにくい流れで記述できると気持ちよくなりますよね。
とはいえ、実行時のことを考えたとき、本当にストリームで良かったのかということになります。
良くありそうなのが、ループ処理で書いていたことをストリームにするような行為です。試してみよう
ということで、100万回乱数を発生してすべて足し込むという処理にかかる時間を計測してみます。実行環境はWin10Pro(i5, 8GB RAM)です。
InfinitTest2_loop.javapublic class InfinitTest2_loop { public static void main(String[] args) { Double result = 0.0; var start = System.currentTimeMillis(); for (int i = 0; i < 100 * 10000; i++) { result += Math.random(); } var end = System.currentTimeMillis(); System.out.println(result); System.out.println("所要時間(ミリ秒): " + (end - start)); } }このコードをさっとストリームに変換してみます。
reduce()
に渡して足し込み処理までを記述してみます。InfinitTest2.javaimport java.util.stream.Stream; public class InfinitTest2 { public static void main(String[] args) { var start = System.currentTimeMillis(); Double result = Stream.generate(Math::random) .limit(100 * 10000) .reduce((p, q) -> p + q).get(); var end = System.currentTimeMillis(); System.out.println(result); System.out.println("所要時間(ミリ秒): " + (end - start)); } }ということで両者を実行してみた。それぞれ3回実行したときのミリ秒です
- ループ版: 45,41,39
- ストリーム版: 63,71,70
ループ版はストリーム版の70%程度ほ処理時間で動いてます。うん、やっぱり下手にクラスの処理が入らないループ版の方が速いんですね。
バカにできない「最適化」
ここで終わりにしてはいけない気がします、Javaは実行環境も含めたJavaです。たとえば、Java VMをサーバーモードにすると、通常のVM(ClientVM)においては実行中の最適化を意識するところを、実行前に可能な範囲で最適化を試みることになります。
PS> java -server -cp . InfinitTest 499714.1320036936 所要時間(ミリ秒): 63あれ? たいして変わらない、ただし繰り返したときに65ミリ秒までにおさまってますので、「可能な範囲での最適化」は試みてるみたいですね。
あともうひとつ最適化という話になると、さらっと出ていた「実行中の最適化」という問題で、「よく実行している部分を順次最適化していく」という動きです。つまりループするならその部分が最適化によって高速化しないのか?ということです。
先ほどの処理を大量にループするようにします。この部分は差違が発生しないよう、どちらも
for
を使って書くことにしておきます。public class InfinitTest2_loop { public static void main(String[] args) { for (int n = 0; n < 100; n++) { Double result = 0.0; var start = System.currentTimeMillis(); for (int i = 0; i < 100 * 10000; i++) { result += Math.random(); } var end = System.currentTimeMillis(); System.out.println(result); System.out.println("所要時間(ミリ秒): " + (end - start)); } } }import java.util.stream.Stream; public class InfinitTest { public static void main(String[] args) { for (int i = 0; i < 100; i++) { var start = System.currentTimeMillis(); Double result = Stream.generate(Math::random).limit(100 * 10000).reduce((p, q) -> p + q).get(); var end = System.currentTimeMillis(); System.out.println(result); System.out.println("所要時間(ミリ秒): " + (end - start)); } } }と、100回繰り返すようにしてみました。
途中で最適化が入り始めるのか、ミリ秒出力が徐々に減っていきました。最後の3回をそれぞれ出してみるとこうなりました。
- ループ版: 33,34,22
- ストリーム版: 32,30,39
あら、けっこう差が縮まってる(それでもループ版が速いけど)。
ということで、ループ処理をストリームに置換すべきにおいて、速度を意識しないといけない場合は、
- ストリーム部分の高速化は、繰り返し処理されることによる最適化は期待できることを意識する
- 1回しか処理しないループであれば、最適化を期待せずループのままの方が速い
というところでしょうかね。
ぼやき
なにも渡さずに無限回数(何らかの条件で抜け出すまで)のデータを生成するストリームのジェネレータってないのかな。
while(true){...}
をストリーム的に表現できないかという話です。
- 投稿日:2020-08-07T00:41:03+09:00
Microsoft Graph を 標準Javaで使う
標準JavaパッケージでMicrosoft Graph
Microsoft SDK for Java を使うといいのですが、これを使うと関連Jarファイルがたくさん必要です。本質的にやっていることを理解するために、標準JavaパッケージだけでMicrosoft Graphの問い合わせを書いてみます。
やることは、
1. Azure AD にアプリケーションを登録してクライアントIDとシークレットを作る(手順はチュートリアルを参考)
2. Azure AD からOAuth2でアクセストークンをもらう。JSONで返ってくる。
3. Microsoft Graph API のエンドポイントに問い合わせする。JSON返ってくる。
だけです。ひとまずCurlコマンドで書くとこうなります
アクセストークンを取得する。
OAuthの手続きに従い、まずはアクセストークンを Azure Active Directoryから取得する。
取得する先のURL(エンドポイント)は、https://login.microsoftonline.com/テナントID/oauth2/v2.0/token
テナントIDはAzure ADのテナント概要にあり。
ここに向かって作成したクライアントIDとシークレットをPOST。
scopeはURLをエスケープしているので%3Aや%2Fとなっています。どんなのが指定できるか詳しくはこちら。grant_typeはOAuthのパラメータで固定値です。curl -d "client_id=クライアントID" \ -d "scope=https%3A%2F%2Fgraph.microsoft.com%2F.default" \ -d "client_secret=クライアントシークレット" \ -d "grant_type=client_credentials" \ -H "Content-Type: application/x-www-form-urlencoded" \ -X POST https://login.microsoftonline.com/テナントID/oauth2/v2.0/tokenアクセストークンを使ってMicrosoft Graph APIにお問い合わせ
先のアクセストークンをコピーして今度はMicrosoft Graph APIに問い合わせや操作を行います。例えばグループメンバーを取得したかったらこんな感じになります。
エンドポイントはこちら。https://graph.microsoft.com/v1.0/groups/{group-id}/members
エンドポイントがどこかや形式はGraph Explorerを使って確認します。
省略しますが、グループIDはAzure ADにログインし「すべてのグループ」一覧に表示されているIDを事前に取得しています。curl -H GET 'https://graph.microsoft.com/v1.0/groups/グループID/members'\ -H 'Content-Type: application/json;charset=utf-8'\ -H 'Authorization: Bearer アクセストークン'結果のスクリーンショットは省略してますが、JSON形式で値が返ってくるのがわかります。
このHTTPの処理をJavaで書くと
今回はJava8で記述しています。
またエラー処理は省略してます。ネットワークがプロキシ配下の場合はJava環境変数でプロキシ設定を行ってください。App.javaimport java.io.*; import java.net.*; //標準パッケージと言いつつここだけjavax追加ライブラリを使いました。 //ユニコードエスケープされた文字もデコードしてくれます //Jarファイルはこちら //https://repo1.maven.org/maven2/org/glassfish/javax.json/1.1.4/javax.json-1.1.4.jar import javax.json.*; public class App { public static void main(String[] args) throws Exception { String tenant_id = "テナントID"; String client_id = "クライアントID"; String client_secret = "クライアントシークレット"; String group_id = "グループID"; HttpURLConnection conn = null; InputStream input = null; HttpURLConnection conn2 = null; InputStream input2 = null; try { //① //Oauth2でアクセストークンを取得 //Java標準のHTTPリクエスト処理 URL url = new URL("https://login.microsoftonline.com/" + tenant_id + "/oauth2/v2.0/token"); conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(5000);// 接続にかかる時間 ミリ秒 conn.setReadTimeout(5000);// データの読み込みにかかる時間 ミリ秒 conn.setRequestMethod("POST");// HTTPメソッド conn.setUseCaches(false);// キャッシュ利用 conn.setDoOutput(true);// リクエストのボディの送信を許可(GETのときはfalse,POSTのときはtrueにする) conn.setDoInput(true);// レスポンスのボディの受信を許可 conn.addRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"); String data = //ポストするデータ "client_id=" + client_id + "&scope=https%3A%2F%2Fgraph.microsoft.com%2F.default" //java.net.URLEncoderでエスケープしてもよい + "&client_secret=" + client_secret + "&grant_type=client_credentials"; conn.getOutputStream().write(data.getBytes("utf-8")); conn.getOutputStream().close(); // send //結果を受け取ってJsonオブジェクトに変換 //JSONライブラリを使わない場合は戻ってきたテキストを自力でパースするだけです //Curlで取得していた文字列と同じようなものが返ってきています int code = conn.getResponseCode(); input = (code == 200 ? conn.getInputStream() : conn.getErrorStream()); JsonReader jsonReader = Json.createReader(new BufferedReader(new InputStreamReader(input, "utf-8"))); JsonObject json = jsonReader.readObject(); jsonReader.close(); conn.disconnect(); //アクセストークンが取り出せました! String access_token = json.getString("access_token"); //② //次はMicrosoft Graph APIにグループメンバーを問い合わせ URL url2 = new URL("https://graph.microsoft.com/v1.0/groups/" + group_id + "/members"); conn2 = (HttpURLConnection) url2.openConnection(); conn2.setConnectTimeout(5000);// 接続にかかる時間 ミリ秒 conn2.setReadTimeout(5000);// データの読み込みにかかる時間 ミリ秒 conn2.setRequestMethod("GET"); conn2.setUseCaches(false);// キャッシュ利用 conn2.setDoOutput(false);// リクエストのボディの送信を許可(GETのときはfalse,POSTのときはtrueにする) conn2.setDoInput(true);// レスポンスのボディの受信を許可 conn2.setRequestProperty("Authorization", "Bearer " + access_token); //取得したアクセストークン conn2.setRequestProperty("Accept", "application/json"); //これ重要です!!! ハマったポイント conn2.connect(); int code2 = conn2.getResponseCode(); input2 = (code2 == 200 ? conn2.getInputStream() : conn2.getErrorStream()); JsonReader jsonReader2 = Json.createReader(new BufferedReader(new InputStreamReader(input2, "utf-8"))); JsonStructure json2 = jsonReader2.read(); jsonReader2.close(); conn2.disconnect(); JsonArray members = json2.asJsonObject().getJsonArray("value"); //グループのメンバーが取れました! System.out.println(members); } catch(Error e) { e.printStackTrace(); } finally { if(input != null) try { input.close(); } catch(Exception e){} if(conn != null) try { conn.disconnect(); } catch(Exception e){} if(input2 != null) try { input2.close(); } catch(Exception e){} if(conn2 != null) try { conn2.disconnect(); } catch(Exception e){} } } }まとめ
SDKを使わなくても単純な取得は可能で、複数の追加Jarファイルがいらないのと、サーバとの手続きを隠蔽化されていないので本質を掴むことはできます。
一方、SDKを使えばトークン有効期限切れや、エンドポイントのURLを意識することなくコーディングに集中でき、また、Graph Explorerがコードスニペットを出力してくれるので生産性は上がるのではないかと思います。