- 投稿日:2020-05-16T23:06:23+09:00
OpenTripPlannerで異なるフィードの駅を結びつける
OpenTripPlannerとGTFSを利用して独自の乗り換え案内を作ってみたの関連記事です。
OTPをJavaライブラリとして使用して乗り換え案内サイト欧亜大陸鉄道を作った時のTips。はじめに
OSSの経路探索アプリケーションOpenTripPlanner(OTP)には、複数のGTFSファイルをフィードとして登録することができる。
このとき複数のフィードの中に同一駅を示すStopが個別に含まれていたらどうしたらいいのかという話。前提条件
通常OTPは、GTFSデータだけでなく地図データを登録するので、この場合駅間は徒歩移動として経路探索されるので問題ない。
また、複数のGTFSを一つに統合できるのであれば、それを読み込ませればいいので解決。
しかしながら、やんごとなき事情で複数のGTFSファイルが必要で、なおかつ地図データを登録しない場合にどうしたらいいのかというのがこの記事の内容。
要するに、かなりニッチな話ということ。結論
org.opentripplanner.routing.edgetype.FreeEdgeオブジェクトを使う。
OTPでは地図やGTFSの経路情報をEdgeオブジェクトの結びつきで保持しているが、この一種であるFreeEdgeは移動コストが0のエッジとみなされる。
よってこのFreeEdgeオブジェクトで2つのStopを結び付ければ、実質的に同一駅であるとみなされることになる。ソース
// GraphオブジェクトからGraphIndexオブジェクトを取得 GraphIndex index = graph.index; // 同一地点とみなす二つのstop情報を取得 Stop stop1 = index.stopForId.get(new FeedScopedId("nmbs", "aachen-hauptbahnhof")); Stop stop2 = index.stopForId.get(new FeedScopedId("dr", "aachen-hauptbahnhof")); // StopオブジェクトからVertexオブジェクトを取得 TransitStop ts1 = index.stopVertexForStop.get(stop1); TransitStop ts2 = index.stopVertexForStop.get(stop2); // FreeEdgeオブジェクトをインスタンス化するだけで結び付けられる new FreeEdge(ts1, ts2); // 反対方向も結び付けておく new FreeEdge(ts2, ts1);解説
StopオブジェクトはGraphオブジェクトから取得されるGraphIndexから取得できる。(詳しくはこちらを参照)
EdgeオブジェクトはVertexオブジェクトを結び付けて経路を表しているのだが、StopオブジェクトはGTFSのメタデータを表すオブジェクトであり、Vertexではない。
そこで、GraphIndexのstopVertexForStopStopからStopをキーに対応するVertexオブジェクトを取得する。
これをFreeEdgeで結び付ければ完成。通常は、2つの駅間双方向で乗り換え可能となるので、逆方向もEdgeも作成しておく。
なお、FreeEdgeは、newするだけで経路として登録されるので、その後特に何もしなくてよい。
- 投稿日:2020-05-16T22:31:54+09:00
OpenTripPlannerのGraphからGTFSデータを取得する
OpenTripPlannerとGTFSを利用して独自の乗り換え案内を作ってみたの関連記事です。
OTPをJavaライブラリとして使用して乗り換え案内サイト・欧亜大陸鉄道を作った時のTips。はじめに
OpenTripPlanner(OTP)にはWebサービスがあり、その中の IndexAPI を使えば、登録済みのGTFSデータを取得することができる。
ですが、アプリ内部でライブラリ的にOTP的を利用しているときはどのようにしてデータを取得すればよいのかという話。結論
結論から言えば、IndexAPIのソース(org.opentripplanner.index.IndexAPI)を見れば一目同然。
それでは、身もふたもないので...。ソース
// GraphオブジェクトからGraphIndexオブジェクトを取得 GraphIndex index = graph.index; // GraphIndexオブジェクトからGTFSデータを取得する // route情報を取得 Route r = index.routeForId.get(new FeedScopedId("fid", "tokaido")); // stop情報を取得 Stop s = index.stopForId.get(new FeedScopedId("fid", "tokyo")); // trip情報を取得 Trip t = index.tripForId.get(new FeedScopedId("fid", "fuji1"));解説
OTP上でGTFSの各要素を特定するためには、Feed IDとGTFS上のIDの組み合わせが必要。
複数のGTFSファイルを取り込むことができるOTPではデータ取り込み時に各GTFSファイルに一意のIDを付与するが、これがFeed ID。(詳しくは、こちらを参照)
このFeed IDとGTFS上のIDの組み合わせを表すオブジェクトがorg.opentripplanner.model.FeedScopedId。このオブジェクトを生成し、それをキーとして該当要素を検索する。
GTFSデータが格納されているのは、org.opentripplanner.routing.graph.Graphオブジェクトのindexプロパティから取得できるorg.opentripplanner.routing.graph.GraphIndexオブジェクト。
表にまとめると以下の通り。
GTFSのファイル 対応するOTPのオブジェクト 取得できるGraphIndexのプロパティ agency.txt org.opentripplanner.model.Agency agenciesForFeedId stops.txt org.opentripplanner.model.Stop stopForId routes.txt org.opentripplanner.model.Route routeForId trips.txt org.opentripplanner.model.Trip tripForId なお、OTP 1.3以前では、GTFS関連のメタデータは、OneBusAwayの同名オブジェクトをしようしていた。パッケージは違うものの使い方は同じである。
- 投稿日:2020-05-16T22:12:44+09:00
単体テストを書くためのstaticメソッドからの脱却
概要
レガシーコードでは、単体テストを書こうとした際に、しばしば既存のstaticメソッドが妨げになることがある。
この記事では、「既存のstaticメソッドが妨げになっているケースの解決方法」と「新たにそのようなコードを増やさないようにするための指針」を示す。問題
下記の
LegacyService#execute()
のようなメソッドに対して、うまく単体テストを書くことができない。
loadFromDatabaseメソッドがデータベースへのアクセスを必要とするため、それが不可能な環境から実行した際には、例外が発生してしまう。仮にデータベースへのアクセスが可能だったとしても、そのデータベースから安定して同じデータを取得することは難しく、実行に時間がかかってしまう。
public class LegacyService { public void execute() { // 省略 Object obj = loadFromDatabase(); // 省略 } public static Object loadFromDatabase() { // データベースにアクセスするような処理 } }データベースにアクセスする処理をMockで差し替えればよいのだが、loadFromDatabaseメソッドはstaticメソッドとして宣言されているため、それが困難である。1
loadFromDatabaseメソッドをインスタンスメソッドに変更すれば良いようにも思えるのだが、すでに他のクラス・メソッドからも参照されている場合は、単純に変更することができない。
問題を解決するための原則(ルール)
staticメソッドに下記のような条件を満たす処理を書いてはいけない。
1. データベースとやり取りする 2. ネットワークを介した通信をする 3. ファイルシステムにアクセスする 4. 実行するために特別な環境設定を必要とする(環境設定ファイルの編集など)(引用: レガシーコード改善ガイド / 2.1 単体テストとは)
static メソッドは、円周率や平方根を求める関数などの「(少なくとも、そのシステムのそのメジャーバージョンでは)どのような実行環境においても、結果が変わることのない定数・関数」のみに利用すべきである。
どのように解決するか
しかし、上記の原則を破っている既存コードに対しては、どうすればよいのか。
このような場合、原則的にはテスト対象コードのリファクタリングをおこなう必要がある。commitを分けて、ステップを追って修正していくべきである。
ステップ1: ラップするインスタンスメソッドを作る
まず、問題となっているloadFromDatabaseメソッドをラップするloadDataメソッドを作成する。
このとき、そのメソッドをprivateで宣言してはいけない(理由はステップ2で説明)。
public class LegacyService { public void execute() { // 省略 Object data = loadFromDatabase(); // 省略 } // 新しく追加したインスタンスメソッド @VisibleForTesting Object loadData() { // もともと存在していた、問題のstaticメソッドを呼び出す return loadFromDatabase(); } // もともと存在していたstaticメソッド public static Object loadFromDatabase() { // ここでDBアクセスするような処理 } }ステップ2: テストコードでステップ1の実装をMockする
テストコードで、ステップ1にて作成したloadDataメソッドの実装をMockで上書きする。
LegacyServiceのJUnitテストクラスpublic class StaticLegacyServiceTest extends StaticLegacyService { @Test public void test() { execute(); } @Override Object loadData() { // ここで実装をMockする } }この上書きを可能にするため、ステップ1ではloadDataメソッドの可視性をパッケージプライベートにしていた(protectedでも可)。
テストクラスのために可視性を上げており、その意図をわかりやすくするために@VisibleForTestingアノテーションをつけている。また、サンプルのテストクラスはテストクラス自身をテスト対象クラスのサブクラスにするSelf Shuntアプローチを使っている。もちろん、他の書き方でも問題ない。
ステップ3: テスト対象メソッドの実装を変える
テスト対象メソッドであるexecuteメソッドについて、元々のloadFromDatabaseメソッドの呼び出し部分を、ラップしたloadDataの呼び出しに書き換える。
public class LegacyService { public void execute() { // 省略 // 新しく追加したインスタンスメソッドに呼び出し先を変える Object data = loadData(); // 省略 } // 省略 }このステップまでは、むやみにテスト対象メソッドを書き換えていない。修正範囲が限られるため、安全性の高いリファクタリングができる。
まとめ
ここまでが、レガシーコード改善ガイドで「ラップメソッド」という名前で紹介されている手法を適用したシンプルなケースである。
このような手法を利用することで、既存のコードに問題がある場合でも、影響範囲を限定しつつ単体テストを書くことができる。
問題のstaticメソッドが異なるクラスに宣言されているケースなどでは、もっと適したやり方がある。それについてはまた別記事で。。。
PowerMockなどのライブラリを使えば可能だが、実行が遅くなるなどの欠点があるため、今回は対象外とする ↩
- 投稿日:2020-05-16T22:12:44+09:00
単体テストのためのstaticメソッドからの脱却
概要
レガシーコードでは、単体テストを書こうとした際に、しばしば既存のstaticメソッドが妨げになることがある。
この記事では、「既存のstaticメソッドが妨げになっているケースの解決方法」と「新たにそのようなコードを増やさないようにするためのルール」を示す。レガシーコードに対して、はじめて単体テストを書くとき向けの記事。
問題
下記の
LegacyService#execute()
のようなメソッドに対して、うまく単体テストを書くことができない。
loadFromDatabaseメソッドがデータベースへのアクセスを必要とするため、それが不可能な環境から実行した際には、例外が発生してしまう。仮にデータベースへのアクセスが可能だったとしても、そのデータベースから安定して同じデータを取得することは難しく、実行に時間がかかってしまう。
public class LegacyService { public void execute() { // 省略 Object obj = loadFromDatabase(); // 省略 } // クラスの外からも呼ばれているようなstaticメソッド public static Object loadFromDatabase() { // データベースにアクセスするような処理 } }データベースにアクセスする処理をMockで差し替えればよいのだが、loadFromDatabaseメソッドはstaticメソッドとして宣言されているため、それが困難である。1
loadFromDatabaseメソッドをインスタンスメソッドに変更すれば良いようにも思えるのだが、すでに他のクラス・メソッドからも参照されている場合は、単純に変更することができない。
問題を解決するための原則(ルール)
staticメソッドに下記のような条件を満たす処理を書いてはいけない。
1. データベースとやり取りする 2. ネットワークを介した通信をする 3. ファイルシステムにアクセスする 4. 実行するために特別な環境設定を必要とする(環境設定ファイルの編集など)(引用: レガシーコード改善ガイド / 2.1 単体テストとは)
staticメソッドは、円周率や平方根を求める関数などの「(少なくとも、そのシステムのそのメジャーバージョンでは)どのような実行環境においても、引数に対する結果が変わることのない定数・関数」のみに利用すべきである。
どのように解決するか
しかし、上記の原則を破っている既存コードに対しては、どうすればよいのか。
このような場合、原則的にはテスト対象コードのリファクタリングをおこなう必要がある。commitを分けて、ステップを追って修正していくべきである。
ステップ1: ラップするインスタンスメソッドを作る
まず、問題となっているloadFromDatabaseメソッドをラップするloadDataメソッドを作成する。
このとき、そのメソッドをprivateで宣言してはいけない(理由はステップ2で説明)。
public class LegacyService { public void execute() { // 省略 Object data = loadFromDatabase(); // 省略 } // 新しく追加したインスタンスメソッド @VisibleForTesting Object loadData() { // もともと存在していた、問題のstaticメソッドを呼び出す return loadFromDatabase(); } // もともと存在していたstaticメソッド public static Object loadFromDatabase() { // ここでDBアクセスするような処理 } }ステップ2: テストコードでステップ1の実装をMockする
テストコードで、ステップ1にて作成したloadDataメソッドの実装をMockで上書きする。
LegacyServiceのJUnitテストクラスpublic class StaticLegacyServiceTest extends StaticLegacyService { @Test public void test() { execute(); } @Override Object loadData() { // ここで実装をMockする } }この上書きを可能にするため、ステップ1ではloadDataメソッドの可視性をパッケージプライベートにしていた(protectedでも可)。
テストクラスのために可視性を上げており、その意図をわかりやすくするために@VisibleForTestingアノテーションをつけている。また、サンプルのテストクラスはテストクラス自身をテスト対象クラスのサブクラスにするSelf Shuntアプローチを使っている。もちろん、他の書き方でも問題ない。
ステップ3: テスト対象メソッドの実装を変える
テスト対象メソッドであるexecuteメソッドについて、元々のloadFromDatabaseメソッドの呼び出し部分を、ラップしたloadDataの呼び出しに書き換える。
public class LegacyService { public void execute() { // 省略 // 新しく追加したインスタンスメソッドに呼び出し先を変える Object data = loadData(); // 省略 } // 省略 }このステップまでは、むやみにテスト対象メソッドを書き換えていない。修正範囲が限られるため、安全性の高いリファクタリングができる。
まとめ
ここまでが、レガシーコード改善ガイドでは「ラップメソッド」という名前で紹介されている手法を適用したシンプルなケースである。
このような手法を利用することで、既存のコードに問題がある場合でも、影響範囲を限定しつつ単体テストを書くことができる。
なお、問題のstaticメソッドが異なるクラスに宣言されているケースなどでは、もっと適したやり方がある。それについてはまた別記事で。。。
PowerMockなどのライブラリを使えば可能だが、実行が遅くなるなどの欠点があるため、今回は対象外とする ↩
- 投稿日:2020-05-16T22:12:44+09:00
単体テストを書くためにstaticメソッドから脱却しよう
概要
レガシーコードでは、単体テストを書こうとした際に、しばしば既存のstaticメソッドが妨げになることがある。
この記事では、「新たにそのようなコードを増やさないようにするための原則(ルール)」と「既存のstaticメソッドが妨げになっているケースの解決方法」を示す。レガシーコードに対して、はじめて単体テストを書くとき向けの記事。
問題
下記の
LegacyService#execute()
のようなメソッドに対して、うまく単体テストを書くことができない。
テスト対象メソッドから呼び出されているloadFromDatabaseメソッドがデータベースへのアクセスを必要とするため、アクセスが不可能な環境から実行した際には、例外が発生してしまう。仮にデータベースへのアクセスが可能だったとしても、そのデータベースから安定して同じデータを取得することは難しく、また実行時間がかかってしまう。
public class LegacyService { public void execute() { // ここにテスト対象のコードが入る // 問題のコード Object data = loadFromDatabase(); // ここにテスト対象のコードが入る } // クラスの外からも呼ばれているようなstaticメソッド public static Object loadFromDatabase() { // データベースにアクセスするような処理 } }データベースにアクセスする処理をMockで差し替えればよいのだが、loadFromDatabaseメソッドはstaticメソッドとして宣言されているため、それが困難である。1
また、loadFromDatabaseメソッドをインスタンスメソッドに変更すれば良いようにも思えるのだが、すでに他のクラス・メソッドからも参照されている場合、単純に変更することができない。
問題を解決するための原則(ルール)
staticメソッドに下記のような条件を満たす処理を書いてはいけない。
1. データベースとやり取りする 2. ネットワークを介した通信をする 3. ファイルシステムにアクセスする 4. 実行するために特別な環境設定を必要とする(環境設定ファイルの編集など)(引用: レガシーコード改善ガイド / 2.1 単体テストとは)
staticメソッドは、円周率や平方根を求める関数などの「(少なくとも、そのシステムのそのメジャーバージョンでは)どのような実行環境においても、引数に対する結果が変わることのない定数・関数」のみに利用すべきである。
既存コードの問題をどのように解決するか
しかし、上記の原則を破っている既存コードに対しては、どうすればよいのか。
このような場合、基本的にはテスト対象コードのリファクタリングをおこなう必要がある。commitを分けて、ステップを追って修正していこう。
ステップ1: ラップするインスタンスメソッドを作る
まず、問題となっているloadFromDatabaseメソッドをラップするloadDataメソッドを作成する。
このとき、そのメソッドをprivateで宣言してはいけない。また、@VisibleForTestingアノテーションをつけている。これらの理由はステップ2で説明する。
import com.google.common.annotations.VisibleForTesting; public class LegacyService { public void execute() { // ここにテスト対象のコードが入る Object data = loadFromDatabase(); // ここにテスト対象のコードが入る } // 新しく追加したインスタンスメソッド @VisibleForTesting Object loadData() { // もともと存在していた、問題のstaticメソッドを呼び出す return loadFromDatabase(); } // もともと存在していたstaticメソッド public static Object loadFromDatabase() { // データベースにアクセスするような処理 } }ステップ2: テストコードでステップ1の実装をMockする
テストコードで、ステップ1にて作成したloadDataメソッドの実装をMockで上書きする。
LegacyServiceのJUnitテストクラスpublic class LegacyServiceTest extends LegacyService { @Test public void test() { execute(); } @Override Object loadData() { // ここで実装をMockする } }この上書きを可能にするため、ステップ1ではloadDataメソッドの可視性をパッケージプライベートにしていた(protectedでも可)。
テストクラスのために可視性を上げており、その意図をわかりやすくするために@VisibleForTestingアノテーションをつけている。また、サンプルのテストクラスはテストクラス自身をテスト対象クラスのサブクラスにするSelf Shuntアプローチを使っている。もちろん、他の書き方でも問題ない。
ステップ3: テスト対象メソッドの実装を変える
テスト対象メソッドであるexecuteメソッドについて、元々のloadFromDatabaseメソッドの呼び出し部分を、ラップしたloadDataの呼び出しに置き換える。
public class LegacyService { public void execute() { // ここにテスト対象のコードが入る // 新しく追加したインスタンスメソッドに呼び出し先を変える Object data = loadData(); // ここにテスト対象のコードが入る } // 省略 }このステップまでは、むやみにテスト対象メソッドを書き換えていない。修正範囲が限られるため、安全性の高いリファクタリングができる。
修正後のクラスimport com.google.common.annotations.VisibleForTesting; public class LegacyService { public void execute() { // ここにテスト対象のコードが入る Object data = loadData(); // ここにテスト対象のコードが入る } // 新しく追加したインスタンスメソッド @VisibleForTesting Object loadData() { return loadFromDatabase(); } // もともと存在していたstaticメソッド public static Object loadFromDatabase() { // ここでDBアクセスするような処理 } }まとめ
単体テストを書くためには、むやみにメソッドをstaticで宣言してはいけない。
しかし、既存のコードに問題がある場合でも、ステップを分けて修正することで、リファクタリングによる影響範囲を限定しつつ単体テストを書くことができる。
なお、既存のコードに問題がある場合の解決方法は、これ以外にも複数の手法が存在する。それらの詳細についてはまた別の記事で。。。
PowerMockなどのライブラリを使えば可能だが、実行が遅くなるなどの欠点があるため、今回は対象外とする ↩
- 投稿日:2020-05-16T21:26:35+09:00
Javaのif文とswich文
条件分岐
特定の状況のときだけある処理を行うです。
OOだったらXXするみたいな感じです。
例えば、天気予報が雨だったら傘を持っていくということです。
if文の場合Main.javaif (条件式) { 処理; }さっきの例に当てはめると
Main.javaif (天気予報 == 雨) { 傘を持っていく; }となります。
if文を数字に当てはめます。
true(正しい)の場合Main.javaint x = 10; if (x == 10){ System.out.println("xは10"); }上記の場合だと(x == 10)の条件分岐がtrueになるため、コンソールはxは10と表示されます。
false(間違い)の場合Main.javaint x = 20; if (x == 10){ System.out.println("xは10"); }上記の場合だと(x == 10)の条件分岐がfalseになるため、コンソールはなにも表示されません。
else
if文のelseは「もし〜ならOO、そうでなければXX」という条件分岐ができるようになります。
Main.javaint x = 10; if (x < 20){ System.out.println("xは20より小さい"); } else { System.out.println("xは20より大きい"); }上記の場合だと結果はxは20より小さいと表示されます。
Main.javaint x = 30; if (x < 20){ System.out.println("xは20より小さい"); } else { System.out.println("xは20より大きい"); }上記の場合だと結果はxは20より大きいと表示されます。
else if
ifとelse if、elseを組み合わせると、「もし〜なら◯◯、そうではなくてもし××なら△△、どちらでもない場合は□□」という条件分岐ができます。
Main.javaint x = 25; if (x < 30){ System.out.println("xは30より大きい"); } else if { System.out.println("xは20より大きく、30より小さい"); } else { System.out.println("xは20より小さい");上記の場合だと結果はxは20より大きく、30より小さいと表示されます。
ひとつ注意することは複数の条件に合致しても、実行されるのは最初に合致した条件にのみになることです。swich文
条件分岐にはswitch文という構文もあります。
switch文は条件の値がcaseの値と一致するときに処理が実行されます。Main.javaswich(条件の値) { case 値1: 処理; break; case 値2: 処理; break; case 値3: 処理; break; }上記のように記述します。caseの後ろはコロン(:)です。
break;はswitch文を終了する命令です。break;がないと、合致したcaseの処理を行った後、その次のcaseの処理も実行してしまいます。
実際のコードの例Main.javaint x=10 swich(x % 2) { case 0: System.out.println("偶数") break; case 1: System.out.println("奇数") break; }上記の例だと偶数が実行されます。
default
どのcaseとも一致しなかったときに実行する処理を、defaultを用います。
【例】Main.javaswich(rank) { case 1: System.out.println("1位") break; case 2: System.out.println("2位") break; case 2: System.out.println("3位") break; default: System.out.println("4位以下") break; }上記の場合だと4位以下であれば、defaultが実行されます。
- 投稿日:2020-05-16T21:08:18+09:00
Java SE 11 Programmer II試験とJava SE 8 Programmer II試験の相違点(作成中)
※随時更新していきたいと思います。
この記事について
2020/5/16にJava SE 11 Programmer II試験を受けて合格し、Java Programmer, Gold SE 11(以下Goldと記載)の資格を取得しましたので、これから受験する方の一助になればと思い、この記事を書くことにしました。
自分自身新機能についてそこまで理解があったわけではないので、その復習も兼ねています。
Java Programmer, Silverの資格まで持っていて(Java SE 11 Programmer IIの受験資格がある)、Java SE 8 Goldの知識についてもある程度学習が進んでいる前提で話を進めて行きますので、ラムダ式やStream APIなどの話はあまりせず、SE 8との違いをメインに書きたいと思います。はじめに
自分が勤めている会社には資格手当があり、Javaの学習と資格手当の取得を兼ねて資格勉強を行っていました。Java SE 7/8 Bronze、java SE 8 Silver(Java SE 8 Programmer I)はいずれも2~3週間ほど黒本を解けば比較的容易に取れました。
Goldについても2カ月ほど勉強して、SE 8なら受かるレベルまでにはなりましたが、せっかく最上位の資格を受けるならなら最新の資格を取りたいと思い、SE 11のほうを受験することにしました。しかし、参考書が2020/5/16現在出版されておらず、まだまだ出版までは時間がかかりそうな見通しです。なのでSE 11からの新分野は参考書などに頼らずに学習しなければならず、これがなかなか大変でした。
大まかな情報はOracleが開催した無料のオンラインセミナーを受講して情報を集めました。出題者の方が講師をやっており、話も分かりやすいので、大変有意義でした。もし機会があれば受講して損はないと思います。
また、5/31までに受験することで、再受験が無料になるキャンペーンをおこなっているので、迷っている方は一度受けてみるのも良いと思います。詳細は以下のリンクに記載されています。試験概要
まず、試験の概要から見ていきます。
Java SE 11 Programmer II | Oracle University
Java SE 11 Programmer II Java SE 8 Programmer II 試験名 Java SE 11 Programmer II Java SE 8 Programmer II 試験番号 1Z0-816 1Z0-809 受験料(税抜) ¥26,600 ¥26,600 出題形式 選択問題 選択問題 試験時間 180分 150分 出題数 80問 85問 合格ライン 63% 65% 問題が少し減り、試験時間が伸びました。問題文のコードが全体的に少し長くなった影響だと思われます。合格ラインも微妙に減っていますが、これは新しめの資格だからなのかなと思います。本番では30分くらい余りましたので、焦って解く必要はそこまでないのかなと思います。
出題範囲の相違点
新規に追加されたトピック
・インタフェースのprivateメソッド
・ローカル変数型推論(var)の使用法
・モジュール機能
・セキュアコーディング新規に追加されたトピックからは、モジュール機能とセキュアコーディングが重点的に出題されていました。
重点的に出題されるトピック
・シリアライゼーション
・ジェネリクス
・JDBC
・ラムダ式
・Stream APIシリアライゼーションの比率が思ったより高いので、しっかり理解しておきましょう。
JDBCについては、セキュアコーディングと絡めたSQLインジェクション対策に関する問題も出たりします。
ラムダ式、Stream APIはSE 8と同様に最も重要なトピックで、出題比率もかなり高いです。出なくなったトピック
・Date and Time API
・Fork/Join フレームワーク
・デザインパターン
Date and Time APIは、セミナーで出てこないと言われたのですが、1問だけフォーマットの問題が出てきました。基本だけでも覚えたほうがよさそうです。
デザインパターンも、Singletonパターンは同期制御などのセキュアコーディングと絡めて出題されるかもしれません。Java SE 11の実行環境
Pleiades Java 11 標準搭載と Eclipse コードネーム終焉
リンク先からダウンロードできるPleiades All In Oneに一通りそろっているので、手っ取り早く使いたいならこれが一番です。
インタフェースのprivateメソッド
Java 9 から、インタフェースにprivateメソッドを定義できるようになりました。
同じインタフェース内のstaticメソッドやdefaultメソッドで使用するために用いるもので、実装を持ち、継承や外部のクラス等からの呼び出しは出来ません。SampleInterface.javapublic interface SampleInterface{ default void defaultMethod(){ privateMethod("太郎"); privateMethod("次郎"); } static void staticMethod(){ privateStaticMethod("三郎"); privateStaticMethod("四郎"); } private void privateMethod(String name){ System.out.println( "こんにちは、" + name + "さん"); } private static void privateStaticMethod(String name){ System.out.println( "こんにちは、" + name + "さん"); } }SampleClass.javapublic class SampleClass implements SampleInterface{ public static void main(String[] args) { SampleClass sc = new SampleClass(); sc.defaultMethod(); SampleInterface.staticMethod(); } }実行結果こんにちは、太郎さん こんにちは、次郎さん こんにちは、三郎さん こんにちは、四郎さんこのように、インタフェース内で似たような処理を記述するときに何回も同じコードを書かずに簡潔に記述することが出来ます。
ローカル変数型推論(var)の使用法
Java 10から、ローカル変数時のvarを用いた型推論が使えるようになりました。変数を代入した際の右辺から型を自動的に推論します。
使用例
LocalVariableType.javapublic class LocalVariableType { public static void printInt(int i) { System.out.println(i + " is int type"); } public static void printBoolean(boolean bl) { System.out.println(bl + " is boolean type"); } public static void printString(String str) { System.out.println(str + " is String type"); } public static void main(String[] args) { var i = 1; var bl = true; var str = "String"; printInt(i);//int型の引数を取る関数 printBoolean(bl);//boolean型の引数を取る関数 printString(str);//String型の引数を取る関数 //Listやfor文でも使用可能 var list = List.of(i,bl,str); for(var l :list) { System.out.print(l + " "); } //Java 11から、ラムダ式の引数にvarが使用できるようになり、これによりアノテーションを付けられるようになった Function<String,Integer> function = (@Annotation var a) -> a.length(); } }実行結果1 is int type true is boolean type String is String type 1 true StringJavaScriptなどと同様に、右辺から型推論が行えるようになったのが分かると思います。リストの型推論はジェネリクスの記述を省略できるので、コード量が少なくなって見やすくなると思います。
コンパイルエラーになる例
右辺から型を推論できない場合は、コンパイルエラーになります。
LocalVariableType2public class LocalVariavleType2 { public static void main(String[] args) { var v;//宣言時に初期化を行わなければならない var f1 = a -> a + 1; //ラムダ式は代入できない var f2 = (Runnable)() -> new String("Hello");//関数型インタフェースをキャストすると初期化できる var list1 = {1, 2, 3};//配列の初期化には使用できない var list2 = new int[]{1, 2, 3};//型を指定すると初期化できる var list3 = new ArrayList<>();//コンパイル可能だが、型はArrayList<Object>になる var n = null;//nullは代入不可 } }varの使用法について直接聞いてきたのは2問ほどでしたが、当たり前のように問題文のコード中に書かれていたりするので、意味は分かるようにしておきましょう。
モジュール機能
執筆中
セキュアコーディング
執筆中
- 投稿日:2020-05-16T21:08:18+09:00
Java SE 11 Programmer II試験とJava SE 8 Programmer II試験の相違点
※この記事は作成中です。随時更新していきたいと思います。5/24までには一通り記事作成を終える予定です。
この記事について
2020/5/16にJava SE 11 Programmer II試験を受けて合格し、Java Programmer, Gold SE 11(以下Goldと記載)の資格を取得しましたので、これから受験する方の一助になればと思い、この記事を書くことにしました。
自分自身新機能についてそこまで理解があったわけではないので、その復習も兼ねています。
Java Programmer, Silverの資格まで持っていて(Java SE 11 Programmer IIの受験資格がある)、Java SE 8 Goldの知識についてもある程度学習が進んでいる前提で話を進めて行きますので、ラムダ式やStream APIなどの話はあまりせず、SE 8との違いをメインに書きたいと思います。はじめに
自分が勤めている会社には資格手当があり、Javaの学習と資格手当の取得を兼ねて資格勉強を行っていました。Java SE 7/8 Bronze、java SE 8 Silver(Java SE 8 Programmer I)はいずれも2~3週間ほど黒本を解けば比較的容易に取れました。
Goldについても2カ月ほど勉強して、SE 8なら受かるレベルまでにはなりましたが、せっかく最上位の資格を受けるならなら最新の資格を取りたいと思い、SE 11のほうを受験することにしました。しかし、参考書が2020/5/16現在出版されておらず、まだまだ出版までは時間がかかりそうな見通しです。なのでSE 11からの新分野は参考書などに頼らずに学習しなければならず、これがなかなか大変でした。
大まかな情報はOracleが開催した無料のオンラインセミナーを受講して情報を集めました。出題者の方が講師をやっており、話も分かりやすいので、大変有意義でした。もし機会があれば受講して損はないと思います。
また、5/31までに受験することで、再受験が無料になるキャンペーンをおこなっているので、迷っている方は一度受けてみるのも良いと思います。詳細は以下のリンクに記載されています。試験概要
まず、試験の概要から見ていきます。
Java SE 11 Programmer II | Oracle University
Java SE 11 Programmer II Java SE 8 Programmer II 試験名 Java SE 11 Programmer II Java SE 8 Programmer II 試験番号 1Z0-816 1Z0-809 受験料(税抜) ¥26,600 ¥26,600 出題形式 選択問題 選択問題 試験時間 180分 150分 出題数 80問 85問 合格ライン 63% 65% 問題が少し減り、試験時間が伸びました。問題文のコードが全体的に少し長くなった影響だと思われます。合格ラインも微妙に減っていますが、これは新しめの資格だからなのかなと思います。本番では30分くらい余りましたので、焦って解く必要はそこまでないのかなと思います。
出題範囲の相違点
新規に追加されたトピック
・インタフェースのprivateメソッド
・ローカル変数型推論(var)の使用法
・モジュール機能
・セキュアコーディング新規に追加されたトピックからは、モジュール機能とセキュアコーディングが重点的に出題されていました。
重点的に出題されるトピック
・シリアライゼーション
・ジェネリクス
・JDBC
・ラムダ式
・Stream APIシリアライゼーションの比率が思ったより高いので、しっかり理解しておきましょう。
JDBCについては、セキュアコーディングと絡めたSQLインジェクション対策に関する問題も出たりします。
ラムダ式、Stream APIはSE 8と同様に最も重要なトピックで、出題比率もかなり高いです。出なくなったトピック
・Date and Time API
・Fork/Join フレームワーク
・デザインパターン
Date and Time APIは、セミナーで出てこないと言われたのですが、1問だけフォーマットの問題が出てきました。基本だけでも覚えたほうがよさそうです。
デザインパターンも、Singletonパターンは同期制御などのセキュアコーディングと絡めて出題されるかもしれません。Java SE 11の実行環境
Pleiades Java 11 標準搭載と Eclipse コードネーム終焉
リンク先からダウンロードできるPleiades All In Oneに一通りそろっているので、手っ取り早く使いたいならこれが一番です。
インタフェースのprivateメソッド
Java 9 から、インタフェースにprivateメソッドを定義できるようになりました。
同じインタフェース内のstaticメソッドやdefaultメソッドで使用するために用いるもので、実装を持ち、継承や外部のクラス等からの呼び出しは出来ません。SampleInterface.javapublic interface SampleInterface{ default void defaultMethod(){ privateMethod("太郎"); privateMethod("次郎"); } static void staticMethod(){ privateStaticMethod("三郎"); privateStaticMethod("四郎"); } private void privateMethod(String name){ System.out.println( "こんにちは、" + name + "さん"); } private static void privateStaticMethod(String name){ System.out.println( "こんにちは、" + name + "さん"); } }SampleClass.javapublic class SampleClass implements SampleInterface{ public static void main(String[] args) { SampleClass sc = new SampleClass(); sc.defaultMethod(); SampleInterface.staticMethod(); } }実行結果こんにちは、太郎さん こんにちは、次郎さん こんにちは、三郎さん こんにちは、四郎さんこのように、インタフェース内で似たような処理を記述するときに何回も同じコードを書かずに簡潔に記述することが出来ます。
ローカル変数型推論(var)の使用法
Java 10から、ローカル変数時のvarを用いた型推論が使えるようになりました。変数を代入した際の右辺から型を自動的に推論します。
使用例
LocalVariableType.javapublic class LocalVariableType { public static void printInt(int i) { System.out.println(i + " is int type"); } public static void printBoolean(boolean bl) { System.out.println(bl + " is boolean type"); } public static void printString(String str) { System.out.println(str + " is String type"); } public static void main(String[] args) { var i = 1; var bl = true; var str = "String"; printInt(i);//int型の引数を取る関数 printBoolean(bl);//boolean型の引数を取る関数 printString(str);//String型の引数を取る関数 //Listやfor文でも使用可能 var list = List.of(i,bl,str); for(var l :list) { System.out.print(l + " "); } //Java 11から、ラムダ式の引数にvarが使用できるようになり、これによりアノテーションを付けられるようになった Function<String,Integer> function = (@Annotation var a) -> a.length(); } }実行結果1 is int type true is boolean type String is String type 1 true StringJavaScriptなどと同様に、右辺から型推論が行えるようになったのが分かると思います。リストの型推論はジェネリクスの記述を省略できるので、コード量が少なくなって見やすくなると思います。
コンパイルエラーになる例
右辺から型を推論できない場合は、コンパイルエラーになります。
LocalVariableType2public class LocalVariavleType2 { public static void main(String[] args) { var v;//宣言時に初期化を行わなければならない var f1 = a -> a + 1; //ラムダ式は代入できない var f2 = (Runnable)() -> new String("Hello");//関数型インタフェースをキャストすると初期化できる var list1 = {1, 2, 3};//配列の初期化には使用できない var list2 = new int[]{1, 2, 3};//型を指定すると初期化できる var list3 = new ArrayList<>();//コンパイル可能だが、型はArrayList<Object>になる var n = null;//nullは代入不可 } }varの使用法について直接聞いてきたのは2問ほどでしたが、当たり前のように問題文のコード中に書かれていたりするので、意味は分かるようにしておきましょう。
モジュール機能
執筆中
セキュアコーディング
執筆中
- 投稿日:2020-05-16T20:50:08+09:00
Effective Java 第2版 関連事項調査まとめ
はじめに
新人のころEffective Java 第2版を購入したが2~3年積んだまま放置してしまった。
読み返してみると案外今でも知らなかったりふわっとしてて人に説明できない関連事項が結構多かったため、恥を忍んでこの記事に調査内容をまとめていく。
Javaの本であるが、本記事の筆者は現在C#を現場で使っているため、もしC#に同等の内容が存在すれば、C#の内容を記載することもある。
また、このシリーズは、Effective Javaの本筋を追うものではなく、理解の障壁となりうる関連事項の調査をまとめたものである。
Effective Javaの内容そのものに関しては、すでに先人たちの努力によって多様なサマリーがあちこち落ちているので参照されたし。なお、本書は2020年5月現在第3版がすでに出版されているので、最新のEffective Javaが知りたい方はそちらを参照すること。
#Lambda式とかtry-with-resource文とか追加されたらしい目次
- 第1章は「はじめに」のため省略
- 第2章 オブジェクトの生成と消滅
第2章 オブジェクトの生成と消滅
- 投稿日:2020-05-16T20:48:36+09:00
インスタンス制御とは?
はじめに
Effective Java 第2版 P6に出てくる下記の内容について深堀する。
static ファクトリーメソッドが、何度呼び出されても同じオブジェクトを返すことができることは、ある時点でどのようなインスタンスが存在するかを厳密に制御するのにも使用できます。この制御をおこなうクラスは インスタンス制御されている (instance-controlled)と言われます。
目次
結局どんな状態?
- 実行環境が、あるクラスのインスタンス数を制限している状態
- 1個の場合もあるし、複数個の場合もあるが、「このクラスのインスタンスはn個以上存在することはありません!!」というのを保証している状態
- 同じインスタンスが1つだけしか存在しない
- インスタンスが同じ(
a == b
)であれば、インスタンスの内容も同一(a.equal(b)
)ということ- 上記の性質により、値比較の際
equal(Object)
の代わりに==
演算子による比較が可能具体例は?
シングルトン
インスタンスが1個であることが保証されているクラス。
下記はJavaでの記述例
Singleton.cs// Enumで実装したパターン // sealedで継承不可クラスになる public class SingletonClass { // 外部から変更できないようにする private static final SingletonClass INSTANCE = new SingletonClass(); // 外部から生成できないようにする private SingletonClass() { //初期化処理 }; // インスタンス取得 public static SingletonClass GetInstance() {return INSTANCE;} }下記はC#での記述例
Singleton.cs// Singletonパターン public class SingletonClass { // 外部から変更できないようにする private static SingletonClass _singleInstance = new SingletonClass(); // インスタンス取得 public static SingletonClass GetInstance(){ return _singleInstance; } // 外部から生成できないようにする private SingletonClass(){ //初期化処理 } }Enum
インスタンスがn個であることが保証されているクラス。 ※追記:Javaのみ
Enum.javaenum Season { Spring, Summer, Autumn, Winter }追記:ただし、C#のEnumはインスタンスがn個であることが保証されて いない。
クラスとして実装されているJavaと違って、C#のEnumはCの列挙型のように、名前付きの整数に過ぎないので、Javaと同様の保証はされない。
詳細はコメント欄を参照。
C#ではEnumでシングルトン作れないの?
蛇足だが気になったので調査する。
Javaでは下記のようにしてEnumを使用してシングルトンを実装することができる。EnumSingleton.javapublic enum Singleton { INSTANCE; public void execute (String arg) { // インスタンス取得など、お目当ての処理を実装 } }同様にしてC#でもEnumでシングルトンを実装できないのか?というのが疑問である。
調査の結果、 C#はEnumそのものにフィールドやメソッドを定義することができないため、Javaと同様にSingletonを作るのは難しいということが判明した。
仮にEnumを使って実装するとしたら、下記のように 拡張メソッド を使用する必要がある
追記:コメントで指摘があったが、そもそもC#のEnumは名前付き整数に過ぎないため、インスタンス数の制限を保証できない。
そのため、C#でEnumを利用したシングルトンの実装はできない
参考文献
- 投稿日:2020-05-16T20:41:04+09:00
【API】郵便番号検索APIを使ってみた
APIを使用したアプリを作ってみる
背景
- APIを使用したアプリケーションが増えてきている
- APIを使用する、作るエンジニアの仕事も増えている
- APIを扱う技術の需要が増えそう
目次
0.環境確認
- OS: Windows10 home
- IDE : Eclipse(Photon 4.8)
- ビルドツール : Gradle
- サーバーサイド言語 : Java(1.8)
- フレームワーク : SpringBoot(2.2.7)
- JavaScript ライブラリ : jQuery(3.3.1)
- テンプレートエンジン : thymeleaf
1.APIの確認
以下のサイトからAPIの使用を確認します。
https://zip-cloud.appspot.com/doc/api仕様を確認します。
- https://zip-cloud.appspot.com/api/search をベースにする
- リクエストパラメータ
パラメータ名 項目名 必須 備考 zipcode 郵便番号 ○ 7桁の数字。ハイフン付きでも可。完全一致検索。 callback コールバック関数名 - JSONPとして出力する際のコールバック関数名。UTF-8でURLエンコードした文字列。 limit 最大件数 - 同一の郵便番号で複数件のデータが存在する場合に返される件数の上限値(数字) ※デフォルト:20
- レスポンスパラメータ
フィールド名 項目名 備考 status ステータス 正常時は 200、エラー発生時にはエラーコードが返される message メッセージ エラー発生時に、エラーの内容が返される。 results zipcode(郵便番号)
prefcode(都道府県コード)
address1(都道府県名)
address2(市区町村名)
address3(町域名)
kana1(都道府県名カナ)
kana2 (市区町村名カナ)
kana3(町域名カナ)複数の場合、配列となる
- (例)郵便番号「7830060」で検索する場合
- リクエストURL https://zip-cloud.appspot.com/api/search?zipcode=7830060
- レスポンス
{ "message": null, "results": [ { "address1": "北海道", "address2": "美唄市", "address3": "上美唄町協和", "kana1": "ホッカイドウ", "kana2": "ビバイシ", "kana3": "カミビバイチョウキョウワ", "prefcode": "1", "zipcode": "0790177" }, { "address1": "北海道", "address2": "美唄市", "address3": "上美唄町南", "kana1": "ホッカイドウ", "kana2": "ビバイシ", "kana3": "カミビバイチョウミナミ", "prefcode": "1", "zipcode": "0790177" } ], "status": 200 }2.プロジェクトの作成
別の記事で詳細に紹介しているので、そちらを参照ください。
GradleのSpringBootプロジェクトを作成する3.バックエンドの実装
- build.gradle
build.gradle//中略 dependencies { implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-web' compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' compile("com.fasterxml.jackson.core:jackson-databind") }
- Controllerクラス
- パターン1:Json形式の文字列で受け取り
FrontController.java@Controller public class FrontController { @Autowired private FrontService frontService; @RequestMapping({ "/", "/index" }) public String index() { return "index"; } @ResponseBody @RequestMapping(value = "/getAddress" ,method = RequestMethod.POST, produces="application/json;charset=UTF-8") public String getAddress(@RequestBody(required = false) AddressForm addressForm) { return frontService.getAddress(addressForm.getZipcode()); } }
- Serviceクラス
FrontService.javapublic interface FrontService { public String getAddress(String zipCode); }FrontServiceImpl.java@Service public class FrontServiceImpl implements FrontService { /** 郵便番号検索API リクエストURL */ private static final String URL = "https://zip-cloud.appspot.com/api/search?zipcode={zipcode}"; @Override public String getAddress(String zipCode) { String zipCodeJson = restTemplate.getForObject(URL, String.class, zipCode); return zipCodeJson; } }
- formクラス
AddressForm.java@Data public class AddressForm { /** 郵便番号 */ String zipcode; }4.フロントエンドの実装
- html
index.html<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>address</title> <script type="text/javascript" th:src="@{/jquery/jquery-3.3.1.js}"></script> <script th:src="@{/js/index.js}"></script> </head> <body> <form name="getAddress"> <input id="zipcode" type="text"> <button type="button" id="getAddressBtn">住所取得</button> <div id="dispAddress"></div> </form> </body> </html>
- JavaScript
index.js$(function() { $('#getAddressBtn').on('click', function() { var params = { "zipcode" : $('#zipcode').val() }; $.ajax({ url : 'getAddress', type: 'POST', contentType: "application/json", data: JSON.stringify(params), dataType : 'json', async: false, success: function (data) { $("#dispAddress").empty(); var dispAddress = document.getElementById("dispAddress"); var table = document.createElement("table"); table.setAttribute("border","2"); table.setAttribute("cellpadding","15"); table.setAttribute("style","margin :15px"); $(data.results).each(function(index, result){ table.appendChild(createRow("郵便番号",result.zipcode)); table.appendChild(createRow("都道府県コード",result.prefcode)); table.appendChild(createRow("都道府県名",result.address1)); table.appendChild(createRow("市区町村名",result.address2)); table.appendChild(createRow("町域名",result.address3)); table.appendChild(createRow("都道府県名カナ",result.kana1)); table.appendChild(createRow("市区町村名カナ",result.kana2)); table.appendChild(createRow("町域名カナ",result.kana3)); }); dispAddress.appendChild(table); } }); }); }); function createRow(header , value){ var tr = document.createElement("tr"); var th = document.createElement("th"); th.append(header); var td = document.createElement("td"); td.append(value); tr.appendChild(th); tr.appendChild(td); return tr; }5.動作確認
6.おまけ
Json形式をDTOクラスに変換して受け取る方法
- ControllerクラスFrontController.java@Controller public class FrontController { @Autowired private FrontService frontService; @RequestMapping({ "/", "/index" }) public String index() { return "index"; } @ResponseBody @RequestMapping(value = "/getAddress" ,method = RequestMethod.POST, produces="application/json;charset=UTF-8") // 戻り値をString → ZipcodeDto に変更 public ZipcodeDto getAddress(@RequestBody(required = false) AddressForm addressForm) { return frontService.getAddress(addressForm.getZipcode()); } }
- Serviceクラス(修正)
FrontService.javapublic interface FrontService { // 戻り値をString → ZipcodeDto に変更 public ZipcodeDto getAddress(String zipCode); }
- DTOクラス(追加)
ZipcodeDto.java@Data public class ZipcodeDto { /** ステータス */ int status; /** メッセージ */ String message; /** 郵便番号情報リスト */ List<ZipcodeResultDto> results = new ArrayList<>(); }
- DTOクラス(追加)
ZipcodeResultDto.java@Data public class ZipcodeResultDto { /** 郵便番号 */ String zipcode; /** 都道府県コード */ String prefcode; /** 都道府県名 */ String address1; /** 市区町村名 */ String address2; /** 町域名 */ String address3; /** 都道府県名カナ */ String kana1; /** 市区町村名カナ */ String kana2; /** 町域名カナ */ String kana3; }
- Service実装クラス
- ObjectMapperにURLを渡してDTOクラスに変換するパターン
FrontServiceImpl.java@Service public class FrontServiceImpl implements FrontService { // ObjectMapperを追加 @Autowired ObjectMapper objectMapper; // URLのパラメータを正規表現に変更 private static final String URL = "https://zip-cloud.appspot.com/api/search?zipcode=%s"; @Override public ZipcodeDto getAddress(String zipCode) { ZipcodeDto zipcodeDto = null;; try { // ObjectMapperでURLと受け取りクラスを指定 java.net.URL url = new java.net.URL(String.format(URL,zipCode)); zipcodeDto = objectMapper.readValue(url, ZipcodeDto.class); } catch (Exception e) { e.getStackTrace(); } return zipcodeDto; } }
- Service実装クラス
- restTemplateにMappingJackson2HttpMessageConverterを設定して変換するパターン
FrontServiceImpl.java@Service public class FrontServiceImpl implements FrontService { // restTemplateを追加 RestTemplate restTemplate = new RestTemplate(); private static final String URL = "https://zip-cloud.appspot.com/api/search?zipcode={zipCode}"; @Override public ZipcodeDto getAddress(String zipCode) { ZipcodeDto zipcodeDto = null;; try { // reatTemplateのmessageConverterにMappingJackson2HttpMessageConverter を追加 MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter(); List<MediaType> supportedMediaTypes = new ArrayList<>(messageConverter.getSupportedMediaTypes()); supportedMediaTypes.add(MediaType.TEXT_PLAIN); messageConverter.setSupportedMediaTypes(supportedMediaTypes); restTemplate.setMessageConverters(Collections.singletonList(messageConverter)); zipcodeDto = restTemplate.getForObject(URL, ZipcodeDto.class, zipCode); } catch (Exception e) { e.getStackTrace(); } return zipcodeDto; } }7.まとめ
- APIを使用するのは簡単(認証機能付きはもう少し難しい)
- 他APIからデータ活用できる
- いろんなAPIを組み合わせて新しいサービスを作れそう。
- 投稿日:2020-05-16T19:27:10+09:00
JSPカスタムタグを簡単に作る方法
はじめに
今のところ、画面を作成する際に、jspをよく使われています。ある程度、JSPカスタムタグは必要になる場合もあります。
本記事は簡単にJSPカスタムタグを作る方法を解説します。概念解説
本記事はwebプロジェクトをベースにJSPカスタムタグを開発していきますよ。
また、少しだけ、JSPカスタムタグに関するの知識をまとめています。
- SimpleTagSupportクラス: インタフェースSimpleTagを実現するクラスです。JSPカスタムタグを開発する際に、よく親のクラスとしての使われています。
- doTag()メソッド : このメソッドをOverrideして実際の業務ロジックを実現する。
- tldファイル: JSPカスタムタグのタグ名、タグ属性、タグクラスなどの情報を定義する場所。
本記事は<hyman:hello name="">というカスタムタグを作成します。
タグ名はhelloですし、タグの属性はnameです。
このタグを使うと、画面上にnameのvalueを含めて出力します。作る方法
一、java側の実装
簡単に言うと、新しいjavaクラスを作成して親のSimpleTagSupportクラスを継承し、doTagメソッドを実装します。
ソースコードは下記の通りです。HymanTag.javapackage tag; import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.SimpleTagSupport; public class HymanTag extends SimpleTagSupport { private String name; @Override public void doTag() throws JspException { try { getJspContext().getOut().println("Hello, " + name); } catch (Exception e) { throw new JspException(e); } } public String getName() { return name; } public void setName(String name) { this.name = name; } }二、tldファイル作成
下記のように、タグの名、属性、クラスなどの情報を記入します。
その後、hyman.tldファイルを/webapps/WEB-INFディレクトリの下に格納します。
例:/webapps/WEB-INF/hyman.tldhyman.tld<?xml version="1.0" encoding="UTF-8" ?> <taglib> <tlib-version>1.0</tlib-version> <jsp-version>2.0</jsp-version> <short-name>Example TLD</short-name> <tag> <name>hello</name> <tag-class>tag.HymanTag</tag-class> <body-content>empty</body-content> <info>Hello tag with parameters.</info> <attribute> <name>name</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> </tag> </taglib>三、JSPカスタムタグ使用
前提:hyman.tldファイルを/webapps/WEB-INFディレクトリの下に格納します。
例:/webapps/WEB-INF/hyman.tld先ずはjspファイルを作ってJSPカスタムタグを導入し、使います。
導入:<%@ taglib prefix="hyman" uri="/WEB-INF/hyman.tld"%>
jspのソースコードは下記の通りです。
index.jsp<%@page pageEncoding="UTF-8"%> <%@ taglib prefix="hyman" uri="/WEB-INF/hyman.tld"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>JSPカスタムタグを簡単に作る方法</title> </head> <body> <h2 style="color:red;"> <hyman:hello name="hyman's custom tag"/> </h2> </body> </html>四、JSPカスタムタグの表現
ここまで、JSPカスタムタグの実装はすべて完了しまして、プロジェクトを起動して画面上で確認しましょう。
イメージが以下のように表示されます。
最後に
最後まで読んでいただき、ありがとうございます。
おかしいと思う部分は遠慮なくご指摘いただければと思います。
よろしくお願いします。
- 投稿日:2020-05-16T19:11:34+09:00
Ruby と Python と Java で解く AtCoder ABC141 D 優先度付きキュー
はじめに
AtCoder Problems の Recommendation を利用して、過去の問題を解いています。
AtCoder さん、AtCoder Problems さん、ありがとうございます。今回のお題
AtCoder Beginner Contest D - Powerful Discount Tickets
Difficulty: 826今回のテーマ、優先度付きキュー
Ruby
操作自体はシンプルで、一番値段の高い品物をキューから取り出し、割引券を一枚適用してキューに戻します。その都度、一番値段の高い品物ついて同様の操作を割引券がなくなるまで行います。
但し、次の様に単にソートするだけの実装では、TLE
になります。ruby_tle.rbn, m = gets.split.map(&:to_i) a = gets.split.map(&:to_i) a.sort_by!{|x| -x} m.times do b = a.shift b /= 2 a << b a.sort_by!{|x| -x} end puts a.inject(:+)優先度付きキューは、ソートに比べて少ない計算量で一番値段の高い品物を調べることができます。
Python ですとheapq
、Java ですとPriorityQueue
になりますが、Ruby には無いので、Ruby で Priority Queue を実装してみたい のコードをお借りして、少々修正して通しました。ruby.rbclass PriorityQueue def initialize(array = []) @data = [] array.each{|a| push(a)} end def push(element) @data.push(element) bottom_up end def pop if size == 0 return nil elsif size == 1 return @data.pop else min = @data[0] @data[0] = @data.pop top_down return min end end def size @data.size end private def swap(i, j) @data[i], @data[j] = @data[j], @data[i] end def parent_idx(target_idx) (target_idx - (target_idx.even? ? 2 : 1)) / 2 end def bottom_up target_idx = size - 1 return if target_idx == 0 parent_idx = parent_idx(target_idx) while (@data[parent_idx] > @data[target_idx]) swap(parent_idx, target_idx) target_idx = parent_idx break if target_idx == 0 parent_idx = parent_idx(target_idx) end end def top_down target_idx = 0 while (has_child?(target_idx)) a = left_child_idx(target_idx) b = right_child_idx(target_idx) if @data[b].nil? c = a else c = @data[a] <= @data[b] ? a : b end if @data[target_idx] > @data[c] swap(target_idx, c) target_idx = c else return end end end # @param Integer # @return Integer def left_child_idx(idx) (idx * 2) + 1 end # @param Integer # @return Integer def right_child_idx(idx) (idx * 2) + 2 end # @param Integer # @return Boolent def has_child?(idx) ((idx * 2) + 1) < @data.size end end n, m = gets.split.map(&:to_i) a = gets.split.map(&:to_i) e = a.map{|x| -x} b = PriorityQueue.new(e) m.times do c = b.pop b.push(-(-c / 2)) end ans = 0 while b.size > 0 ans -= b.pop end puts ansこれでもギリギリです。
Python
python.pyimport heapq import math n, m = map(int, input().split()) a = [-1 * int(i) for i in input().split()] heapq.heapify(a) for _ in range(m): b = heapq.heappop(a) heapq.heappush(a, math.ceil(b / 2)) print(-1 * sum(a))Pythonの
heapq
は最小値を取るものですので、マイナスの符号を付けて入れる必要があります。
また、//
によるマイナスの割り算は絶対値の大きい方の値を返すので、ここではceil
を使用しています。Java
java.javaimport java.util.*; class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); int N = Integer.parseInt(sc.next()); int M = Integer.parseInt(sc.next()); PriorityQueue<Long> A = new PriorityQueue<>(Collections.reverseOrder()); for (int i=0; i<N; i++) { A.add(Long.parseLong(sc.next())); } sc.close(); for (int i=0; i<M; i++) { long new_price = (long)A.poll()/2; A.add(new_price); } long sum = 0; for (long a : A) { sum += a; } System.out.println(sum); } }PriorityQueue<Long> A = new PriorityQueue<>(Collections.reverseOrder());Java は
reverseOrder()
で最大値に対応しています。
Ruby Python Java コード長 (Byte) 1933 230 673 実行時間 (ms) 1981 163 476 メモリ (KB) 14004 14536 50024 まとめ
- ABC 141 D を解いた
- Ruby に詳しくなった
- Python に詳しくなった
- Java に詳しくなった
参照したサイト
Ruby で Priority Queue を実装してみたい
[ruby] Priority Queueの実装
- 投稿日:2020-05-16T18:03:02+09:00
C++/JavaとWin/Linuxの組み合わせにおけるビルドについて整理する
はじめに
本記事では、C++とJavaという2つのプログラミング言語について、ビルド周りの知識を整理します。
具体的には、各言語における「基本的なビルド方法」、「共有ライブラリの利用方法」、「ビルドツール」について整理します。
その際、WindowsとLinuxとで相違がある場合には、その点についても触れます。本記事の投稿目的は、投稿者自身が知識を整理することであり、内容に間違いが含まれる可能性があります。
間違いを見つけた方はコメントなどで指摘をお願いします。基本的なビルド方法
本節では、コマンドラインからビルドを行う基本的な方法について整理します。
「ビルド = コンパイル + リンク」として説明します。C++
main.cpp, hoge.cpp, hoge.hという3つのファイルからプロジェクトが構成されるとします。
コンパイルは、g++ -c main.cpp hoge.cppリンクは、
g++ main.o hoge.oというコマンドで行うことができ、実行ファイル(Linuxではa.out, Windowsではa.exe)が生成されます。
-cは「コンパイルのみを行う」オプションであり、このオプションを省くと一気に実行ファイルの生成まで済ませることができます。g++ main.cpp hoge.cppJava
Main.java, Hoge.javaという2つのファイルからプロジェクトが構成されるとします。
コンパイルは、javac Main.javaというコマンドで行うことができ、Main.classとHoge.classというファイルが生成されます。
依存関係は自動解決してくれるので、Hoge.javaを引数に加える必要はありません。
また、リンクは実行時に行うため、コマンドで明示的にリンクを行う必要はありません。
実行は、java Mainというコマンドで行います。
共有ライブラリの利用方法
共有ライブラリの利用方法についてOpenCVを例に説明します。
OpenCVを例とした理由は投稿者にとって身近なライブラリだというだけなので、OpenCVをよく知らない方は別のライブラリに置き換えて読んでいただければと思います。C++(Linux)
インクルードパス、ライブラリパス、共有ライブラリファイルの3つをg++のオプションで指定します。
例えば、インクルードパス:/usr/local/include/opencv4, ライブラリパス:/usr/local/lib, 共有ライブラリファイル:libopencv_core.soの場合は、以下のようにします。g++ main.cpp -I/usr/local/include/opencv4 -L/usr/local/lib -lopencv_coreC++(Windows)
Windowsの場合、-lオプションでインポートライブラリ(.libファイル)を指定します。
インポートライブラリファイル名がopencv_world420.libの場合、以下のコマンドでビルドします。g++ main.cpp -IC:/opencv/build/include -LC:/opencv/build/x64/lib -lopencv_world420実行時は.libファイルと対となる.dllファイル(共有ライブラリ本体)にパスを通す必要があります。
.libファイルと.dllファイルの関係については、以下のサイトが参考になるかと思います。
http://exlight.net/devel/windows/dll/windll.htmlJava(Linux)
Javaでは、jarファイルが上記のインポートライブラリのような役割を担っているため、以下のように、classpathにjarファイルを指定してビルドを行います。
javac -cp /usr/local/share/java/opencv4/opencv-430.jar Main.javaそして、実行時には共有ライブラリ本体である.soファイルへのパスをjava.library.pathとして与える必要があります。
(説明は省略しますが、ソースコード上でもSystem.loadLibrary関数で.soファイルをロードする必要があります)
また、実行時にもclasspathが必要で、かつ、実行したいclassファイルのパス(.)も指定する必要があります。java -cp .:/usr/local/share/java/opencv4/opencv-430.jar -Djava.library.path=/usr/local/share/java/opencv4 Main詳細は以下のページが参考になるかと思います。
https://stackoverflow.com/questions/28727766/how-to-add-an-external-library-opencv-jar-file-to-the-java-build-path-from-thJava(Windows)
Windowsの場合、.soファイルの代わりに.dllファイルをロードし、classpathの区切り文字が":"ではなく";"となります。
javac -cp C:/opencv/build/java/opencv-430.jar Main.java java -cp .;C:/opencv/build/java/opencv-420.jar -Djava.library.path=C:/opencv/build/java/x64 Mainビルドツール
本節では、C言語のビルドツールであるCMakeとJavaのビルドツールであるGradleを紹介します。
CMake
CMakeの説明をする前に、makeについて触れておきます。
makeはビルドツールの草分け的存在であり、Makefileにビルドルールを書いておくことで、ビルドを自動化できます。CMakeはC言語のソースコードをインストールする際に、CMake→make→make installという手順でよく利用されます。
この各手順では、以下のような作業がなされています。
- CMake : CMakeLists.txtに書かれたルールにしたがって、Makefileを作成する
- make : Makefileに書かれたルールに従ってビルドする
- make install : Makefileに書かれたルールに従って、ビルド済ファイルのコピーなどを行う
OpenCVを使う例だと、CMakeLists.txtは以下のような内容になります。
(pkg_configを利用しない場合)cmake_minimum_required(VERSION 3.10) project(Main) add_executable(Main main.cpp) target_include_directories(Main PRIVATE /usr/local/include/opencv4 ) target_link_libraries(Main PRIVATE /usr/local/lib/libopencv_core.so )Gradle
Gradleについては自分用のメモとして、build.gradleの中身のみ載せておきます。
apply plugin: 'java' dependencies { compile files("/usr/local/share/java/opencv4/opencv-430.jar") } test { systemProperty 'java.library.path', '/usr/local/share/java/opencv4' }おわりに
本記事では、C++とJavaという2つのプログラミング言語について、ビルド周りの知識を整理しました。
元々はIDEで隠蔽されているビルド処理などについても触れるつもりだったのですが、必要性がいまいち分からなくなってきたので、この辺りで終わりたいと思います。
結果的に20年前の記事みたいな内容になってしまいましたが、参考にしていただければ幸いです。
- 投稿日:2020-05-16T17:27:57+09:00
eclipse.iniに-vmを指定する方法
Eclipseを実行するJVMをeclipse.iniに
-vm
オプションで指定する。
が、CentOSでOpenJDK11のjavaw.exe
がなかった、ので調べてみた。
- 参考 : Eclipse を起動する Java VM を指定する :Tips & FAQ | arbk-works Blog
- 凡例 :
JAVA_HOME
: javaのインストールディレクトリ実行ファイルのパスを指定する方法
これまでこの方法でしか指定したことがなかった、しかも
javaw
がWindows用って知らなかった。javawはどうもWindowsのために用意されたコマンドみたいですね。Windows環境向けの記事でjavaw.exeを使う解説があった場合、Linux/Mac環境では素直にjavaコマンドに置き換えましょう。
Linux/Mac に javaw コマンドがない…だと…; Windowsの場合 -vm JAVA_HOME¥bin¥javaw.exe ; Linuxの場合(CentOS) -vm JAVA_HOME/bin/javaディレクトリパスを指定する方法
; Windowsの場合 -vm JAVA_HOME\bin ; Linuxの場合(CentOS) -vm JAVA_HOME/bin共有ライブラリのパスを指定する方法
Java11より前だと若干パスが違うようだ。
OpenJDK 11 - Change inlibjvm.so
default location ? · Issue #137 · LeeKamentsky/python-javabridge; Windowsの場合 -vm JAVA_HOME\bin\client\jvm.dll ; または JAVA_HOME\bin\server\jvm.dll ; Linuxの場合(CentOS) -vm JAVA_HOME/bin/libjvm.so ; または JAVA_HOME/lib/server/libjvm.so
- 投稿日:2020-05-16T16:44:10+09:00
【AtCoderProblem-ABC001】C-風力観測をJavaでやる【コード】
はじめに
Java-Silverの勉強がひと段落ついたので、無料でアウトプットできる「AtCoderProblem」をやってみました。備忘録がてらQiitaに残しておこうと思います!
問題
問題文が長いのでこちらをどうぞ
コード
WindObservation.javaimport java.util.Scanner; public class WindObservation { public static void main(String[] args) { Scanner sc = new Scanner(System.in); double wind_direction = sc.nextInt(); wind_direction = wind_direction / 10; double Wind = sc.nextInt(); Wind = Wind / 60; Wind = ((double)Math.round(Wind * 10))/10; System.out.println(Direction(wind_direction)); System.out.println(Amount(Wind)); } public static String Direction(double direction){ String master = ""; if((11.25 <= direction) && (direction < 33.75)){ master = "N"; }else if((33.75 <= direction) && (direction < 56.25)){ master = "NE"; }else if((56.25 <= direction) && (direction < 78.75)){ master = "ENE"; }else if((78.75 <= direction) && (direction < 101.25)){ master = "E"; }else if((101.25 <= direction) && (direction < 123.75)){ master = "ESE"; }else if((123.75 <= direction) && (direction < 146.25)){ master = "SE"; }else if((146.25 <= direction) && (direction < 168.75)){ master = "SSE"; }else if((168.75 <= direction) && (direction < 191.25)){ master = "S"; }else if((191.25 <= direction) && (direction < 213.75)){ master = "SSW"; }else if((213.75 <= direction) && (direction < 236.25)){ master = "SW"; }else if((236.25 <= direction) && (direction < 258.75)){ master = "WSW"; }else if((258.75 <= direction) && (direction < 281.25)){ master = "W"; }else if((281.25 <= direction) && (direction < 303.75)){ master = "WNW"; }else if((303.75 <= direction) && (direction < 326.25)){ master = "NW"; }else if((326.25 <= direction) && (direction < 348.75)){ master = "NNW"; }else{ master = "N"; } return master; } public static int Amount(double wind){ int i = 0; if((0.0 <= wind) && (wind < 0.2)){ i = 0; }else if((0.3 <= wind) && (wind < 1.5)){ i = 1; }else if((1.6 <= wind) && (wind < 3.3)){ i = 2; }else if((3.4 <= wind) && (wind < 5.4)){ i = 3; }else if((5.5 <= wind) && (wind < 7.9)){ i = 4; }else if((8.0 <= wind) && (wind < 10.7)){ i = 5; }else if((10.8 <= wind) && (wind < 13.8)){ i = 6; }else if((13.9 <= wind) && (wind < 17.1)){ i = 7; }else if((17.2 <= wind) && (wind < 20.7)){ i = 8; }else if((20.8 <= wind) && (wind < 24.4)){ i = 9; }else if((24.5 <= wind) && (wind < 28.4)){ i = 10; }else if((28.5 <= wind) && (wind < 32.6)){ i = 11; }else{ i = 12; } return i ; } }コードが冗長かつ変数名がちゃんちゃらおかしいのはお許しください。「まずは動けばいいだろう」という精神の元コードを書いて、これからコードの質を上げていきたいと思います。
$ java WindObservation > 2750 > 628 W 5$ java WindObservation > 1687 > 1029 SSE 8動いてはいるのでセーフ!!!
暫くの間AtCoderProblemでアウトプットしていきます。コロナが落ち着いたらJava-Silverを取ってGoldにも挑戦していきたいです。
- 投稿日:2020-05-16T16:31:34+09:00
【超基礎】PythonとJavaとJavaScriptを比較します(変数, if文, while文, for文)
目的
- 今週から本配属だった
- 結局Javaを勉強することになった
- 記憶が新しいうちにJavaとPythonでちがったことまとめておこう
- ついでに少し勉強したことがあるJavaScriptも引っ張り出して比較しよう
この記事で比較する内容
- 変数
- if文
- while文
- for文
変数
Python
- データ型を宣言する必要はありません
- 変数の中身によって勝手に型を指定してくれます
- セミコロンもいりません
asobi.pypy_str = "Python" py_int = 23 py_float = 23.5 # データ型確認 print(type(py_str)) print(type(py_int)) print(type(py_float))コンソール<class 'str'> <class 'int'> <class 'float'>
- 変数のデータ型を書きかえることもできます
asobi.pypy_str = "Python" py_int = 23 print(type(py_str)) py_str = 10 print(type(py_str)) py_cal = py_str * py_int print(py_cal)コンソール<class 'str'> <class 'int'> 230Java
- データ型を宣言しなければなりません
- float型もありますが、主にdouble型を使うようです
- 直接的にデータ型を確認する方法は見つかりませんでした(instanceofというメソッドがあるようですが、intやdoubleには使えません)
- Javaのデータ型についてはこちらのサイトがわかりやすいです
- セミコロンを忘れずに
asobi.javapublic class asobi { public static void main(String[] args) { String javaStr = "Java"; int javaInt = 23; double javaFloat = 23.5; } }JavaScript
- データ型の宣言は必要ありませんが、変数名の前にvarをつける必要があります
- int型やfloat型ではなくどちらもnumber型になるんですね…
- セミコロンもいります
asobi.html<html> <script> var jsStr = "JavaScript"; var jsInt = 23; var jsFloat = 23.5; // データ型確認 console.log(typeof(jsStr)); console.log(typeof(jsInt)); console.log(typeof(jsFloat)); </script> </html>コンソールstring number number
if文
Python
- if, elif, elseが使われます
- 条件の末尾に:を付けます
- 実行する文のインデントは必須です
asobi.pyage = 23 if age >= 65: print("定年です") elif age >= 20: print("成人です") else: print("未成年です")Java
- if, else if, elseが使われます
- それぞれの条件を(), 実行する文を{}で囲います
asobi.javapublic class asobi { public static void main(String[] args) { int age = 23; if (age >= 65) { System.out.println("定年です"); }else if (age >= 20) { System.out.println("成人です"); }else{ System.out.println("未成年です"); } } }JavaScript
- if, else if, elseが使われます
- それぞれの条件を(), 実行する文を{}で囲います
- if文の形自体はJavaと同じですね
asobi.html<html> <script> var age = 23; if (age >= 65) { console.log("定年です"); }else if (age >= 20){ console.log("成人です"); }else{ console.log("未成年です"); } </script> </html>while文
- それぞれの言語で以下のようにコンソール表示させてみましょう
コンソール0です 1です 2です 3です 4です
Python
- int型であるiと文字列をそのままつなげることができないので、iをstr型にします
asobi.pyi = 0 while i < 5: print(str(i) + "です") i += 1Java
- 例によって条件を()、実行文を{}で囲います
- インクリメントでi++という形を使うことができます
asobi.javapublic class asobi { public static void main(String[] args) { int i = 0; while (i < 5){ System.out.println(i + "です"); i++; } } }JavaScript
- 例によってwhile文の形自体はJavaと同じです
asobi.html<html> <script> var i = 0; while (i < 5){ console.log(i + "です"); i++; } </script> </html>for文
- それぞれの言語で以下のようにコンソール表示させてみましょう
コンソール0です 1です 2です 3です 4です
Python
- iが0から4までの5回の範囲で繰り返されます
asobi.pyfor i in range(5): print(str(i) + "です")Java
- for文の繰り返し条件が(初期値; 範囲; 増減)で表されます
- 初期値を格納する変数はデータ型を宣言する必要があります
asobi.javapublic class asobi { public static void main(String[] args) { for (int i = 0; i < 5; i++){ System.out.println(i + "です"); } } }JavaScript
- 例によってfor文の形自体はJavaと同じです
- 初期値を格納する変数名の前にはvarを付ける必要があります
asobi.html<html> <script> for (var i = 0; i < 5; i++){ console.log(i + "です"); } </script> </html>感想
- 複数言語を勉強するとき、比較しながら勉強すると覚えやすい
- JavaとJavaScript、インドとインドネシア並にちがうといいつつ、書き方結構似てるんだな
- やっぱりPythonがいちばん書きやすい!
- 投稿日:2020-05-16T15:00:27+09:00
脳死のうえ CentOS8 で OpenJDK 使って HelloWorld
題名の通りやってみます。
環境確認
OS はインストールして yum update 実行済みです。
# cat /etc/redhat-release CentOS Linux release 8.1.1911 (Core) #
Java インストール
# yum install java # yum install java-devel # which java /usr/bin/java # java -version openjdk version "1.8.0_252" OpenJDK Runtime Environment (build 1.8.0_252-b09) OpenJDK 64-Bit Server VM (build 25.252-b09, mixed mode) #初期セットアップ
パスを通します。
/etc/profile の末尾に dirname で返って来た文字列より、最後の /jre/bin を抜いて記載します。# dirname $(readlink $(readlink $(which java))) /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.252.b09-2.el8_1.x86_64/jre/bin # vi /etc/profile # tail -5 /etc/profile export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.252.b09-2.el8_1.x86_64 export PATH=$PATH:$JAVA_HOME/bin export CLASSPATH=.:$JAVA_HOME/jre/lib:$JAVA_HOME/lib:$JAVA_HOME/lib/tools.jar # source /etc/profile #スクリプト作成
HelloWorld のスクリプトを記載します。
# pwd /root # mkdir -v test1 mkdir: ディレクトリ 'test1' を作成しました # cd test1 # vi HelloWorld.java # cat HelloWorld.java class HelloWorld { public static void main(String[] args ){ System.out.println("HelloWorld"); } } #コンパイル
# javac HelloWorld.java # ll 合計 8 -rw-r--r--. 1 root root 411 5月 11 01:17 HelloWorld.class -rw-r--r--. 1 root root 111 5月 11 01:17 HelloWorld.java #実行
# java HelloWorld HelloWorld #うまくいきました。
- 投稿日:2020-05-16T12:24:29+09:00
BindingResultの結果にフィルターを掛ける【Spring】
対象読者
・Springframeworkで、Validationの実装を行っているが、BindingResultの結果に対してフィルターを掛けたい!
・newとupdateで、共通のフォームを使いたい!対象物の確認
View
シンプルにSpringタグを用いて、userFormをmodelAttributeに定義し、inputタグ、labelタグが書いてあります。更新側も全く、同じ構造です。
Form
UserForm.java@Data @ConfirmMail(field="mail", groups = ValidGroup3.class) @ConfirmPassword(field="password", groups = ValidGroup4.class) public class UserForm { private Long id; @NotBlank(groups = ValidGroup1.class) @Size(min=6, max=20, groups = ValidGroup2.class) @Pattern(regexp="^[a-zA-Z0-9]+$", groups = ValidGroup3.class) private String account; @NotBlank(groups = ValidGroup1.class) @Size(min = 6, max = 12, groups = ValidGroup2.class) @Pattern(regexp="^[a-zA-Z0-9]+$", groups = ValidGroup3.class) private String password; @NotBlank(groups = ValidGroup1.class) @Size(min = 6, max = 12, groups = ValidGroup2.class) @Pattern(regexp="^[a-zA-Z0-9]+$", groups = ValidGroup3.class) private String confirmPassword; @NotBlank(groups = ValidGroup1.class) @Size(min=1, max=10, groups = ValidGroup2.class) private String name; @NotBlank(groups = ValidGroup1.class) private String birthday; @NotBlank(groups = ValidGroup1.class) @Email(groups = ValidGroup2.class) private String mail; @NotBlank(groups = ValidGroup1.class) @Email(groups = ValidGroup2.class) private String confirmMail; }各項目にBlankや正規表現、確認用パスワード、確認用メールアドレス諸々のvalidationが定義してあります。
Controller
SignupController@PostMapping("/signup") String createUser(@ModelAttribute("userForm") @Validated(GroupOrder.class) UserForm userForm, BindingResult result, RedirectAttributes redirectAttributes) { if(result.hasErrors()) { return "users/signup/new"; } mailAuthUserService.authUserbyMail(userForm); redirectAttributes.addFlashAttribute("resultMessage", "メールが送信されました。メールボックスを確認して下さい。"); return "redirect:/login"; }これらを更新側にも流用することを考えたときに、問題点として、
(DBからそのままの情報を持ってくるとしてます)・パスワードを更新するときに、パスワードが不可逆的にハッシュ化されているので、更新側のフォームにはDBから持ってきた情報をそのまま入れることは出来ない(更新時にハッシュ化されたものをハッシュ化することになるため)。パスワードは空にする必要がある。
・更新したくないときに、確認用パスワード・確認用メールアドレスの項目が空っぽなので、validationが掛かる。いちいち項目を入れるのはめんどくさい。なので、めちゃめちゃvalidationが邪魔になります。
対策
BindinResultのerror項目を一部消せないか?
Google先生に聞いたら、どうやらエラー項目を消すメソッドは
BindinResult
には定義されていないとのことで出来ないらしい。消すのではなく、オブジェクトを再定義
すれば、結果的に消すことが出来そう!フィルターメソッド
UserService.javapublic BindingResult filteringBindingResult(BindingResult result, UserForm userForm) { UserDto userDto = findById(userForm.getId()); //・・・1 BindingResult tmpResult = new BeanPropertyBindingResult(userForm, "userForm");//・・・2 if(StringUtils.isEmpty(userForm.getPassword()) && userDto.getMail().equals(userForm.getMail())) { //・・・3 for(FieldError fieldError : result.getFieldErrors()) { //・・・4 if(fieldError.getField().equals("confirmMail") || fieldError.getField().equals("confirmPassword") || fieldError.getField().equals("password")) { continue; } tmpResult.addError(fieldError); } return tmpResult; } if(StringUtils.isEmpty(userForm.getPassword())) { for(FieldError fieldError : result.getFieldErrors()) { if(fieldError.getField().equals("confirmPassword") || fieldError.getField().equals("password")) { continue; } tmpResult.addError(fieldError); } return tmpResult; } if(userDto.getMail().equals(userForm.getMail())) { for(FieldError fieldError : result.getFieldErrors()) { if(fieldError.getField().equals("confirmMail")) { continue; } tmpResult.addError(fieldError); } return tmpResult; } return result; }引数・・・
BindingResult result(エラー結果が格納されている)
、UserForm userForm(View側で入力された項目)
戻り値・・・
BindingResult
1・・・DBからユーザー情報を持ってきて、メールアドレスに変更が無いかチェックするために定義
2・・・BindingResultはインターフェースなので、実装を持っていません。BeanPropertyBindingResultはBindingResultのデフォルトの実装になっているとのこと。そのため、このオブジェクトにフィルターを掛けたerror項目を代入していく必要があるため定義する。
Springframework
3・・・今回の実装は、更新の必要があるときは、valaidationを効かせたいので、パスワードが空とメールアドレスに変更が無いかをチェックして、trueの時は、valaidationにフィルターを掛ける
、falseの時は、通常のvalaidationを掛ける
といった実装にしています。
4・・・コントローラーで、貰ったBindingResultを一個ずつ取り出し、フィルターを掛けたい項目にマッチするものはcontinueでスキップ、それ以外は、tmpResultに代入しています。Controller
UserController.java@PostMapping("/{id}/edit") String edit(Model model, @ModelAttribute("userForm") @Validated(GroupOrder.class) UserForm userForm, BindingResult result) { BindingResult filteringBindingResult = userService.filteringBindingResult(result, userForm); //・・・1 if(filteringBindingResult.hasErrors()) { model.addAttribute("org.springframework.validation.BindingResult.userForm", filteringBindingResult); //・・・2 return "users/edit"; } userService.update(userForm); return "redirect:/user/mypage"; }1・・・先ほど定義さいたメソッドの戻り値を
filteringBindingResult
に格納しています。
2・・・ここがとっても苦戦したところなのですが、最初は、BindingResult resultの参照を書き換えれば、すんなり実装が出来るかと思っていたのですが、どうやら、このコントローラーが呼ばれた時点で、Modelにフィルターを掛ける前のBindingResultの結果が格納されていました。なので、そのオブジェクトを上書きしてあげる必要があります。"org.springframework.validation.BindingResult.userForm
はModel自体をプリントデバッグして調べました…感想
ベストプラクティスかどうかは正直なところ分かりません。
シンプルに更新用と新規登録用でFormを分けた方が、後から見たときに見やすいかもしれませんが、Formを共通にすることで、エラーメッセージも共通化出来るので、僕はこちらのやり方を選定しました。参考になった方が居れば、幸いです。
- 投稿日:2020-05-16T10:40:13+09:00
Mapの使い方
- 投稿日:2020-05-16T03:10:01+09:00
JUnitをつくってみる。
動機
「何かを勉強するには作ってみるのが良い」という言葉を聞いたことがあるでしょうか?
業務でJUnitを利用することはあっても、具体的にどう作られているのかは知りませんでした。
会社を辞めて転職活動中のため、時間があるので、実際に独自バージョンのJUnitを作ってみることにしました。参考にするJUnitのバージョン
JUnitのソースコードを探したのですが、JUnit4とJUnit5しか見つかりませんでした。(公式以外なら、JUnit4とJUnit5以外にも存在します。)
JUnit4のソースコードのjunit4/src/main/java/
配下には2つのディレクトリが存在します。
- junit
- org/junit
中をみてみると、どうも別々のソースが格納されているみたいです。
とりあえずjunit
の方を参考にすることにしました。理由は下記の通りです。
- Kent Beckの"Test Driven Development: By Example"に近い
- コードがシンプル
JUnitの大まかな流れ
どのように調査したか
最初はjunitのソースを1つ1つ読んでいましたが、下記の理由で主要な流れを把握するだけにしました。
- クラスの量がそこそこある。
- クラスの関係を整理するために、ツールが欲しくなったがいいのがなかった。
- ずっと読んでいるだけだとしんどい。
JUnitの実行
JUnitは下記の流れで実行します。業務で使用する場合は、統合開発環境がよしなに実行してくれます。
詳細はJUnitの公式ページでご確認ください。
- テストクラスを作成する。
- テストクラスをコンパイルする。
- TestRunnerを実行する。
テストクラスの作成方法および、テストクラスのコンパイルの説明は省略します。
TestRunnerを実行する。
下記のようなコマンドを実行します。
java -cp 必要なライブラリ テストを実行するクラス(JUnit3ならjunit.textui.TestRunner) テスト対象クラス
重要なことは、テストを実行するクラス及び、テスト対象クラスを指定していることです。JUnitの解析
JUnitはどのように処理を実行しているのでしょうか?
何か特別な仕組みが存在するのでしょうか?
結論から言うと、何か難しいことをしているわけではなくReflectionを使用しているだけです。エントリポイントをみてみる
テストを実行するクラスをしていることから、そのクラスがエントリポイントだとわかります。
実際にjunit4/src/main/java/junit/textui/TestRunner.java
を見てみるとmain()
メソッドが存在します。
つまりjunitは単純にmain()
メソッドを読んでいるだけです。何か特別なことをしているわけではありません。テストメソッドをどのように実行しているか
エントリポイントはわかりましたが、ではどのようにしてテストメソッドを実行しているのでしょうか?
先ほどのmain()
メソッドからはいろいろメソッドが呼ばれていますが、実際にメソッドを実行しているのは下記となります。
junit4/src/main/java/junit/framework/TestCase.javaのrunTest()メソッド
中身をみてみるとmethod.invoke()
しているだけです。(methodはMethod型)
これはReflectionの機能です。Reflectionとは
Reflectionとは簡単に言うと、実行時にクラスやメソッドの情報を読み取ったり実行したりできる機能です。
結構昔ですが、黒魔術として有名でした。
Reflectionの長所は、柔軟にクラスやメソッドを操作できることです。
短所は長所の裏返しです。
実行時にJVMで情報を集めてくる必要があるため、実行速度が遅くなります。またprivateメソッドも実行できてしまうため、使用方法を間違えるとエラいことになります。実装をしてみる
クラス構成
上記でJUnitを作ってみるのに、最低限必要な知識は得られました。
最低限の機能を実施するとして、必要なクラスを考えてみました。
- Assert(assertEqualsを提供する)
- TestResult(テスト結果を保持する)
- TestRunner(エントリポイント)
- TestCase(テストクラスのスーパークラス)
コード
GitHubを参照してください。
なお現在は、クラス構成が多少異なりますやってみてどうだったか
やってみてよかった点は下記の通りです。
- クラス設計とか久しぶりで、頭の体操になった。
- なんとなくコードリーティングするよりモチベーションが保ちやすい
- 投稿日:2020-05-16T00:20:12+09:00
Javaの怒られコード5選と修正案
はじめに
本記事では、Javaで怒られたコードを公開します。
・横に長すぎる
・処理の多すぎるStream
・無意味なラッパークラス
・returnが無駄に遅いこの4点を書いていきます。
Javaに限らず、色々な言語に存在する文法からピックアップしました。①横に長すぎる。
ちょっとできる気がしてきたときにやりがちなコードです。
一例としては、無駄な三項演算子の使用を挙げます。BatCodes.java/** * * 怒られコード①:横に長すぎる * 怒られ度:★★★☆☆ * 怒られポイント:読み辛さがMax */ private boolean checkBadCode(String str) { return str.equals("abc") ? true : str.endsWith("c") ? true : str.startsWith("a") ? true : false ; }こういった場合は素直にif else文を使いましょう。
GoodCodes.java/** * 怒られコード①修正案 */ private boolean checkGoodCode(String str) { if(str.equals("abc")) { return true; } if (str.startsWith("a") || str.endsWith("c")) { return true; } return false; }三項演算子は便利ですが、マイルールないしコーディング規約にのっとることを強くお勧めします。
ちなみに、私はコードの文字数が70文字を超えず、2回以上判定を行わないときに使います。GoodCodes.javaprivate boolean checkBadCode(String str) { // これくらいなら読みやすい return str.equals("abc") ? true : false }②処理の多すぎるStream
たとえばこんなforEach()
BatCodes.java/** * 怒られコード②:処理の多すぎるStream * 怒られ度:★★★☆☆ * 怒られポイント:何でもforEach()を使えば言い訳ではない */ private void streamBadCode(List<String> targetList) { targetList.stream().forEach((String str) -> {str.concat("a"); if(str.startsWith("a") ){ System.out.print(str); };}); }適した中間操作を選ぶようにしましょう。だいたい用意されています。
上記の例であれば、拡張for文を利用するのも手です。GoodCodes.java/** * 怒られコード②修正案 */ private void streamBadCode(List<String> targetList) { targetList.stream().map(s -> s.concat("a")).filter(s -> s.startsWith("a")).forEach(System.out:: print); }GoodCodes.java/** * 怒られコード②修正案 */ private void streamBadCode(List<String> targetList) { for (String target : targetList) { target.concat("a"); if(target.startsWith("a")) { System.out.print("a"); } } }
ちなみに余談ですがStreamの使用を絶対に許さない職場で働いている場合退職を検討したほうがいいです。③無意味なラッパークラス
これも慣れてきたころにやりました。
どうせAutoBoxingで処理すんだし、最初からラッパークラスで宣言すればよくね?
という浅はかな考えです。BatCodes.java/** * 怒られコード③:無意味なラッパークラス * 怒られ度:★★★★☆ * 怒られポイント:無駄なメモリ消費 */ private Integer literalBadCode(Integer i) { Integer firstValue = 10; Integer secondValue = 20; Integer sumValue = i + firstValue + secondValue; return sumValue; }ラッパークラスは、メモリ領域をプリミティブ変数よりも約4倍ほど使用するので、
メモリリークを起こしてしまいかねません。
必要なときだけ使用しましょう。
上記のコードだと。returnで直接返せばいいんですけどね・・・GoodCodes.java/** * 怒られコード③:無意味なラッパークラス * 怒られ度:★★★★☆ * 怒られポイント:無駄なメモリ消費 */ private int literalBadCode(int i) { return i + firstValue + secondValue; }⑤returnが無駄に遅い
return句はメソッドの最後に記述するのがクールと思っていたときのコードです。
それは間違いです。BatCodes.javaprivate boolean checkBatCode2(String target) { boolean japanFlg = false; boolean englishflg = false; boolean nonflg = true; switch(target) { case "Hello" : englishflg = true; break; case "こんにちは" : japanFlg = true; break; default : nonflg = false; break; } if(japanFlg) { return japanFlg; } else if (englishflg) { return englishflg; } else { return nonflg; } }長すぎて処理を読む気にもならないですし、結果が確定したら即座にreturnしてあげましょう。
そうすることで、コードが短く簡潔になるので不必要なバグを埋め込んだり後に読む人がいらいらしなくてすみます。BatCodes.javapublic class GoodCodes { public static final String LANGEAGE_ENG = "Hello"; public static final String LANGEAGE_JP = "こんにちは"; /** * 怒られコード④修正案 */ private boolean checkGoodCode2(String target) { if(target.equals(LANGEAGE_ENG) || target.equals(LANGEAGE_JP)) { return true; } return false; }終わりに
今回は自分が過去にやらかしたものや、コードレビューで飛んできたものをいくつかピックアップしました。
似たような記事でこの記事より質の良いものはたくさんありますが、なかなかレアなものもあったのではないかと思います。
- 投稿日:2020-05-16T00:12:08+09:00
Javaプログラミングの全て
1.Javaプログラミングの全て
superHello.HelloJava.java/** @author Ryome */ /** Javaプログラミングの全て(カプセル化・継承・ポリモーフィズム・インターフェース・オーバーロード・オーバーライド・ * List・try-catch・例外・Javadoc)*/ package superHello; import java.util.Arrays; class HelloException extends Exception{private static final long serialVersionUID=1L;HelloException(String m){super(m);}} interface SuperHelloWorld {public abstract String SuperHello() throws HelloException;} abstract class Hello implements SuperHelloWorld{final String HELLO="Helloworld";private String hello; Hello(){this.hello=HELLO;}Hello(String hello){this.hello=hello;}public abstract String SuperHello() throws HelloException; public String getHello(){return hello;}} class HelloWorld extends Hello {HelloWorld(){super();}@Override public String SuperHello() throws HelloException{return getHello();}} public class HelloJava{ /** mainメソッド @param args 使用しない */ public static void main(String[] args){ try {Arrays.asList(new HelloWorld().SuperHello()).forEach(System.out::println);}catch(HelloException e){}finally{} } }2.関連