- 投稿日:2020-12-01T23:28:21+09:00
Javaの文字列入門
今回はJavaの文字列についての扱いを簡単にまとめて見ます。
1 Javaでの文字列変数の定義方法
Javaでは文字列は標準クラス(Javaの言語機構に標準で含まれているクラス)のStringを使います。
String str = new String("Hello world");こんな感じです。
あるいは、下記のような書き方でも宣言できます。String str = "Hello world";ただし、newを使うやり方だと無駄なインスタンスを使うことになってしまうので、通常はString str = "Hello world";のようにします。
2 文字列を扱う便利なメソッド
文字列の扱いは、文字列用に用意されたStringクラスのメソッドを使うことで便利になります。
// length 文字列の長さを取得 String str1 = "apple"; int strlen = str1.length(); // 5 // substring 文字列の部分文字列を取得 // 1番目の引数:始まりの位置(0から始まる) // 2番目の引数:終わりの位置(省略すると最後まで) String str2 = "orange"; String str3 = str2.substring(2, 4); // ang String str4 = str2.substring(0, 3); // oran String str5 = str2.substring(3); // nge // indexOf 指定された文字列が最初に現れる位置を返す(0が起点) // 存在しない場合は-1が返される String str6 = "banana"; int index1 = str6.indexOf("nan"); // 2 int index2 = str6.indexOf("non"); // -1他にも文字列を扱うメソッドはたくさんあるので、詳しくは公式ドキュメント(Java8)を見てください。
3 文字列の比較方法
文字列の比較はequalsというメソッドを用います。
String str7 = "Tokyo"; String str8 = "Tokyo"; String str9 = "Osaka"; boolean check1 = str7.equals(str8); // true boolean check2 = str7.equals(str9); // falseこんな感じです。イコール(=)で比較するとメモリ領域を比較することになるので、必ずしも期待した結果にならないので気をつけてください。
4 文字列の結合
文字列の結合はプラス(+)を使います。
String str10 = "Super"; String str11 = "Star"; String str12 = str10 + str11; // "SuperStar"こんな感じです。ただし、何回も繰り返すとパフォーマンスが悪くなるので、このような場合はStringBuilderクラスを使って以下のようにします。
StringBuilder builder = new StringBuilder(); List<String> data = getData(); // getDataは文字列のリストを取得するメソッドとします int dataSize = data.size(); for (int i = 0; i < dataSize; i++) { builder.append(data.get(i)); } return builder.toString();終わりに
こんな感じで、いくつか気をつけるところがあったりしますが、うまく文字列処理ができるようになりたいものです。
文字列処理は正規表現を使うともっと複雑なことができたりしますが、それについては別の機会に紹介します。この記事はこちらのブログの内容を転記したものです。
- 投稿日:2020-12-01T21:54:38+09:00
ArchUnit 実践:Layered Architecture のアーキテクチャテスト
// 実行環境 * AdoptOpenJDK 11.0.9.1+1 * JUnit 5.7.0 * ArchUnit 0.14.1レイヤーの依存関係
Java プロジェクトのパッケージ構成
アーキテクチャテストの実装
package com.example; import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.archunit.core.importer.ClassFileImporter; import com.tngtech.archunit.core.importer.ImportOption; import org.junit.jupiter.api.Test; import static com.tngtech.archunit.library.Architectures.layeredArchitecture; class ArchitectureTest { // 検査対象のクラス private static final JavaClasses CLASSES = new ClassFileImporter() .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS) .importPackages("com.example"); @Test void レイヤードアーキテクチャのアーキテクチャテスト() { layeredArchitecture() // presentation パッケージを UI 層として定義 .layer("ui").definedBy("com.example.presentation..") // application パッケージをアプリケーション層として定義 .layer("app").definedBy("com.example.application..") // domain パッケージをドメイン層として定義 .layer("domain").definedBy("com.example.domain..") // infrastructure パッケージをインフラストラクチャ層として定義 .layer("infra").definedBy("com.example.infrastructure..") // UI 層はどの層からも依存されない .whereLayer("ui").mayNotBeAccessedByAnyLayer() // アプリケーション層は UI 層からのみ依存される .whereLayer("app").mayOnlyBeAccessedByLayers("ui") // ドメイン層は UI 層、アプリケーション層からのみ依存される .whereLayer("domain").mayOnlyBeAccessedByLayers("ui", "app") // インフラストラクチャ層は UI 層、アプリケーション層、ドメイン層から依存される .whereLayer("infra").mayOnlyBeAccessedByLayers("ui", "app", "domain") .check(CLASSES); } }アーキテクチャテストの実行例(テスト失敗例)
アプリケーション層の Service クラスが、プレゼンテーション層の Helper クラスに依存してしまっている、というアーキテクチャ違反を検知した想定でのテスト失敗例。
$ ./gradlew clean check > Task :test ArchitectureTest > レイヤーアーキテクチャ() FAILED java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule 'Layered architecture consisting of layer 'ui' ('com.example.presentation..') layer 'app' ('com.example.application..') layer 'domain' ('com.example.domain..') layer 'infra' ('com.example.infrastructure..') where layer 'ui' may not be accessed by any layer where layer 'app' may only be accessed by layers ['ui'] where layer 'domain' may only be accessed by layers ['ui', 'app'] where layer 'infra' may only be accessed by layers ['ui', 'app', 'domain']' was violated (2 times): Constructor <com.example.application.employee.EmployeeRegisterService.<init>(com.example.presentation.JsonRenderHelper)> has parameter of type <com.example.presentation.JsonRenderHelper> in (EmployeeRegisterService.java:0) Field <com.example.application.employee.EmployeeRegisterService.jsonRenderHelper> has type <com.example.presentation.JsonRenderHelper> in (EmployeeRegisterService.java:0) at com.tngtech.archunit.lang.ArchRule$Assertions.assertNoViolation(ArchRule.java:94) at com.tngtech.archunit.lang.ArchRule$Assertions.check(ArchRule.java:82) at com.tngtech.archunit.library.Architectures$LayeredArchitecture.check(Architectures.java:267) at com.example.ArchitectureTest.レイヤーアーキテクチャ(ArchitectureTest.java:69) 1 test completed, 1 failed > Task :test FAILED
- 投稿日:2020-12-01T21:37:43+09:00
【Java】チェックボックスが選択されているかどうかを取得する
プログラミング勉強日記
2020年12月1日
チェックボックスはクリックされるごとに選択状態と非選択状態になっている。その状態を取得する方法について紹介する。選択しているかどうか取得する
チェックボックスが選択されているかどうかを取得するためにはJCheckBoxクラスの親クラスであるAbstractButtonクラスで用意されているisSelectedメソッドを使う。
isSelectedメソッドは、ボタンが選択されている場合はtrueを、されていない場合はfalseを返す。if ([チェックボックス名].isSelected()) { // チェックボックスが選択されている場合 } else { // チェックボックスが選択されていない場合 }メモ
isSelectedメソッドのように
is
で始まるメソッドは答えが真が偽になるので、if文の条件式に利用することができる。参考文献
インタフェースButtonModel
チェックボックス(JCheckBox)の使い方
チェックボックスが選択か非選択かを取得する
- 投稿日:2020-12-01T21:33:31+09:00
【Java】例外処理メモ(try, catch, finally, throw, throws)
例外処理
「想定外の動作が起こったときの対応を、あらかじめ指定しておくこと」
【説明】 例外とは
例外の発生する原因として、例えば
- ファイルの終了検出
- エラーの発生
- 0除算の実行
といったなど、通常ではない特別の状態のときに発生します
例外はランタイムシステムが投げる場合もありますし、 自分で例外オブジェクトを作って投げる (throw する) こともできます。
<!-- 例外処理とは -->【説明】 例外処理とは
例外が起こったときの対応を、あらかじめ指定しておくことを例外処理といいます。
通常の条件分岐(if文や case文) をつかようより 例外処理を使うことによって、プログラムの構造がシンプルでわかりやすものになる。スタックトレース (stack traceback)
スタックトレースとは、実行中のコンピュータプログラムにエラーが発生した際に、直前に実行していた関数やメソッドなどの履歴を表示すること。
例外処理を行う方法
例外処理は以下の2つの方法があります。
1. 自分で例外を処理する
自分で例外を処理する場合は、try – catch – finally構文を使います。
finallyブロックは必要に応じて記述しましょう。
例外が発生する可能性のあるメソッドをtryブロックで囲み、catchブロックで処理したい例外を捕まえます。finallyブロックは、例外が発生してもしなくても共通で行う処理を書きます。2. 自分では処理せず、例外処理を呼び出し元のクラスにスルーする
「例外処理を自分自身で行うのではなく、呼び出し元のメソッドでやってもらう」こともできます。
メソッドのシグニチャに以下の構文で記述することで呼び出し元に例外処理を丸投げできます。throws 例外クラス名例外が投げられたら、Java のランタイムシステムはその例外の種類に応じた 例外ハンドラ を探します。 例外ハンドラが見つかれば、そのハンドラが実行されます。
例外ハンドラが見つからない場合は、Java ランタイムはデフォルトの例外ハンドラを実行します。 デフォルトのハンドラではスタックトレースを出力して、プログラムを終了します。
例外の種類
例外には大きく分けて、非検査例外と検査例外に分かれます。
そして、非検査例外の中にも「実行時例外」と「エラー」があります。
主にその3種類があり、それぞれ内容と処理の仕方が異なります。非チェック例外 (非検査例外, unchecked exception)
非検査例外は、例外処理を記述したかどうかをコンパイラが検査しない例外を指します。
非チェック例外には、次の2種類があります。
- 例外処理を記述してもプログラムレベルでは回復できないような重大な例外
- 頻繁に発生するので、いちいちtry/catchするのが効率的でない例外
1の場合はその例外を処理してどうこうする、というのはあまり期待されていなくて、速やかにプログラムをシャットダウンするべきというものです。
2のの場合は、たとえば「宣言した範囲を越えて配列をアクセスした」ときに
ArrayIndexOutOfBoundsException
という例外が発行されますが、この例外の発生に備えようとすると、配列を扱うすべての処理をtryブロックで囲まねばならず、これはそもそも「プログラムが間違ってる」ので例外処理などせずに、さっさと実行を止め手書き直したほうがいいよね、って話です。Exceptionのサブクラスでも、RuntimeExceptionを継承していれば非検査例外として扱われ、try-catchなどの例外処理を記載してももちろん問題ないが、基本的に「例外処理は不要」になる。
非チェック例外の中でも「実行事例外」と「エラー」の2タイプに分かれます。
・ 実行時例外(RuntimeException)
null の参照や配列のインデックス外アクセスなど、プログラムの不具合に起因する例外。
キャッチすることも一応できるが、本来は不具合のほうを直すべき。・ エラー
メモリ不足や「あるファイルを開いて、文字を読み込む」ということを想定しているときに、 ファイルが存在しないとか、ファイルのアクセス権の設定がでていていなくて、読み込めないとか、そういう場合の例外。
ちなみに、エラーは必ずしも例外で検出しないといけないことはなくて、状況によっては普通に関数の戻り値がこれこれだったら、エラーとする、という風にもできます。【代表的な非検査例外】
例外の名前 概要 NullPointerException オブジェクトが必要な場合に null に対して操作を試みるなどした場合に発生する例外。(通称ヌルポ) IndexOutOfBoundsException 配列のインデックスなどのインデックスについて、有効な範囲を超えてアクセスした場合に発生する例外。 チェック例外 (検査例外, checked exception)
検査例外とは、例外処理を記述したかをコンパイラが検査する例外を指します。
チェック例外というのは、ファイルを開こうとしたらそのファイルがなかったとか、プログラムの不具合とは関係なく、 状況によっては発生しうる状況とされるものです。検査例外のクラスであるExceptionを継承している例外クラスは、try-catchやthrows句で宣言するなどの「例外処理が必須」になる。
ここで言う検査とは、catchやthrowのこと。検査例外を検査していない場合はコンパイルエラーになる。【代表的な検査例外】
名前 概要 IOException I/O (入出力) 関連のなんらかの例外が発生したことを示す汎用の例外クラス。 ParseException パース (文字解析) 中になんらかの例外が発生したことを示す例外クラス。 SQLException データベース関連の例外が発生したことを示すための例外クラス。 SOAPException SOAP 関連 (いわゆる XML Web サービス) に関連してなんらかの問題が発生したことを示す例外クラス。 TimeoutException ブロッキング操作 (処理の結果を待つ待ち状態) がタイムアウトによって失敗したことを示す例外クラス。 【例外処理の基本形】 try-catch ブロック
以下が例外処理の基本的な構文です。try、catch、finallyはこの順序で連続して記述する必要があります。 tryとcatchだけ、またはtryとfinallyだけでもかまいません。
try { // 例外が発生する可能性のある処理 } catch (例外クラスA インスタンス名A) { // 対策処理A } catch (例外クラスB インスタンス名B) { // 対策処理B } finally { // 例外が発生するしないに関わらず必ず実行する後処理 }try(例外の発生する可能性のある処理を記述)
Javaの標準APIにおいては、予め例外処理の発生する可能性のあるメソッドに対しては、
throws ◯◯Exception と定義されています。catch(発生した例外をキャッチ)
複数記述することが可能です。
発生する例外によって対策処理を分ける場合などに有効です。
catch では、発生した例外がどんな内容の例外であるかをコンソールへログ出力することが多いです。
通常の処理の道程に加え、例外もログ出力することで保守体制を整えます。その他には、例外の情報を一時的に保持しておいて、
finally へ処理が移行した際に、その保持していた内容を使用して何かの処理を実行する。
といった感じです。finally(例外発生の有無に関わらず実行される)
finally に関しては、 必須ではありません。
しかし、例外の発生有無に関わらず確実に処理を実行するので、
〆となる終了処理を追加したい場合 は必要に応じて実装すればよい、といった感じです。【サンプル.java】
下記は例外内容によってそれぞれ違うメッセージを出力する例外処理です、
今回はわざと null を触り、ランタイムに NullPointerException 例外を投げさせています。public class Main { public static void main(String[] args) { try { String s = null; // 値に nullを格納 System.out.println(s.toUpperCase()); } catch (IndexOutOfBoundsException e) { System.out.println("範囲外の例外です。");// 有効な範囲を超えてアクセスした場合 } catch (NullPointerException e) { System.out.println("ヌルポです。"); // 値がnullだった場合 System.out.println(e); } catch (Exception e) { System.out.println("それ以外の例外です。");//不明の例外 } } }【実行結果】
ヌルポです。 java.lang.NullPointerExceptionthrowステートメント / throwsステートメント
どちらも意図的に例外処理を発生させる機能 です。
「throw」を使用すると任意のタイミングで例外を発生させて、例外処理を行うことができます。
「例外処理(tryブロック、catchブロック、finallyブロック)」、または、 throws を記述し対応する必要があります。throwステートメント
「throw」を使用すると任意のタイミングで例外を発生させて、例外処理を行うことができます。
throwステートメントは以下のように記述します。throw new 例外クラス例えばある変数の値が指定した範囲にないときに必ずエラーにしたいとします。
上記の例では様にしています。
【サンプル】 iが負の数の場合に例外を発生させる
この場合はExceptionクラスを使用しています。例外の発生後はこのthrowステートメントに対応するcatch節に処理が移ります。
try{ if(i < 0){ throw new Exception("iが指定範囲を超えています"); } } catch(Exception e){ e.printStackTrace();//throwの後ここに処理が移る }throws
通常、throw されている場合は、 try-catch により、例外を補足し対応しますが、数のメソッドを使用した処理を書く際、例外処理を各メソッドの中ではなく呼び出し元でまとめて行いたい場合があります。
そういった場合、「例外を投げる可能性があるメソッド」に対して宣言時に 「throws」を加えることで例外処理を呼び出し元に移譲することができます。
<!-- 記述が少なくなる -->
例外処理は突き詰めるとかなりの量を記述する必要があります。
そのため、必要以上に捕捉するような不要な例外処理は極力避けた可読性の高いコードとなる方が好まれます。例外がthrowされるメソッドより 呼び出し元処理で例外を捕捉することで、処理が簡潔になり、同じような例外処理が発生するメソッドが複数存在した場合、例外処理が必要な箇所を絞り込んだ対応ができます。
【サンプル.java】
package about_exception; public class Main { private static final String PASSWORD = "password"; public static void main(String args[]) { String password = PASSWORD; String inputPassword = "passsswooorrddd";// 入力されたパスワード try { if (!password.equals(PASSWORD)) { throw new Exception("パスワードが間違っています"); } System.out.println("入力された " + inputPassword + " はパスワードと一致しません");// 例外でthrowされた以降の処理は実行されない } catch (Exception e) { System.out.println("パスワードが間違っています"); System.out.println(e); } } }【実行結果】
入力された passsswooorrddd はパスワードと一致しません【まとめ】
参考文献・記事
- 投稿日:2020-12-01T21:29:03+09:00
UDPのマルチキャストをDockerコンテナ・ネットワーク上で動かしてみた
※半年ほど前に書いた記事をQiitaに持ってきました
JavaのMulticastSocketを使って、UDPのマルチキャストを送受信するクライアントを作り、Dockerコンテナ・ネットワーク上で動かしてみようと思います。
今回書いたコードはこちらのリポジトリに置いてあります。
UDPのマルチキャストとは
コネクションの確立をせず、パケットを一方的に送りつけることが特徴なUDPですが、以下のような送信方式があります。
- ユニキャスト
- ブロードキャスト
- マルチキャスト
ユニキャストは、特定の一台のホストに対して送信します。ブロードキャストは、あるネットワークに属する全てのホストに対して送信します。マルチキャストは、あるネットワークに属する特定のホスト(一台でも複数台でもよい)に対して送信します。
マルチキャストにおいて、送信側はマルチキャストアドレスを宛先アドレスとして指定してデータを送信します。マルチキャストアドレスはIPアドレスクラスのうち、クラスDのIPアドレスです。受信側はこのマルチキャストアドレスにJoinすることでデータを受け取ることができるようになります。
また、ユニキャストを複数台に行うことに対して、マルチキャストを行うことのメリットは以下のようになります。
- 宛先のIPアドレスを知らなくても、マルチキャストアドレスだけ分かっていれば、Joinしているホスト全員に対して送信できる
- 一つのパケットで複数台に向けて送信できるので通信効率が良く、送信元の負荷が低い
Dockerコンテナ・ネットワークとは
Dockerコンテナ・ネットワークとは、Dockerコンテナが属するネットワークのことです。
docker network ls
コマンドを実行すると一覧が表示されます。今回はマルチキャストを検証するために、同一ネットワーク内にコンテナを立てなければなりません。しかし、実は難しいことをする必要はなく、docker-composeを使えば自動でネットワークを作成してくれるます。なので、今回はdocker-composeを使って複数のクライアントを動かそうと思います。簡単なチャットアプリを作ってみる
それではUDPのマルチキャストを送受信するクライアントとして、今回は簡単なチャットアプリを作ってみます。一つのクライアントで送信と受信の両方を行うようにしてみます。
ソースコード
MulticastClient.javaというファイルに以下を記述します。
import java.io.IOException; import java.net.DatagramPacket; import java.net.InetAddress; import java.net.MulticastSocket; import java.util.Scanner; class Sender extends Thread { private final int toPort; private final String mcastAddrName; public Sender(int toPort, String mcastAddress) { this.toPort = toPort; this.mcastAddrName = mcastAddress; } public void run() { Scanner scan = null; MulticastSocket socket = null; try { InetAddress mcastAddress = InetAddress.getByName(mcastAddrName); scan = new Scanner(System.in); socket = new MulticastSocket(); String message; while ( (message = scan.nextLine()).length() > 0 ) { byte[] bytes = message.getBytes(); DatagramPacket packet = new DatagramPacket(bytes, bytes.length, mcastAddress, toPort); socket.send(packet); } } catch (IOException e) { e.printStackTrace(); } finally { if (scan != null) scan.close(); if (socket != null) socket.close(); } } } class Receiver extends Thread { private final int fromPort; private final String mcastAddrName; private static final int PACKET_SIZE = 1024; public Receiver(int fromPort, String mcastAddrName) { this.fromPort = fromPort; this.mcastAddrName = mcastAddrName; } public void run() { MulticastSocket socket = null; byte[] buf = new byte[PACKET_SIZE]; DatagramPacket packet = new DatagramPacket(buf, buf.length); try { socket = new MulticastSocket(fromPort); InetAddress mcastAddress = InetAddress.getByName(mcastAddrName); socket.joinGroup(mcastAddress); while (true) { socket.receive(packet); String message = new String(buf, 0, packet.getLength()); System.out.println(packet.getSocketAddress() + " : " + message); } } catch (IOException e) { e.printStackTrace(); } finally { if (socket != null) { socket.close(); } } } } public class MulticastClient { public static final int PORT = 3000; public static final String MCAST_ADDR = "224.0.1.1"; public static void main(String args[]) { Receiver receiver = new Receiver(PORT, MCAST_ADDR); Sender sender = new Sender(PORT, MCAST_ADDR); receiver.start(); sender.start(); } }ソースコードの説明
上記のコードには以下の3つのクラスが含まれています。
- Senderクラス
- Receiverクラス
- MulticastClientクラス
それぞれについて説明します。
Senderクラス
Senderクラスは、データを送信するためのクラスです。受信と並列して動かすためにThreadクラスを継承して、runメソッドをオーバーライドしています。
フィールド
private final int toPort; private final String mcastAddrName;toPortは送信先のポート番号(受信側が受信するポート番号)です。mcastAddrNameは送信先のマルチキャストアドレスの名前です。
コンストラクタ
public Sender(int toPort, String mcastAddress) { this.toPort = toPort; this.mcastAddrName = mcastAddress; }変数を初期化します。
runメソッド
public void run() { Scanner scan = null; MulticastSocket socket = null; try { InetAddress mcastAddress = InetAddress.getByName(mcastAddrName); scan = new Scanner(System.in); socket = new MulticastSocket(); String message; while ( (message = scan.nextLine()).length() > 0 ) { byte[] bytes = message.getBytes(); DatagramPacket packet = new DatagramPacket(bytes, bytes.length, mcastAddress, toPort); socket.send(packet); } } catch (IOException e) { e.printStackTrace(); } finally { if (scan != null) scan.close(); if (socket != null) socket.close(); } }whileループ内だけ説明します。
message = scan.nextLine()
では標準入力から一行読み取って、String message
に入れます。byte[] bytes = message.getBytes()
では、message
をバイト配列に変換します。DatagramPacket packet = new DatagramPacket(bytes, bytes.length, mcastAddress, toPort)
では、上記のバイト配列、宛先のマルチキャストアドレス、宛先のポート番号を指定し、DatagramPacket
を生成します。socket.send(packet)
でパケットを送信します。Receiverクラス
Receiverクラスは、データを受信するためのクラスです。送信と並列で動かすためにThreadクラスを継承して、runメソッドをオーバーライドしています。
フィールド
private final int fromPort; private final String mcastAddrName; private static final int PACKET_SIZE = 1024;fromPortはパケットを受け取るポート番号です。mcastAddrNameはJoinするマルチキャストアドレスです。PACKET_SIZEは受け取るパケットのサイズです。
コンストラクタ
public Receiver(int fromPort, String mcastAddrName) { this.fromPort = fromPort; this.mcastAddrName = mcastAddrName; }変数を初期化します。
runメソッド
public void run() { MulticastSocket socket = null; byte[] buf = new byte[PACKET_SIZE]; DatagramPacket packet = new DatagramPacket(buf, buf.length); try { socket = new MulticastSocket(fromPort); InetAddress mcastAddress = InetAddress.getByName(mcastAddrName); socket.joinGroup(mcastAddress); while (true) { socket.receive(packet); String message = new String(buf, 0, packet.getLength()); System.out.println(packet.getSocketAddress() + " : " + message); } } catch (IOException e) { e.printStackTrace(); } finally { if (socket != null) { socket.close(); } } }whileループの中だけ説明します。
socket.receive(packet)
でパケットの受信を待機します。パケットを受信したらString message = new String(buf, 0, packet.getLength())
によりバイト配列をStringに変換します。そしてSystem.out.println(packet.getSocketAddress() + " : " + message)
により、標準出力に出力します。MulticastClientクラス
MulticastClientクラスでは、mainメソッドを定義しています。
フィールド
public static final int PORT = 3000; public static final String MCAST_ADDR = "224.0.1.1";SenderクラスとReceiverクラスに渡すポート番号とマルチキャストアドレスを定義しています。
mainメソッド
public static void main(String args[]) { Receiver receiver = new Receiver(PORT, MCAST_ADDR); Sender sender = new Sender(PORT, MCAST_ADDR); receiver.start(); sender.start(); }ReceiverクラスとSenderクラスをインスタンス化し、Threadをstartさせています。
検証環境を準備する
上記のチャットアプリを動かす環境をDockerで構築します。同一ネットワーク内に複数のクライアントを立ち上げるためにdocker-composeファイルを定義します。
Dockerfile
Dockerfileは以下のようになります。
FROM openjdk:14 COPY . /usr/src/myapp WORKDIR /usr/src/myapp RUN javac MulticastClient.java CMD ["/bin/bash"]Imageはopenjdk:14を使います。
javac MulticastClient.java
でコンパイルした後、/bin/bash
でbashを開きます。実行するときはdocker container exec -it [CONTAINER ID] bash
で中に入ってから、java MulticastClient
を叩きます。本当は
CMD ["/bin/bash"]
のところをCMD ["java", "MulticastClient"]
として実行しておき、containerの中に入りたかったのですが、やり方がわかりませんでした。docker-compose
docker-compose.ymlは以下のようになります。
version: '3' services: client1: build: context: . dockerfile: Dockerfile tty: true client2: build: context: . dockerfile: Dockerfile tty: true先ほどのDockerfileからImageをBuildします。
tty: true
を記述しておくとコンテナが起動したままになります。client1と同じように、client2, client3...とコンテナを作成します。これらのコンテナは
docker-compose up
を実行すると自動で作成されるnetworkに属することになります。プライベートIPアドレスは自動で割り振られます。検証してみる
それではクライアントを複数立ち上げてマルチキャストが動くか検証してみます。
コンテナを起動させるために以下のコマンドを実行します。
docker-compose up --build
コンテナに入るには以下のコマンドを実行します。CONTAINER IDは
docker container ls
で調べることができます。docker container exec -it [CONTAINER ID] bashアプリケーションを実行するにはコンテナ内で以下のコマンドを実行します。
java MulticastClient3つコンテナを作って、それぞれクライアントを実行してみたのが以下の画像です。ターミナルを横に3つ並べています。
左のターミナルでHello!と入力すると、他のターミナルにもメッセージが表示されました!左のターミナルで実行しているコンテナのプライベートIPアドレスは
172.29.0.3
だということも分かりますね!真ん中のターミナルでHello!Hello!と入力しても他のターミナルに表示されます。真ん中は
172.29.0.2
ですね。右のターミナルも同じく動きます。
感想
今回はJavaのMulticastSocketを使って、UDPのマルチキャストを送受信するチャットクライアントを作り、Dockerコンテナ・ネットワーク上で動かしてみました。UDPについて調べる過程でかなり勉強できたので良かったです。また、Dockerの動かし方についても学べたので一石二鳥でした!
参考
https://www.techscore.com/tech/Java/JavaSE/Network/5/
https://milestone-of-se.nesuke.com/nw-advanced/multicast/multicast-summary/
- 投稿日:2020-12-01T21:12:53+09:00
Javaの例外の話(チェック例外と非チェック例外)
はじめに
社内でJava関連の講師として話すことをやめて少し経ちますが、当時例外の話とかをしていて受講生にポカーンとされていたなぁ、とふと思い出したので記録としてまとめてみたのがこの内容です。
初心者向けにしていますが、Javaの文法レベルは理解している人がターゲットになります。
Javaの例外処理とは
基本的には例外はtry 内で throwして、ちゃんとcatchしましょうね、と習うと思います。
try { // さまざまな処理 throw new Exception("エラーです!"); } catch (Exception e) { e.printStackTrace(); }クラスやメソッドをまたがって例外を伝えるには
1つのメソッド内であれば、分かりやすいのですが、メソッドを跨った場合に迷いという名の設計のしどころが出てきます。
public static void main(String[] args) { try { call(); } catch (Exception e) { e.printStackTrace(); } } public static void call(){ throw new Exception("メソッド内からのエラーです!"); }こんな風に書くと、callメソッドの実行行でコンパイルエラーになります。何故だか分かりますでしょうか?
call()メソッド側を
public static void call() throws Exception{ throw new Exception("メソッド内からのエラーです!"); }もしくは
public static void call() { throw new RuntimeException("メソッド内からのエラーです!"); }とするとコンパイルエラーが無くなります。
前者は、このメソッドからExceptionという例外がthrowされる可能性があるよ、ということをメソッドを呼ぶ人に対して
正しく定義するという対処法です。後者は、RuntimeExceptionというExceptionクラスの継承先クラスですが、Java言語仕様で定められたクラスであり
非チェック例外として、throws節で非チェック例外を宣言する必要がないと定められています。
この仕様はRuntimeExceptionを継承した例外クラスにも継承されます。非チェック例外と比して、先ほどのExceptionクラスについては、チェック例外と呼びます。
例外をthrowしますよ、ということをメソッドを呼ぶ側と呼ばれる側であらかじめ取り決めておくことを強制することができます。よって、
public static void main(String[] args) { call(); } public static void call() throws Exception{ throw new Exception("メソッド内からのエラーです!"); }みたいな書き方をすると、mainメソッド側で、callメソッド読んでいるけどtry-catchしていないよ、とコンパイルエラーがでます。
ちなみに
public static void main(String[] args) { try { call(); } catch (Exception e) { e.printStackTrace(); } } public static void call() throws RuntimeException{ throw new RuntimeException("メソッド内からのエラーです!"); }のように、非チェック例外をthrows節に書くことも可能です。
他の例外系のJava標準クラス
ざっくり全体をクラス図で示すと以下のような形になります。
Throwlableクラスが例外処理系のクラスの大元です。名前の通り、throwできるクラスを意味します。
そこから2つに分かれます。ExceptionとErrorです。
Exceptionは前述の通りです。初登場のErrorですが、エラーを示しますのでこれが起きたらメモリ内の状態異常などが発生している可能性もあるので、Javaプロセスごと再起動したほうが良いレベルです。アプリケーション内でやれることとしては何が起きているのか適切にエラーの内容をログに出力しておくこと、ぐらいです。(ログ出力時のIOErrorであれば、それもままならない状況かもしれませんが、やれることはやりましょう)
例外設計について
さて、話を戻しますと、例外をthrowする際に(特に独自例外クラスを作る場合に)、チェック例外としてthrowしてメソッドの呼び元に例外処理を強制させるのか、非チェック例外にして例外処理をするかどうかを任せるのか?ということを論じます。
前者の場合は、品質は高くなりそうですが、コーディング量は増えます。クラスが増えてきて構造が深くなると、例外をcatchして上位にthrowするだけのコードが量産されてしまいそうです。
(昔ながらの大規模システムでは、こちらが多いような感触はあります)後者の場合は、コーディング量は適切になりそうですが、適切な例外処理がされているか、しないことを意思を持ってやっているのかはコードレビューや結合テストなどで確認する必要が出てきます。
で、落とし所ですが、非チェック例外を扱いつつ、メソッド定義のthrows節に書くという方法がよいのではないか、と今時点では考えてます。(システム特性、開発体制によるので、あくまで個人意見として受け取ってください)
メソッド呼ぶ側に例外throwするかもしれないよ、ということを示しつつ、強制はされないためコード量が無駄に増えないためです。public static void main(String[] args) { try { call(); } catch (Exception e) { e.printStackTrace(); } } public static void call() throws RuntimeException{ throw new RuntimeException("メソッド内からのエラーです!"); }このパターンですね。
まとめ
Javaにおける例外処理と考え方について、簡単にまとめました。
ほんのさわりの部分だけですが、設計や実装の奥深さの入り口ぐらいは認識できたのではないでしょうか?もっとよい、モダンな考え方とかあれば教えてください。
ラムダ関数などFunction系での考え方については、あまり理解していないので、そのあたりも今後理解を深めていきたいです。
- 投稿日:2020-12-01T20:32:22+09:00
Flutter for webでFlashプロダクトを置き換えた話
はじめに
Flashのサポートが2020年12月31日で終わってしまいます。
私が所属する企業のアプリはマルチデバイス対応がウリで、Web版はFlashで作られていました。
もともとのチームは10名(パートナー含めると倍近く)くらいいましたが、今は私とパートナーさんの2名。
少ないリソースでやりきらないといけない。
というところで、白羽の矢が立ったのがFlutter for Webでした。どんなプロダクト?
電子書籍ビューアです。
よくある漫画ビューアのような難読化(パズルみたいに画像をバラバラにするもの)ではなく、暗号化と復号処理をするものです。これ以上は細かく言えませんが、セキュリティに少し配慮したものになってます。移行開始
オリジナルのFlashのコードは存在するものの、当時流行ってたオフショア開発だったのでコメントが壊滅的でした。日本語がよくわからないのはいいとして、英語のコメントもなし。幸い、Javaのサンプルがあったのでそちらを参考にしました。
ハマったポイント
バイナリデータの値
復号処理をビューア側でしないといけないのですが、そもそも転送されてきたデータがなにか違う。
ここでのハマりポイントは、DartのUint8List型とJavaのbyte[]型のintの範囲が異なっていたことでした。
なので、最初は復号処理がおかしいんじゃないかとロジックを見直して無駄に時間を浪費してしまいました。
具体的にはDartは0〜255、Javaは-127〜128となっています。なので、128までは問題ないのですが130となるとJava側では-2となり意味が分かりませんでした。
その他のデータ型に関しては下記URLを参考にして実装しました。https://flutter.dev/docs/development/platform-integration/platform-channels#codec
HTTPリクエスト
FlutterでHTTPリクエストを実行するにはdart:ioとdart:htmlがあって、前者はモバイルなどネイティブアプリから利用できます。後者はWebで利用できます。しかしどちらか一方しか使えないという制約があるので、ラッパーライブラリのhttp.dartを利用しました。
WebサーバにはContent-Type: x-www-form-urlencodedでリクエストをPOSTすればいいのであまり気にしていなかったのですが、JSONをPOSTするとエラーになってしまいました。
リクエストヘッダにapplication/jsonを設定したのにおかしいなと思って調べたところ、Web版はContent-Typeを設定することができないそうです。
仕方ないのでサーバ側の受け取りをx-www-form-unlencodedに実装を変更して対応しました。
同様のdio.dartでも上手く動かないのでdart:jsで自力で実装しないといけないのかな?
まだ未検証なのでそのうち試してみます。動かなかったコードimport 'package:http.dart' as http; Future<void> postData(var body) async { var headers = {'Content-Type': 'application/json'}; var response = await http.post('https:/domain/endpoint', headers=headers, body=body); }文字列定数が丸見え
DratからJavaScriptにトランスパイラされる為、文字列定数が丸見えになっていました。
URL等は見えてしまっても問題ないのですが、復号処理に使う値が見えるのは流石にまずいので、難読化することで対応しました。
パフォーマンスのこともあるのでそこまで複雑なことはしてませんが、パッと見ではわからなくなりました。やってよかったこと
テストファースト
ユニットテストを積み上げて手戻りがないように最初に動けたのがよかったです。
Javaでお世話になったmockitoがDartでもそのまま使えたのが大きいと感じています。
また、ユニットテストはxUnitなのでこちらも違和感なくテストを記述できました。
テストについては下記URLを読んで取り組めばいいと思います。プロジェクトにもよりますが、ユニットテストはやっといて損はないかと思いました。
https://flutter.dev/docs/testingUIとビジネスロジックの分離
既存のコードはMVCパターンで書かれてましたが、名ばかりMVCでした。
今回はMVCに分離することを頑張ったおかげで責任の範囲が限定されて見通しがよくなり、カオスになることを避けれたように思います。おわりに
FlutterのAdvent CalendarなのにほとんどDartの話になってしまいました。私の担当範囲が通信とかデータの保持、込み入った処理が中心だったためなので仕方ないなと思う事にします。
Flutterでは描画コストの見直し以外は特に困ったことはありませんでした。それくらいサクサクとUIを構築できました。
そのおかげでFlash版で提供していたプロダクトをひとまず置き換えることに成功したと言えるかなと思います。会社のことなので書けない事が多くなりましたが、同じように悩んでる人の助けになれば幸いです。
他にも思い出したらまた追記します。
- 投稿日:2020-12-01T19:43:20+09:00
AndroidStudioメソッドのパラメータを確認する方法
- 投稿日:2020-12-01T18:42:39+09:00
Kotlin どうでしょう
Kotlin コトリン
Kotlin は JET BRAINS社が開発したオブジェクト型指向言語。
2017年に Google がAndroid開発言語として正式サポート。
元々、Android開発環境である「Android Studio」は、JET BRAINS社の IntelliJ ベース。
開発環境の言語サポートが充実している!言語仕様について
Javaと相互運用可能!
JavaからKotlin のクラス使える。
逆に、KotlinからJavaのクラスも使用可能。
(Kotlin が JVM上で動作する言語のため。)JavaコードをAndroid Studio上で 貼り付けすると、
Kotlinコードに変換できる。
(ただし、完璧ではない。ビルドが通らないことも有。)・総評
記述が簡潔にできるので、積極的に使用したい。
Swiftの文法に近いため、
iOS - Android 開発者のスキルトランスファーの敷居は下がると思われる。
(設計の思想は違う。)Android開発には、Javaを知った上でのKotlin知識が必要なため、
Javaを捨てることはまだできない。
- 投稿日:2020-12-01T18:36:18+09:00
Android開発者向けオプションを使ったチート対策
PONOS Advent Calendar 2020 5日目の記事です。
はじめに
Androidチート対策の一つとして
開発者向けオプションを使用した対策を紹介しますこの記事の対象者
・アプリ開発者
・Android開発者開発者向けオプションが有効かの判定
チート端末では開発者向けオプションが有効になっていることが多いです。
アプリ側で開発者向けオプションが有効か無効か判定してみましょう。android.provider.Settings.Systemクラスを使用します。
システム環境設定関連の値を取得するなど便利な機能があります。
Settings.Secure.getInt()
メソッド
Settings.Global.DEVELOPMENT_SETTINGS_ENABLED
フィールド
を使用します。boolean isEnable = Settings.Secure.getInt(this.getContentResolver(), Settings.Global.DEVELOPMENT_SETTINGS_ENABLED , 0) == 1; if(isEnable){ //開発者向けオプション 有効時の処理 //例:タイトル画面でロックして、開発者向けオプションをOFFにするように促すポップアップ表示 }else{ //開発者向けオプション 無効時の処理 }メリット
アプリのセキュリティーレベルを上げることができる。
実装コストが軽いデメリット
主に開発者など日常的に開発者向けオプション有効にしているユーザーにとっては迷惑導入する場合
端末操作で開発者向けオプション有効にする方法は
「設定」アプリ内で「ビルド番号」を7回連続してタップ
と一般ユーザーはやらない操作だと思われるが、トレードオフが必要ですUSBデバッグモードの判定
フィールド名だけを変えた同様の方法でadbのチェックも可能です。
USB経由のadbが有効かどうか判定できます。boolean isAdb = Settings.Secure.getInt(this.getContentResolver(), Settings.Global.ADB_ENABLED , 0) == 1;チート対策としては有効ですが
こちらも導入する場合は、トレードオフが必要です。まとめ
今回紹介した方法は「小を捨てて大に就く」手法で完璧なものではありません。
他にも有効なチート対策があるので合わせて検討するのがお勧めです。
チート対策
・メモリシャッフル(メモリハック対応)
・root権限チェック
・不正端末名、不正エンジン名のチェック(エミュレーター対策)
・ソースコード難読化
・リソースなどの暗号化
・サーバー側でのバリデーションチェック
・サーバー通信時の内容を暗号化
・SafetyNetの導入
などなど今回の手法の最大のメリットは実装コストが軽いことなのですぐ実装して
・複数端末テストの結果をみて導入を検討する
・得られた情報をユーザーデータとして保存してデータ分析してから検討する
などのやり方がベターかなと考えております。明日は、@nissy_gpさんの記事です。
お楽しみに!!
- 投稿日:2020-12-01T17:42:09+09:00
初めての投稿
今回が初めての投稿です。
筑波大学情報学郡情報科学類、現在3年の学生です。得意な言語はPython, Java, C, Matlab, Rなどです。
今後、個人的な備忘録として、また共有したい知識についての記事を投稿していきたいと思います。
- 投稿日:2020-12-01T16:30:35+09:00
【Java】サーブレット(サーバサイドで動くJavaプログラム)
サーブレット
「WEBアプリを開発するために作られたプログラムの部品」
【説明】
サーブレットは、「Webサーバー(HTTPサーバー)上で、ウェブページなどを動的に生成したり、データ処理を行うためにサーバサイドのプログラム 」で、WEBアプリの中心を担う機能をもっています。
【メリット】
サーブレットはWEBアプリを開発する上で便利な特徴を持っています。
データを再利用(ライフサイクルの管理)
処理が始まってから終了するまでの一連の流れを管理する機能をもっていて、効率よく仕事をするようにできています。
通常は、一連の処理が完了したらそれまでに使ったデータを破棄してしまいますが、サーブレットは一連の処理が終わっても、データを保持して再利用できるようにします。
そのため、最初の1回目は処理に時間がかかるものの、それ以降データを再利用するので処理が早くなります。
WEBアプリのような頻繁に不特定多数の処理を行う場合は、データをできる限り共有して使いまわした方が効率が良くなります。マルチスレッドに対応(同時に複数の処理をする)
1つのプログラムを実行している間は、他のプログラムは実行中のプログラムが処理されるのを待つ必要があり、WEBアプリでは、複数のユーザーから頻繁に同時にアクセスがあるため、常に順番待ちになってしまい効率が悪くなってしまいます。
サーブレットは複数の処理を同時に行う「マルチスレッド」に対応しているので、複数のユーザーからのアクセスでも効率良く処理を行うことができます。プラットフォームに依存しない
サーブレットはJavaのプログラムから作られていて、Windows, Mac, LINUXなどどの環境でも動作することが可能です。
【特徴】
今までのJavaと大きな相違点は、「Javaのプログラムが、ブラウザ上(HTTPサーバ上)で動くこと」です。つまり、サーブレットはWebサーバ(HTTPサーバ)がなければ意味がありません。
【使用場面】
サーブレットは「WEBブラウザからの要求に応えたり、送られてきたデータを処理したりする役割」で「WEBページをはじめとし画面に関する処理」は行いません。その特性から他のプログラムと連携することで真価を発揮します。
JSP (HTML内にJavaのコードを埋め込む)
JSPは「動的にWEBページを生成する技術」でHTML内にJavaのコードを埋め込むことができます。
- サーブレットはWEBアプリ内部の処理
- JSPは主に外部の表示担当
と考えると良いかと思います。サーブレットとJSPが連携することで、
- 「特定のユーザー情報を画面に表示」する
- 「ログイン時に表示する画面の内容を変える」
といったような、いわゆる「動的なWEBページ」を作ることが可能です。
Tomcat (サーブレットを動かすソフト)
サーブレット単独ではプログラムを動作させることができません。
Tomcatはサーブレットを動かすエンジンの役割をしていて、必要に応じて命令を出してサーブレットを動かしてくれます。【まとめ】
JSP とサーブレットの関係についてようやく考えがまとまってきました。
近いうちになにかアプリを作ってみたいです。参考文献・記事
- 投稿日:2020-12-01T16:30:35+09:00
【Java】サーブレットとは
サーブレット
「WEBアプリを開発するために作られたプログラムの部品」
【説明】
サーブレットは、「Webサーバー(HTTPサーバー)上で、ウェブページなどを動的に生成したり、データ処理を行うためにサーバサイドのプログラム 」で、WEBアプリの中心を担う機能をもっています。
【メリット】
サーブレットはWEBアプリを開発する上で便利な特徴を持っています。
データを再利用(ライフサイクルの管理)
処理が始まってから終了するまでの一連の流れを管理する機能をもっていて、効率よく仕事をするようにできています。
通常は、一連の処理が完了したらそれまでに使ったデータを破棄してしまいますが、サーブレットは一連の処理が終わっても、データを保持して再利用できるようにします。
そのため、最初の1回目は処理に時間がかかるものの、それ以降データを再利用するので処理が早くなります。
WEBアプリのような頻繁に不特定多数の処理を行う場合は、データをできる限り共有して使いまわした方が効率が良くなります。マルチスレッドに対応(同時に複数の処理をする)
1つのプログラムを実行している間は、他のプログラムは実行中のプログラムが処理されるのを待つ必要があり、WEBアプリでは、複数のユーザーから頻繁に同時にアクセスがあるため、常に順番待ちになってしまい効率が悪くなってしまいます。
サーブレットは複数の処理を同時に行う「マルチスレッド」に対応しているので、複数のユーザーからのアクセスでも効率良く処理を行うことができます。プラットフォームに依存しない
サーブレットはJavaのプログラムから作られていて、Windows, Mac, LINUXなどどの環境でも動作することが可能です。
【特徴】
今までのJavaと大きな相違点は、「Javaのプログラムが、ブラウザ上(HTTPサーバ上)で動くこと」です。つまり、サーブレットはWebサーバ(HTTPサーバ)がなければ意味がありません。
【使用場面】
サーブレットは「WEBブラウザからの要求に応えたり、送られてきたデータを処理したりする役割」で「WEBページをはじめとし画面に関する処理」は行いません。その特性から他のプログラムと連携することで真価を発揮します。
JSP (HTML内にJavaのコードを埋め込む)
JSPは「動的にWEBページを生成する技術」でHTML内にJavaのコードを埋め込むことができます。
- サーブレットはWEBアプリ内部の処理
- JSPは主に外部の表示担当
と考えると良いかと思います。サーブレットとJSPが連携することで、
- 「特定のユーザー情報を画面に表示」する
- 「ログイン時に表示する画面の内容を変える」
といったような、いわゆる「動的なWEBページ」を作ることが可能です。
Tomcat (サーブレットを動かすソフト)
サーブレット単独ではプログラムを動作させることができません。
Tomcatはサーブレットを動かすエンジンの役割をしていて、必要に応じて命令を出してサーブレットを動かしてくれます。【まとめ】
JSP とサーブレットの関係についてようやく考えがまとまってきました。
近いうちになにかアプリを作ってみたいです。参考文献・記事
- 投稿日:2020-12-01T15:01:00+09:00
【Java】web.xmlについてメモ
web.xmlについて
「Webアプリケーションに関する様々な設定を、xml形式で定義するためのもの」
【説明】
web.xmlは
- アクセスされたURLに対して呼び出すクラス
- エラー時に実行する処理の指定
などの設定をします。
【配置箇所】(結論: WEB-INF直下)
web.xmlは 「WEB-INF」ディレクトリ直下 に配置し、Webアプリケーション毎に作成します。
(※「web.xml デプロイメント記述子の生成」にチェックして作成した場合は、デフォルトで作成されます。【読み込みのタイミング】
Webサーバ(HTTPサーバ)の起動時にWebアプリケーション毎にweb.xmlが読み込まれます。
【web.xmlの基本構造】
「XML宣言」、「DTD宣言」、「本体」の順で構成されています。
【書き方】
<?xml version="1.0" encoding="ISO-8859-1"?> // XML宣言 <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" //DTD宣言 "http://java.sun.com/dtd/web-app_2_3.dtd"> <web-app> ・・・ // 本体の記述 </web-app>web.xmlは、Webサーバの起動時に読み込まれ構文チェックが行われます。
- 正しく記述されてない場合は、 「致命的エラー」扱いとなりそのコンテナは動きません。
- 正しく記述されていれば、「本体にどのURLで、どのサーブレットを動かすかという定義必要」となります。
(※サーブレットが上手く機能しない場合は、 web.xmlの設定や書き方に間違いがないかチェックしましょう
【サーブレットの命名の仕方】
<web-app> <servlet> <servlet-name>サーブレットの命名</servlet-name> <servlet-class>パッケージ名.サーブレット名</servlet-class> </servlet> <servlet-mapping> <servlet-name>サーブレットの命名</servlet-name> <url-pattern>/URLのパターン名</url-pattern> </servlet-mapping> </web-app>【サンプル】ユーザーに情報を返すサーブレットの場合
<servlet> <servlet-name>userInfo</servlet-name> <servlet-class>UserInfoServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>userInfo</servlet-name> <url-pattern>/user</url-pattern> </servlet-mapping>web-app タグ
サーブレットの命名には「任意の名前」を付与
( ※日本語は付けられません命名規則
servletタグ
内 とservlet-mappingタグ
内 の2つで実施し、
上記「2つのタグにおけるサーブレットの命名は 同一でなくてはならない。」servletタグ
servletタグには、
servlet-classタグ
があります。記述方法に関しては以下です。
- 「動かしたいサーブレット名を記述」します。
- パッケージ化されている場合は、
helloPackage.HelloServlet
のように「パッケージ込みのサーブレット名を記述」します。servlet-mapping タグ と url-pattern タグ
このタグ内に記述された文字列で、ブラウザのURL(アドレス)などで指定した場合、
先ほどの動かしたいサーブレットが動作する仕組みとなります。サーブレットマッピング
上記
servlet-mapping
における設定は、必須 となり、この設定を「 サーブレットマッピング 」と呼びます。マッピングは「 可読性 と サーブレットの紐づけの親和性 」が高まるようなその機能に合った意味を持つ文言を記述するようにすること。
参考文献・記事
- 投稿日:2020-12-01T14:18:23+09:00
Java 文字列の一部を切り出すためのメソッド!
はじめに
学習用のメモになります。
substringメソッドとは?
substring
とは文字列の一部を切り出すために使えるメソッドになります。文字列の一部を切り取る(substring)
文字列.substring( int biginIndex)biginIndex → 開始インデックス(この位置にある文字は含まれる)
サンプルコード
Main.javapublic class Main { public static void main(String[] args) { // Scanner sc = new Scanner(System.in); // String s = sc.next(); String message ; //[1] message = "おはようございます。";//[2] String result = message.substring(4); System.out.println(result); } }出力結果
ございますsubstringを使用して変数messageの4文字目から最後までを取得し、変数resultへ代入し、
println
で出力するsubstringで間を取得する方法
「○文字目から最後」という設定だけではなく、「○文字目から○文字目」という取得もできます。
文字列.substring( int biginIndex,int endIndex)biginIndex → 開始インデックス(この位置にある文字は含まれる)
endIndex → 終了インデックス(この位置にある文字は含まれない)サンプルコード
Main.javapublic class Main { public static void main(String[] args) { String message ; message = "おはようございます。"; String result = message.substring(2,5); System.out.println(result); } }出力結果
ようご
- 投稿日:2020-12-01T12:28:40+09:00
Java containsメソッドと用途
containsメソッドとは?
containsメソッドは特定の要素を含むかどうか判定するメソッドです。
サンプルコード
標準入力で入力された要素が要素の何に含まれているかみます
入力例
Z KirishimaMain.javaimport java.util.*; public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); String a = sc.next(); //1行目の要素を取り出す String s = sc.next(); //2行目の要素をたりだす if (s.contains(a)) { //変数sの要素の中にaの要素が含まれているかみます System.out.println("YES"); } else { System.out.println("NO"); } } }出力結果
NO
- 投稿日:2020-12-01T12:13:44+09:00
【Java】ポリモーフィズム ( 呼び出す関数が呼び出し元に適した振る舞いをすること )
ポリモーフィズム
「中に入るものによって同じメソッドでも違う処理を行える」というプログラミング言語自体の特徴のこと。
【説明】
結論、ポリモーフィズムは「 呼び出す関数が呼び出し元に適した振る舞いをすること 」。
Wikipedia「ポリモーフィズム」には以下のように書かれていました。
「プログラミング言語の型システムの性質を表すもので、プログラミング言語の各要素(定数、変数、式、オブジェクト、関数、メソッドなど)についてそれらが複数の型に属することを許すという性質を指す。」
正直、自分には難解でしたが、つまりは「大量の同じ処理を一つのメソッドでまとめて処理する」ようにすることで、短いコードで管理がしやすく、修正に強いコードにすること」が「 ポリモーフィズム 」であると思いました。
ポリモーフィズム(和訳: 多相性)は、オブジェクト指向三大要素(継承, カプセル化, ポリモーフィズム)の1つで、そのなかでも一番理解が難しいとされます。
「ポリモーフィズム的」な考え方の目的についてのまとめが以下です。【メリット】
ポリモーフィズムを利用しない場合とプログラムと比較して、このようなメリットがあります。
- 記述量が減ることで、修正が加えやすくなる
- コードがきれいにまとまる
- 変更に強い柔軟性があり修正やテストにかかるコストが少ない
【サンプルコード】 ポリモーフィズムを使ってコードの重複を減らす
以下のプログラムは 親クラスに「Animal」,子クラスに「Cat」や「Dog」などのそれぞれ複数の動物クラスを作成し、「鳴き声を表示するメソッド」をそれぞれで出力する ものです。
つまり、以下のような実行結果をするということです。【Animal.java】親クラス
package about_polymorphism; abstract class Animal { private String name; public Animal(String name) { this.name = name; } // 食べ物の味を出力。 public void letsHear() { System.out.println(name + "は" + call() + "と鳴く"); } // 「抽象メソッドの呼び出し」ため、abstract修飾子を使用。 // 具象クラスからのみアクセスするため、protected修飾とする abstract protected String call(); }【各種子クラス】
//【Dog.java】 package about_polymorphism; public class Dog extends Animal { public Dog() { super("犬"); } protected String call() { return "バウバウ"; } } //【Cat.java】 package about_polymorphism; // 抽象クラスの Animalを 継承 public class Cat extends Animal{ public Cat() { super("ネコ"); } protected String call() { return "にゃーん"; } } //【Cow.java】 package about_polymorphism; public class Cow extends Animal { public Cow() { super("牛"); } protected String call() { return "ブルルルルル"; } } //【Horse.java】 package about_polymorphism; public class Horse extends Animal { public Horse() { super("馬"); } protected String call() { return "ヒヒーン"; } }【実行結果】
ネコはにゃーんと鳴く 犬はバウバウと鳴く 牛はブルルルルルと鳴く 馬はヒヒーンと鳴くポリモーフィズムを使わない場合
ポリモーフィズムな書き方をしないと、以下のように 「行数が多く、同じことを何度も記述」しているコードになっている点に注目です。
【Main.java】
package about_polymorphism; public class Main { public static void main(String[] args) { // ネコクラスの インスタンス 生成 Cat cat = new Cat(); // 鳴き声を表示する メソッドの呼び出し cat.letsHear(); // 犬クラスの インスタンス 生成 Dog dog = new Dog(); // 鳴き声を表示する メソッドの呼び出し dog.letsHear(); // 牛クラスの インスタンス 生成 Cow cow = new Cow(); // 鳴き声を表示する メソッドの呼び出し cow.letsHear(); // 馬クラスの インスタンス 生成 Horse horse = new Horse(); // 鳴き声を表示する メソッドの呼び出し horse.letsHear(); } }ポリモーフィズムな書き方の場合
上記のコードを下記のようなポリモーフィズム的な書き方で スッキリさせることができます。
親クラス 配列 = {new 子クラス(),new 子クラス(),new 子クラス(),new 子クラス()..}; for (親クラス 変数 :配列) { 変数.メソッド(); }【Main.java】 ポリモーフィズム化
- インスタンス化を一気にまとめて行い配列に格納
- 「定義した子クラス全部で、それぞれメソッドを呼び出す」を拡張for文(for文でも)を用いて行う。
public class Main { public static void main(String[] args) { //それぞれのオブジェクトのポインタを格納する配列を用意 // 子クラスのインスタンス化を 配列内に入れてひとくくりに行う Animal[] animals = { new Cat(), new Dog(), new Cow(),new Horse()}; // 「定義した子クラス全部で、それぞれメソッドを呼び出す」を拡張for文(for文でも)を用いて行う。 for (Animal a : animals) { a.letsHear(); } } }行数が一気にスッキリしました。
このコードの良いところはスッキリしているだけでなく、「修正に強い」というのも強みです。仮にここに新しく子クラスを追加することになったとしても
- 動物クラスの配列に追加分のインスタンスを書くだけなので行数自体は増えない
- 中心的なソースをいじることなく修正することなく修正が可能
コードがきれいにまとまり、修正が加えやすく、変更に強い柔軟性があり、修正やテストにかかるコストが少ないという、いわゆる「良いコード」を実現しました。
【まとめ】
ポリモーフィズムなプログラムというのは、結果的に DRYの原則(同じことを二度書かない)に従ったプログラムとも言えるかもしれません。
参考文献・記事
- 投稿日:2020-12-01T12:06:54+09:00
Java 数値 ⇔ 文字列変換
はじめに
学習用のメモになります。
数値 ⇒ 文字列
String s = String.valueOf(i);文字列 ⇒ 数値
int i = Integer.parseInt(s);Javaコーディング規約などでも推奨されています。
でもこんな方法でも変換はできます。
数値 ⇒ 文字列
String s = "" + i; String s = new Integer(i).toString(); String s = Integer.toString(i);文字列 ⇒ 数値
int i = new Integer(s).intValue(); int i = Integer.valueOf(s).intValue();
- 投稿日:2020-12-01T11:54:54+09:00
javaで日時文字列を複数フォーマットでパース
まず
DateTimeFormatter
を使用して複数フォーマットでパースするには、ofPattern
にフォーマットを[...][...]
のように指定する。DateTimeFormatter formatter = DateTimeFormatter.ofPattern("[yyyy-MM-dd HH:mm:ss][yyyy/MM/dd HH:mm:ss]"); LocalDateTime p1 = LocalDateTime.parse("2020-10-27 01:01:10", formatter); LocalDateTime p2 = LocalDateTime.parse("2020/10/27 01:01:10", formatter);また、Spring MVCでは
@DateTimeFormat
のpattern
に同じようにして指定する。@GetMapping("/sample-parse") public void timeformat( @RequestParam(name = "t1", required = false) @DateTimeFormat(pattern = "[yyyy-MM-dd HH:mm:ss][yyyy/MM/dd HH:mm:ss]") LocalDateTime t1) { ...
- 投稿日:2020-12-01T11:32:31+09:00
Java 様々なメソッド
はじめに
学習用のメモになります。
length()とlengthメソッド
1.length()
length()メソッドは
文字列の長さ
を取得するメソッドとなります。length()の構文は下記となります。
Main.javaString.length()length()メソッドは、文字列の長さを返します。
つまり半角、全角関係なく文字列の文字数を返してくれます。2.length
lengthは、
配列型の長さ(要素の数)
を格納するフィールドというデータを格納する変数というものになります。
配列型の長さを格納するものと思っていれば大丈夫です。lengthの構文は下記となります。
Main.javaint length = 配列変数名.lengthlengthは、配列に適用できる最後の変数です。
長さ変数を使用して、配列のサイズを取得できます。containsメソッド
containsメソッドは特定の要素を含むかどうか判定するメソッドです。
サンプルコード
標準入力で入力された要素が要素の何に含まれているかみます
入力例
Z KirishimaMain.javaimport java.util.*; public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); String a = sc.next(); //1行目の要素を取り出す String s = sc.next(); //2行目の要素をたりだす if (s.contains(a)) { //変数sの要素の中にaの要素が含まれているかみます System.out.println("YES"); } else { System.out.println("NO"); } } }出力結果
NO
- 投稿日:2020-12-01T11:32:31+09:00
Java length()とlengthメソッド
はじめに
学習用のメモになります。
length()とlengthメソッド
1.length()
length()メソッドは
文字列の長さ
を取得するメソッドとなります。length()の構文は下記となります。
Main.javaString.length()length()メソッドは、文字列の長さを返します。
つまり半角、全角関係なく文字列の文字数を返してくれます。2.length
lengthは、
配列型の長さ(要素の数)
を格納するフィールドというデータを格納する変数というものになります。
配列型の長さを格納するものと思っていれば大丈夫です。lengthの構文は下記となります。
Main.javaint length = 配列変数名.lengthlengthは、配列に適用できる最後の変数です。
長さ変数を使用して、配列のサイズを取得できます。
- 投稿日:2020-12-01T07:17:06+09:00
【Java・SpringBoot】Spring JDBC でユーザー削除処理(SpringBootアプリケーション実践編14)
ホーム画面からユーザー一覧画面に遷移し、ユーザーの詳細を表示するアプリケーションを作成して、Spring JDBCの使い方について学びます⭐️
前回はユーザー更新処理を実装したので、今回はユーザー削除処理を実装します^^
構成は前回の記事を参考にしてください⭐️前回の記事
【Java・SpringBoot】Spring JDBC でユーザー更新処理(SpringBootアプリケーション実践編13)リポジトリークラスを修正
- JdbcTemplateのupdateメソッドを使用
UserDaoJdbcImpl.java//中略(全コードは下記参考) // Userテーブルを1件削除. @Override public int deleteOne(String userId) throws DataAccessException { //1件削除 int rowNumber = jdbc.update("DELETE FROM m_user WHERE user_id = ?", userId); return rowNumber; } //中略サービスクラスを修正
UserService.java//中略(全コードは下記参考) /** * 1件削除用メソッド. */ public boolean deleteOne(String userId) { // 1件削除 int rowNumber = dao.deleteOne(userId); // 判定用変数 boolean result = false; if (rowNumber > 0) { // delete成功 result = true; } return result; } //中略コントローラークラスに削除ボタンが押されたときのメソッドを追加
- 削除ボタン用のメソッドは@PostMappingのparams属性を使って指定
HomeController.java//中略(全コードは下記参考) /** * ユーザー削除用処理. */ @PostMapping(value = "/userDetail", params = "delete") public String postUserDetailDelete(@ModelAttribute SignupForm form, Model model) { System.out.println("削除ボタンの処理"); //削除実行 boolean result = userService.deleteOne(form.getUserId()); if (result == true) { model.addAttribute("result", "削除成功"); } else { model.addAttribute("result", "削除失敗"); } //中略SpringBootを起動してユーザ一覧画面確認!
- http://localhost:8080/signup
- ユーザーを新規登録してから、ユーザー詳細画面でユーザーを削除(selectで取得件数が0件になるとエラーになる)
- ユーザー一覧に戻ると対象のユーザが消えています〜〜
- ここまでで、Jdbc Templateを使った基本のCRUD操作について学びました^o^
リポジトリークラス全体コード
UserDaoJdbcImpl.javapackage com.example.demo.login.domain.repository.jdbc; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import com.example.demo.login.domain.model.User; import com.example.demo.login.domain.repository.UserDao; @Repository("UserDaoJdbcImpl") public class UserDaoJdbcImpl implements UserDao { @Autowired JdbcTemplate jdbc; // Userテーブルの件数を取得. @Override public int count() throws DataAccessException { //全件取得してカウント int count = jdbc.queryForObject("SELECT COUNT(*) FROM m_user", Integer.class); return count; } // Userテーブルにデータを1件insert. @Override public int insertOne(User user) throws DataAccessException { //1件登録 int rowNumber = jdbc.update("INSERT INTO m_user(user_id," + " password," + " user_name," + " birthday," + " age," + " marriage," + " role)" + " VALUES(?, ?, ?, ?, ?, ?, ?)", user.getUserId(), user.getPassword(), user.getUserName(), user.getBirthday(), user.getAge(), user.isMarriage(), user.getRole()); return rowNumber; } // Userテーブルのデータを1件取得 @Override public User selectOne(String userId) throws DataAccessException { // 1件取得 Map<String, Object> map = jdbc.queryForMap("SELECT * FROM m_user" + " WHERE user_id = ?", userId); // 結果返却用の変数 User user = new User(); // 取得したデータを結果返却用の変数にセットしていく user.setUserId((String) map.get("user_id")); //ユーザーID user.setPassword((String) map.get("password")); //パスワード user.setUserName((String) map.get("user_name")); //ユーザー名 user.setBirthday((Date) map.get("birthday")); //誕生日 user.setAge((Integer) map.get("age")); //年齢 user.setMarriage((Boolean) map.get("marriage")); //結婚ステータス user.setRole((String) map.get("role")); //ロール return user; } // Userテーブルの全データを取得. @Override public List<User> selectMany() throws DataAccessException { // M_USERテーブルのデータを全件取得 List<Map<String, Object>> getList = jdbc.queryForList("SELECT * FROM m_user"); // 結果返却用の変数 List<User> userList = new ArrayList<>(); // 取得したデータを結果返却用のListに格納していく for (Map<String, Object> map : getList) { //Userインスタンスの生成 User user = new User(); // Userインスタンスに取得したデータをセットする user.setUserId((String) map.get("user_id")); //ユーザーID user.setPassword((String) map.get("password")); //パスワード user.setUserName((String) map.get("user_name")); //ユーザー名 user.setBirthday((Date) map.get("birthday")); //誕生日 user.setAge((Integer) map.get("age")); //年齢 user.setMarriage((Boolean) map.get("marriage")); //結婚ステータス user.setRole((String) map.get("role")); //ロール //結果返却用のListに追加 userList.add(user); } return userList; } // Userテーブルを1件更新. @Override public int updateOne(User user) throws DataAccessException { //1件更新 int rowNumber = jdbc.update("UPDATE M_USER" + " SET" + " password = ?," + " user_name = ?," + " birthday = ?," + " age = ?," + " marriage = ?" + " WHERE user_id = ?", user.getPassword(), user.getUserName(), user.getBirthday(), user.getAge(), user.isMarriage(), user.getUserId()); return rowNumber; } // Userテーブルを1件削除. @Override public int deleteOne(String userId) throws DataAccessException { //1件削除 int rowNumber = jdbc.update("DELETE FROM m_user WHERE user_id = ?", userId); return rowNumber; } //SQL取得結果をサーバーにCSVで保存する @Override public void userCsvOut() throws DataAccessException { } }サービスクラス全体コード
UserService.javapackage com.example.demo.login.domain.service; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.example.demo.login.domain.model.User; import com.example.demo.login.domain.repository.UserDao; @Service public class UserService { @Autowired UserDao dao; public boolean insert(User user) { // insert実行 int rowNumber = dao.insertOne(user); // 判定用変数 boolean result = false; if (rowNumber > 0) { // insert成功 result = true; } return result; } public int count() { return dao.count(); } public List<User> selectMany() { // 全件取得 return dao.selectMany(); } /** * 1件取得用メソッド. */ public User selectOne(String userId) { // selectOne実行 return dao.selectOne(userId); } public boolean updateOne(User user) { // 判定用変数 boolean result = false; // 1件更新 int rowNumber = dao.updateOne(user); if (rowNumber > 0) { // update成功 result = true; } return result; } /** * 1件削除用メソッド. */ public boolean deleteOne(String userId) { // 1件削除 int rowNumber = dao.deleteOne(userId); // 判定用変数 boolean result = false; if (rowNumber > 0) { // delete成功 result = true; } return result; } }ホームコントローラークラス全体コード
HomeController.javapackage com.example.demo.login.controller; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; 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.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import com.example.demo.login.domain.model.SignupForm; import com.example.demo.login.domain.model.User; import com.example.demo.login.domain.service.UserService; @Controller public class HomeController { @Autowired UserService userService; private Map<String, String> radioMarriage; private Map<String, String> initRadioMarrige() { Map<String, String> radio = new LinkedHashMap<>(); // 既婚、未婚をMapに格納 radio.put("既婚", "true"); radio.put("未婚", "false"); return radio; } @GetMapping("/home") public String getHome(Model model) { //コンテンツ部分にユーザー詳細を表示するための文字列を登録 model.addAttribute("contents", "login/home :: home_contents"); return "login/homeLayout"; } @GetMapping("/userList") public String getUserList(Model model) { //コンテンツ部分にユーザー一覧を表示するための文字列を登録 model.addAttribute("contents", "login/userList :: userList_contents"); //ユーザー一覧の生成 List<User> userList = userService.selectMany(); //Modelにユーザーリストを登録 model.addAttribute("userList", userList); //データ件数を取得 int count = userService.count(); model.addAttribute("userListCount", count); return "login/homeLayout"; } @GetMapping("/userDetail/{id:.+}") public String getUserDetail(@ModelAttribute SignupForm form, Model model, @PathVariable("id") String userId) { // ユーザーID確認(デバッグ) System.out.println("userId = " + userId); // コンテンツ部分にユーザー詳細を表示するための文字列を登録 model.addAttribute("contents", "login/userDetail :: userDetail_contents"); // 結婚ステータス用ラジオボタンの初期化 radioMarriage = initRadioMarrige(); // ラジオボタン用のMapをModelに登録 model.addAttribute("radioMarriage", radioMarriage); // ユーザーIDのチェック if (userId != null && userId.length() > 0) { // ユーザー情報を取得 User user = userService.selectOne(userId); // Userクラスをフォームクラスに変換 form.setUserId(user.getUserId()); //ユーザーID form.setUserName(user.getUserName()); //ユーザー名 form.setBirthday(user.getBirthday()); //誕生日 form.setAge(user.getAge()); //年齢 form.setMarriage(user.isMarriage()); //結婚ステータス // Modelに登録 model.addAttribute("signupForm", form); } return "login/homeLayout"; } @PostMapping(value = "/userDetail", params = "update") public String postUserDetailUpdate(@ModelAttribute SignupForm form, Model model) { System.out.println("更新ボタンの処理"); //Userインスタンスの生成 User user = new User(); //フォームクラスをUserクラスに変換 user.setUserId(form.getUserId()); user.setPassword(form.getPassword()); user.setUserName(form.getUserName()); user.setBirthday(form.getBirthday()); user.setAge(form.getAge()); user.setMarriage(form.isMarriage()); //更新実行 boolean result = userService.updateOne(user); if (result == true) { model.addAttribute("result", "更新成功"); } else { model.addAttribute("result", "更新失敗"); } //ユーザー一覧画面を表示 return getUserList(model); } /** * ユーザー削除用処理. */ @PostMapping(value = "/userDetail", params = "delete") public String postUserDetailDelete(@ModelAttribute SignupForm form, Model model) { System.out.println("削除ボタンの処理"); //削除実行 boolean result = userService.deleteOne(form.getUserId()); if (result == true) { model.addAttribute("result", "削除成功"); } else { model.addAttribute("result", "削除失敗"); } //ユーザー一覧画面を表示 return getUserList(model); } @PostMapping("/logout") public String postLogout() { return "redirect:/login"; } @GetMapping("/userList/csv") public String getUserListCsv(Model model) { return getUserList(model); } }
- 投稿日:2020-12-01T03:07:55+09:00
Bukkitプラグイン開発 - コマンド処理、最適化編
最初に
- この記事は応用で、メインクラスに何でもまとめてしまうのを避けるのがねらいです。
- コマンド処理の前提知識はあると仮定し、記事も長くなるので色々端折ってます。
plugin.yml
の記載も省いてます(既に書いてある体)。この記事を書いた当時の環境は
- Spigot-API
- Java 11(コンパイルは8)
- APIバージョン 1.16(
api-version
では未指定)- Apache Maven
です。当然ながらBukkit、Paperでも応用できますし、固有の機能などは使用しません。
Java 8やAPIバージョンが多少古くても新しくても大半は動くと思いますので、まずは試して下さい。資料
- Creating a Simple Command | SpigotMC - High Performance Minecraft
- コマンド - Plugin Tutorial - Minecraft Modding Wiki
- [MineCraftプラグイン]コマンドを受け付けてイベントを発生させる - Qiita
肥大化は必ず来る
コマンドの受け取りイベントならメインクラスの
onCommand()
で実装しても良いですが、それはケースによるでしょう。コマンドが複数になるとコードも膨大になります。
if
文の嵐にしたくないなら1コマンド1クラスで分けちゃった方が良いこともあります。親となる抽象クラスを書く
1コマンド1クラスと言っても似たような処理をクラス毎に書くのは出来るなら避けたいところです。
そこで便利な抽象クラスを先に書いておく。
これをベースにして、子クラス側ではonCommand()
を処理する感じです。
パッケージも***.***.command
みたいに分けておきます。command/BaseCommand.javapackage com.stsynthe.bukkit.how2.command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.PluginCommand; import org.bukkit.command.TabCompleter; import com.stsynthe.bukkit.how2.HowTo; public abstract class BaseCommand implements CommandExecutor { private static HowTo PLUGIN = null;// 直接扱わず、メソッド ***Plugin() で介す public BaseCommand(HowTo plugin) { if (this.getPlugin() == null) this.setPlugin(plugin); if (this.getInstance() == null) throw new NullPointerException("Instance is null"); if (this.getCommandName() == null) throw new NullPointerException("CommandName is null"); this.register(); } /** * プラグインのインスタンスをゲット * */ final HowTo getPlugin() { return BaseCommand.PLUGIN; } /** * プラグインのインスタンスをセットする * * @param plugin */ final void setPlugin(HowTo plugin) { if (plugin == null) throw new IllegalArgumentException("Argument \"plugin\" is null"); BaseCommand.PLUGIN = plugin; } /** * CommandExecutor と TabCompleter の登録 * */ public void register() { PluginCommand c = this.getPlugin().getCommand(this.getCommandName()); if (c != null) { c.setExecutor(this.getInstance()); if (this.getInstance() instanceof TabCompleter) c.setTabCompleter((TabCompleter) this.getInstance()); } } /** * 自身のインスタンスを返す * */ abstract BaseCommand getInstance(); /** * コマンド名をゲットする * * @return コマンド名を返す */ public abstract String getCommandName(); }とりあえずベースは最低限と言うことでここまで。内訳は割愛します。
試しにコマンド「test」を実装する。
同パッケージ内にクラスTest
を作成。親(スーパー)クラスにBaseCommand
を指定する。実装内容は
- コマンド「test」を実装
- 結果にかかわらず、コンソールに「コマンドを実行」のメッセージを表示する
- チャットで
/test
を送信すると「コマンドを実行しました!」というメッセージを返信、表示する
- プレイヤーチャット以外(コンソール)からはコマンド実行結果を失敗(偽)で返す
command/Test.javapackage com.stsynthe.bukkit.how2.command; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import com.stsynthe.bukkit.how2.HowTo; final public class Test extends BaseCommand { public Test(HowTo plugin) { super(plugin); } @Override public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { boolean result = false; if (sender instanceof Player) { Player player = (Player) sender; player.sendMessage("コマンドを実行しました!"); result = true; } this.getPlugin().getLogger().info("コマンドを実行"); return result; } @Override BaseCommand getInstance() { return this; } @Override public String getCommandName() { return "test"; } }親を抽象クラスにしてあるので以上4つのメソッドは必ず実装しておく。
- コンストラクタは親のコンストラクタを先に実行しておきます。
- 受け取った
plugin
は子クラスで保持しない。既にthis.getPlugin()
で使えます。getInstance()
メソッドも自身のインスタンスを返すだけなのでreturn this;
で固定です。getCommandName()
メソッドは今回コマンド「test」を実装するのでtest
を返してやります。後は
onCommand()
の内容をゴリゴリ書いていくだけです。メインクラスでインスタンスを生成し、登録実装してもらう
HowTo.javapublic class HowTo extends JavaPlugin { @Override public void onEnable() { new Test(this); } }もう少し色々やった方が良いですが、最低限だとこれだけで実装できます。
BaseCommand
でメインクラス(上記例ではHowTo
)をスコープさせるように成ってるので、わざわざ子クラス毎でインスタンスを保持しなくて良いようになってます。また、
BaseCommand
はコモンにもなれるので共通のコードがあればそこに書いておけば使い回しが出来ます。あとは煮るなり焼くなりどうぞ。plugin.yml
を省いてあるのでそちらの追記を忘れずに。最後に
この時期のQiita Advent Calendarは「本番環境でやらかしちゃった人」をよく見てたりしますが参加は初めてです。後で見返すとニッチな記事なので少し場違いな気もしたりしなかったり。
以前だとMinecraft Advent Calendar 2017が有ったのですが近年では見かけなくなりました。Bukkitにかからず、新型ForgeやFabricの一応のドキュメントは有るし、基礎的な記事はいくらでも出てきますが、「はい、基礎はここまで。応用は各自でねー。」なのが多すぎてお腹いっぱいです。自分もそうですけどJavaの事をあまり知らないから応用に踏み込まなかったり、小技的な記事が少ないのかなとも思ったりしてます。
コードの扱いについて
抽象クラス
BaseCommand
を含め、全てのコードは自由にお使い下さい。
著作権や記事へのリンクを求めたりしません。その代わり、コメントなどでのサポートは一切受け付けません。今後「Bukkitプラグイン開発」をシリーズ化する予定ですが、
BaseCommand
を含め独自クラスが採用されている予定です。おまけ:タブ補完
TabCompleter
の実装コマンドのタブ補完になるとQiita内で触れている記事は「CraftBukkit・Spigotプラグインを作るときの小技のような何か - Qiita」ぐらいでした。
今までの流れでTabCompleter
も実装してみましょう。追加するのは子クラスだけ!
お気づきかと思いますが、抽象クラス
BaseCommand
の子クラスにTabCompleter
が実装されているとsetTabCompleter()
を実行してタブ補完を登録してくれます。タブ補完はオプショナルなのでBaseCommand
には実装してません。実装内容は
- コマンド「check」の実装
- タブ補完では1つ目の引数に「one」「two」「three」のリストを返す
- コマンドを発信したのがプレイヤーならチャットに「コマンドは(プレイヤー名)が実行」を送信
- 引数「three」が指定されるとアイテム「石」を3個インベントリに入れる
- 実行結果に関わらず成功(真)で返す
command/Check.javapackage com.stsynthe.bukkit.how2.command; import java.util.Arrays; import java.util.List; import org.bukkit.Material; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.command.TabCompleter; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import com.stsynthe.bukkit.how2.HowTo; final public class Check extends BaseCommand implements TabCompleter { private String[] completeList = new String[] { "one", "two", "three" }; public Check(HowTo plugin) { super(plugin); } @Override public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { if (sender instanceof Player) { Player player = (Player) sender; if (args.length == 1 && args[0].equals(this.completeList[2])) player.getInventory().addItem(new ItemStack[] { new ItemStack(Material.STONE, 3)// 引数が three なら 石3個を得る }); player.sendMessage("コマンドは" + player.getDisplayName() + "が実行"); } return true;// 成功で返す } @Override public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) { if (args.length == 1) return Arrays.asList(this.completeList); return null;// 1つ目以外の引数には null で返す } @Override BaseCommand getInstance() { return this; } @Override public String getCommandName() { return "check"; } }後はメインクラスに
HowTo.javapublic class HowTo extends JavaPlugin { @Override public void onEnable() { new Test(this); new Check(this);// 追加部分 } }を、足して動作確認しましょう。
- 投稿日:2020-12-01T01:34:42+09:00
Java8の日時APIにおける期間の重複判定
Advent Calendarの2日目です
初めてAdvent calendarに寄稿しました。
初日の@tkxlab さんの記事Jakarta EEをはじめよう! - Qiitaが大作なので、翌日がこれですみませんという気持ちでいっぱいです。初めに
SQLなどではよくある(かもしれません)が、Javaでは珍しい期間同士の重複判定を行う方法です。
数直線的に記述するとこんな感じのイメージです。|-----期間A-----| |-----期間B-----| |---| ↑ここの重複している期間があるかどうかを確認したい元々Java8になる前ではJoda Time - Maven Repositoryを使用して日付の計算をすることが多かったです。(もちろん単純な加算減算レベルならjava.util.Calendarクラスなんかを使用することもありました)
Java8で日時APIが導入されてから、ほぼ完結していて不満もありませんでしたが、
今回行った期間の重複判定についてはいろいろ探したのですが見つからなかったので、
ついに拡張コードを書く日がきたか、という印象です。ソースコード
Githubに置いてあります!
バージョン等
- Java 11 (8以上なら大丈夫です)
- Lombok 1.18.16
ざっくり解説
使い方
Main.javapublic class Main { public static void main(String[] args) { LocalDateTime originFrom = LocalDateTime.now(); LocalDateTime originTo = originFrom.plusDays(30L); LocalDateTimeInterval origin = new LocalDateTimeInterval(originFrom, originTo); LocalDateTime otherFrom = originFrom.plusDays(15L); LocalDateTime otherTo = originTo.plusDays(15L); LocalDateTimeInterval interval = new LocalDateTimeInterval(otherFrom, otherTo); System.out.println(origin.overlapsAsOpen(other)); // -> true } }このように、日付の組み合わせで表現される期間同士を比較しています。
この
LocalDateTimeInterval
クラスの実装は以下です。LocalDateTimeInterval.javapublic class LocalDateTimeInterval extends AbstractTemporalInterval<LocalDateTime, LocalDateTimeInterval> { public LocalDateTimeInterval(LocalDateTime from, LocalDateTime to) { super(from, to); } @Override protected long toEpoch(@NonNull LocalDateTime dateTime) { return dateTime.toEpochSecond(ZoneOffset.UTC); } }非常に少ないコードで記述してあります。
なぜかというと、やはりLocalDateTime
を扱うということはLocalDate
やLocalTime
を扱うことも考慮したからです。実際に
LocalDate
やLocalTime
版も作成して、ほぼ同じコードとなっています。LocalDateInterval.javapublic class LocalDateInterval extends AbstractTemporalInterval<LocalDate, LocalDateInterval> { public LocalDateInterval(LocalDate from, LocalDate to) { super(from, to); } @Override protected long toEpoch(@NonNull LocalDate date) { return date.toEpochDay(); } }LocalTimeInterval.javapublic class LocalTimeInterval extends AbstractTemporalInterval<LocalTime, LocalTimeInterval> { public LocalTimeInterval(LocalTime from, LocalTime to) { super(from, to); } @Override protected long toEpoch(@NonNull LocalTime time) { return time.toEpochSecond(LocalDate.EPOCH, ZoneOffset.UTC); } }ということで、ほとんどの実装は抽象クラスにまとめています。
AbstractTemporalInterval.java@Getter(AccessLevel.PROTECTED) public abstract class AbstractTemporalInterval<T extends Temporal, I extends AbstractTemporalInterval<T, I>> { @NonNull protected final T from; @NonNull protected final T to; protected AbstractTemporalInterval(@NonNull T from, @NonNull T to) { if (toEpoch(from) >= toEpoch(to)) { throw new IllegalArgumentException("from must be before to"); } this.from = from; this.to = to; } public final boolean contains(@NonNull T temporal) { return toEpoch(from) <= toEpoch(temporal) && toEpoch(temporal) <= toEpoch(to); } public final boolean equals(@NonNull I other) { return toEpoch(from) == toEpoch(other.getFrom()) && toEpoch(to) == toEpoch(other.getTo()); } public final boolean overlapsAsOpen(@NonNull I other) { return toEpoch(from) < toEpoch(other.getTo()) && toEpoch(other.getFrom()) < toEpoch(to); } public final boolean overlapsAsClosed(@NonNull I other) { return toEpoch(from) <= toEpoch(other.getTo()) && toEpoch(other.getFrom()) <= toEpoch(to); } protected abstract long toEpoch(@NonNull T temporal); }
Interface
にdefault
実装が許されたJava8以降、あまり書く機会がなかった抽象クラスを使っています。
というのも、期間を表すFrom/Toの情報をプロパティとして保持したいからですね。将来的にサブクラス側でFrom/Toの値を使用するメソッドが必要になるかもしれないので、
可視性はprotected
にしてあります。工夫点
抽象メソッドの
toEpoch
は、LocalDateTime#toEpochSecond
やLocalDate#toEpochDay
と行ったエポック変換を行うメソッドが各クラスで異なるので、そこを吸収するためのメソッドです。
このメソッドを使って、contains
、equals
、overlapsAsOpen
、overlapsAsClosed
の各メソッドで判定を行っています。
AsOpen
とかAsClosed
ってなんぞや、となりますが、これは数学の区間の分類で、
開区間(Open interval)は境界点を含む、いわゆる黒丸で表される数直線です。
閉区間(Closed interval)は境界点を含まない、いわゆる白丸で表される数直線です。実際に使用する際は、デフォルトとしてどちらを扱うのかを決めて、
overlapsAsOpen
とoverlapsAsClosed
はprivate
メソッドにし、
public
のoverlaps
メソッドでデフォルトを呼び出すような形にするのもいいかもしれません。AbstractTemporalInterval.javapublic final boolean overlaps(@NonNull I other) { return overlapsAsOpen(other); } private final boolean overlapsAsOpen(@NonNull I other) { return toEpoch(from) < toEpoch(other.getTo()) && toEpoch(other.getFrom()) < toEpoch(to); } private final boolean overlapsAsClosed(@NonNull I other) { return toEpoch(from) <= toEpoch(other.getTo()) && toEpoch(other.getFrom()) <= toEpoch(to); }一番苦労したのはクラス宣言部分
AbstractTemporalInterval<T extends Temporal, I extends AbstractTemporalInterval<T, I>>
です。
この宣言により、各メソッドで引数の型を各サブクラスでLocalDateTimeInterval extends AbstractTemporalInterval<LocalDateTime, LocalDateTimeInterval>
のように指定し、取り扱う型をコンパイル時に判別できるようにしています。終わりに
Githubの方ではテストやコメントもしっかり書いてあるので、ぜひ一度ご覧になってください!
また、コメントの翻訳ミスや、こんなメソッドもあった方がいいんじゃない?みたいなのもあればコメントやGithubのPull Requestで教えていただけると嬉しいです!明日は@yonetty さんの「ラムダ式とStream APIに関するネタ」です!(公開後にリンク貼ってタイトルも変えます)
参考
- 投稿日:2020-12-01T00:50:44+09:00
【Spring Boot】CORSをアプリケーション全体で設定する時にEnvironmentインタフェースを使いたい
概要
Spring MVC で CORS 設定の記事で解説されている通り、Spring BootでCORSの設定をする際にはアノテーションを個別に設定する方法と、アプリケーション全体で設定する方法があります。個別のパスでCORSの設定をするケースは、個人的にはあまり多くないかなと思っていてまずは、アプリケーション全体で設定を選択すると思います。今回は、アプリケーション全体で設定する時の課題に関して書いてみます。
課題
上記の記事で紹介されている
WebMvcConfig
のclassを定義する方法では、Environmentインタフェースを使用できません。理由として考えられるのは@Configuration
のアノテーションが付与されているclassは、applicantion.properties
と同列の扱いとなり、このclass内ではEnvironmentインタフェースの設定が読み込めないと考えられます。Spring Bootがプロパティファイルを読み込む方法に一同驚愕!!の記事が参考になります。どうすれば良いのか
とはいえ、やはり環境毎にCORSの許可URLを変えたいので、どうすれば良いのかというのがSpring Boot enabling CORS by application.propertiesで紹介されています。いくつか方法はあるのですが、
SpringBootApplication
に直接WebMvcConfig
の定義を書いてしまう方法が、シンプルで分かりやすいかなと感じました。ので、以下にサンプルの実装をのせておきます。サンプル実装
SampleApplication.java@SpringBootApplication public class SampleApplication { @Autowired private Environment env; @Bean public WebMvcConfigurer corsConfigurer() { return new WebMvcConfigurer() { @Override public void addCorsMappings(CorsRegistry registry) { String url = env.getProperty("allowed.url"); registry.addMapping("/**") .allowedOrigins(url) .allowCredentials(true); } }; } public static void main(String[] args) { SpringApplication.run(SampleApplication.class, args); } }