20210911のJavaに関する記事は5件です。

Java Silver 5章 クラス定義とオブジェクトの生成・使用

クラスとインスタンス クラスの宣言 【装飾子】 class クラス名 {}  コンパイラして作成されるクラスファイルは定義したクラス名が付く  インスタンス化して初めてメモリ領域が確保される メンバの宣言 メンバはクラス、またはオブジェクトが持つ変数、メソッドを指す メンバ変数の種類 ・インスタンス変数:オブジェクト毎に存在する変数 ・static変数   :クラスに対して存在する変数 インスタンス変数の宣言 【装飾子】 データ型 インスタンス変数名; メソッドの定義 【修飾子】戻り値の型 メソッド名(引数リスト){} インスタンス化 データ型 変数名 = new クラス名(); 左辺で変数を宣言(変数はあくまで参照先を示すだけの参照型データ) 右辺で参照先であるオブジェクト(実体)を作成 呼び出し方 /* クラス宣言 クラス名 Employee ・static変数名id ・メソッド名 setId() 引数:int i をidに代入する        getId() retun */ //クラス宣言 class Employee { int id; //メンバ変数を宣言 //メソッド宣言  //返り値の型(void:返り値なし) 引数(int i;数値型の引数あり) void setId(int i) { id = i; } //返り値の型(int:数値) 引数(なし) int getId() { return id; } } public class Main { public static void main(String[] args) { Employee a = new Employee(); // Aさん用オブジェクトを作成 a.setId(100); // IDをセットするメソッドを呼び出す Employee b = new Employee(); // Bさん用オブジェクトを作成 b.setId(200); // IDをセットするメソッドを呼び出す System.out.println("Aさん: " + a.getId()); // IDの表示 System.out.println("Bさん: " + b.getId()); // IDの表示 } } 変数のスコープ メンバ変数:クラス定義の直下で宣言される変数       有効範囲(スコープ)はクラス内       宣言した時点でデフォルト値に初期化される(配列と同様) ローカル変数:if文やfor文などのブロック内やメソッドの引数リストで宣言している変数        スコープはブロック内        明示的な初期化が必要 コンストラクタ インスタンス化した時に最初に呼び出されるブロックを設定できる コンストラクタの条件 ・名前がクラス名と同じ ・戻り値を持たない(戻り値宣言もなし) ・必要に応じて引数を受け取る new コンストラクタ名(引数リスト){} コンパイルの際、コンストラクタが定義されていない場合デフォルトコンストラクタが作成される デフォルトコンストラクタ ・引数をもたない ・実装は空({}に何も記述されない) オーバーロード 複数のコンストラクタを定義する事ができる 名前が同一であるため、区別するために引数の並び、データ型、数が異なる class Test { void myPrint(){ System.out.println("myPrint()"); } //引数なし void myPrint(String s) { System.out.println("myPrint(String s)"); } //引数:文字列型の変数s void myPrint(int a) { System.out.println("myPrint(int a)"); } //引数:数値型の変数a void myPrint(int a, int b){ //引数:数値型aとb System.out.println("myPrint(int a, int b)"); } } public class Main { public static void main(String[] args) { Test t = new Test(); // Testクラスのインスタンス化 //引数の指定でメソッドを区別している t.myPrint(); t.myPrint(100); t.myPrint(100, 200); t.myPrint("yamamoto"); } } オーバーロードの悪い例 class Test { String myPrint(int a, String b) { return "test"; } String myPrint(int a, int b) { return "test"; } String myPrint(String a, int b) { return "test"; } //変数 //void myPrint(int a, String b) { } //変数名は違うがデータ型,並び同じなのでNG //String myPrint(int x, String y) { return "test"; } } ポイント ・それぞれ別々に定義されたメソッドとして扱われる ・オーバーロードされたメソッドは戻り値の有無、データ型の違いなどの制限がない ・どのメソッドが呼び出されるかは呼び出し時に指定した引数によってコンパイラが判断する ・異なる修飾子を指定できる 可変長引数の宣言 メソッドの引数の数を可変に扱える class Test { public void method(String s, int... a) { System.out.println(s + " サイズ : " + a.length); for(int i : a){ System.out.println(" 第 2 引数の値 :" + i); } } } public class Main { public static void main(String[] args) { Test obj = new Test(); int[] ary = {10, 20, 30}; obj.method("1 回目 "); obj.method("2 回目 ", 10); obj.method("3 回目 ", 10, 20); obj.method("4 回目 ", ary); //obj.method("5 回目 ", null); } } 1 回目 サイズ : 0 2 回目 サイズ : 1 第 2 引数の値 :10 3 回目 サイズ : 2 第 2 引数の値 :10 第 2 引数の値 :20 //呼び出しす際に配列を引数にすることも可能 4 回目 サイズ : 3 第 2 引数の値 :10 第 2 引数の値 :20 第 2 引数の値 :30 引数にNullを引き渡す例 class Test { public void method(String... val) { String size = ""; size += val == null ? "" : val.length; System.out.println(val + " : " + size); } } public class Main { public static void main(String[] args) { Test obj = new Test(); //可変長引数は配列でデータを受け取る obj.method("A", "B"); //String[] = {A , B} //String[] = null 文字列Nullなのか参照型データnullなのかわからない obj.method(null); obj.method((String[])null); //キャストして配列型に obj.method(); obj.method((String)null); //キャストして参照型に } } Static変数とStaticメソッド クラスで共有管理されるStaticメンバ 通常はインスタンス化するとその数だけ変数、メソッドが作成され領域が確保される 常に共有されるためインスタンス化しなくても呼び出しが可能 クラス名.static変数名 クラス名.staticメソッド名() Staticメンバの宣言と呼び出し class Test { int instanceVal = 100; static int staticVal = 200; void methodA() { System.out.println("methodA(): " + instanceVal); } static void methodB() { System.out.println("methodB(): " + staticVal); } } public class Main { public static void main(String[] args) { // NG:testクラスがインスタンス化されていない(実体がない) //System.out.println(Test.instanceVal); System.out.println(Test.staticVal); // OK //Test.methodA(); // NG:同じく実体がない Test.methodB(); // OK Test obj = new Test(); System.out.println(obj.instanceVal); // OK System.out.println(obj.staticVal); // OK obj.methodA(); // OK obj.methodB(); // OK } } staticメンバのクラス内でのアクセス public class Test { int instanceVal; // インスタンス変数 static int staticVal; // static変数 //クラス内のメソッドでインスタンス変数にアクセス int methodA() { return instanceVal; } // OK //クラス内のメソッドでstatic変数にアクセス int methodB() { return staticVal; } // OK //クラス内のstaticメソッドでインスタンス変数にアクセス(インスタンス化が必要) //static int methodC() { return instanceVal; } // NG //クラス内のstaticメソッドでstatic変数にアクセス static int methodD() { return staticVal; } // OK //クラス内のstaticメソッドでインスタンス化した後、そのインスタンス変数にアクセス static int methodE() { // OK Test obj = new Test(); return obj.instanceVal; } } イニシャライザ クラスがロードされた時点で実行されるブロック コンストラクタがインスタンス化した時点なのに対し、イニシャライザは最初に実行される class Test { //static {}のブロックがクラスのロード時点で実行される static { System.out.println("Testクラス:static イニシャライザ"); } Test() { System.out.println("Testクラス:コンストラクタ"); } } public class Main { //同じくstatic {}のブロックがクラスのロード時点で実行される static { System.out.println("Mainクラス:static イニシャライザ"); } public static void main(String[] args) { //main()メソッドと、別クラスのメソッドではどちらの実行が先か… System.out.println("Mainクラス:main()メソッド"); Test obj = new Test(); } } 実行順序 1.Javaコマンド実行時でMainクラスを実行 2.mainクラスのイニシャライザが実行 3.mainクラスのmain()メソッドが実行 4.Testクラスがインスタンス化 5.Testクラスのイニシャライザが実行 6.TestクラスのTest()メソッドが実行 Mainクラス:static イニシャライザ Mainクラス:main()メソッド Testクラス:static イニシャライザ Testクラス:コンストラクタ アクセス修飾子 他クラスからのアクセスを制限できる class Employee { private int id; // インスタンス変数にprivateを指定 public Employee(int i) { id = i; } // コンストラクタにpublicを指定 public int getId() { return id; } // メソッドにpublic修飾子を指定 } public class Main { public static void main(String[] args) { Employee emp = new Employee(100); private指定されたメンバは他クラスからアクセス不可 System.out.println( "private指定のインスタンス変数へアクセス : " + emp.id); // public指定されたメンバは、他クラスからアクセス可 System.out.println( "public指定のメソッドへアクセス : " + emp.getId()); } } 最終的にPrivate変数のidをMainメソッドで返している 1.Mainクラスのmain()メソッドを実行 2.Employeeクラスをインスタンス化 3.Employeeクラスのコンストラクタが実行 idにクラス作成時の引数を代入 4.emp.getIdを実行 同クラス内のPublicメソッドを経由する事で、他クラスからprivate変数を出力している。 カプセル化 オブジェクト指向では属性と操作を一体化させて表現することをカプセル化という 属性:インスタンス変数 操作:メソッドのこと ・推奨される設定  インスタンス変数:private  メソッド:public 外部から属性(変数)への直接的な操作を避け、操作(メソッド)経由でのみアクセスを許可する・・・データの隠蔽 値のコピーと参照情報コピー 基本データ型:値そのものを保持する(int,char,lonj,short,doubleなど) 参照型:データ領域の中の、値が設置されている場所を保持する 基本データ型は数値のコピーを操作するため元の値は変わらない 参照型は参照場所を渡し操作するため元の値が書き換わる ガベージコレクタ ガベージコレクタ(ゴミ収集人):JVM上で稼働しメモリ管理を行っており、プログラムが使用しなくなったメモリ領域を検出し解放する オブジェクトからの参照を取り除けばガベージコレクタの対象となりメモリが解放される 参照を取り除く方法 ・nullを代入 ・参照変数を他のオブジェクトに割り当てる 5章の感想 基本的なクラスやインスタンスの仕組みは認識通りでしたが、やはりルールが細かいです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LINE BOT APIのreplyMessageでたまにInvalid reply tokenが発生する

概要 line-bot-sdk-javaを使ってLINE BOTを作ったが、たまにInvalid reply tokenが発生し返信できないことがある。 この記事ではJavaの場合の解決方法を書くが、他の言語でも原因は同じである。 ちなみに、Herokuの無料hobbyブランを使っている。 エラー内容 ERROR 4 --- [io-33152-exec-3] c.l.b.s.b.s.LineMessageHandlerSupport : InvocationTargetException occurred. Caused by: java.lang.RuntimeException: java.util.concurrent.ExecutionException: com.linecorp.bot.client.exception.BadRequestException: Invalid reply token : ErrorResponse(requestId=xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, message=Invalid reply token, details=[]) Caused by: java.util.concurrent.ExecutionException: com.linecorp.bot.client.exception.BadRequestException: Invalid reply token : ErrorResponse(requestId=xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, message=Invalid reply token, details=[]) Caused by: com.linecorp.bot.client.exception.BadRequestException: Invalid reply token : ErrorResponse(requestId=xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, message=Invalid reply token, details=[]) 原因 公式のドキュメントには以下のように書いてある。 応答トークンは一定の期間が経過すると無効になるため、メッセージを受信したらすぐに応答を返す必要があります。応答トークンは1回のみ使用できます。 応答メッセージを送る LINE BOTのreplyMessage(応答メッセージ)は、送信されたメッセージ1つに付きreply tokenが発行され、それを使ってBOTが返信するという仕組みになっている。そして、このreply tokenは一定期間経過すると無効になり、そうするとInvalid reply tokenが発生し返信ができない、というのが原因のようである。 以下の記事で、このtokenがどのくらいの時間で無効になるのか検証されているが、30秒らしい。 LINE Messaging API の replyToken は何秒で無効になるのか検証 たまにInvalid reply token エラーが起こる条件 原因はわかったが、なぜこのエラーが発生するのか。 このBOTはHerokuの無料・ホビーのプランで運用している。 https://jp.heroku.com/pricing この無料のプランは、インスタンスに30分アクセスがない場合自動でsleep状態になる。 そして再度アクセスがあった場合に、起動し処理を実行する。 sleep状態であっても、受けたリクエストに対する処理は実行してくれるのだが、sleepからrunning状態になるのに時間がかかるため、sleep開けの初回リクエストに対するレスポンスは、通常よりも少し時間がかかってしまう。 つまり今回のエラーは、LINEでユーザーがBOTにメッセージを送った際、Herokuがsleep状態であるとHerokuが起動する時間がかかるため、その分BOTからの返信に時間がかかる。その間にreply tokenの有効期限が過ぎてしまうので、Invalid reply tokenが発生するということになる。 対策 対策には2つある。 1. Herokuを有料プランにする 有料プランにすればsleep状態にはならないので、お金で解決する。 https://jp.heroku.com/pricing 2. pushMessageを使う Invalid reply tokenが発生したら、pushMessageを使って再送信する方法で解決する。 LINE BOTでメッセージを送信する方法には、大きく分けて、先述したreplyMessage(応答メッセージ)と、このpushMessage(プッシュメッセージ)の2つがある。 replyMessageはreply tokenが必要だが、pushMessageにはreply tokenは不要なので、任意のタイミングでメッセージを送信することができる。 つまり、replyMessageでInvalid reply tokenが発生したら、pushMessageを使ってリトライする、という方法で解決する。 ちなみにpushMesasgeにはreply tokenは不要だが、グループに返信する場合にはgroupId、個別ユーザーへの場合はuserIdが必要なので、送信する際にはいずれかを指定すること。 KitchenSinkController.java @Slf4j @LineMessageHandler public class KitchenSinkController { // 一部省略 // replyMessageでの返信 private void replyMessage(String replyToken, List<Message> replyMessage) throws Exception { try { // 返信処理 BotApiResponse apiResponse = lineMessagingClient .replyMessage(new ReplyMessage(replyToken, replyMessage, this.notificationDisabled)) .get(); log.info("Sent messages: {}", apiResponse); } catch (InterruptedException | ExecutionException e) { //throw new RuntimeException(e); // Invalid reply tokenが発生した場合ここでcatchされるので、pushMessage処理を実行する。 // toにはuserIdもしくはgroupIdを入れる(userId、groupIdをプロパティにsetする処理は省略) String to = (this.groupId.isEmpty()) ? this.userId : this.groupId; pushMessage(to, replyMessage); } } // pushMessageでの送信。toにはuserIdもしくはgroupIdを入れる private void pushMessage(String to, List<Message> replyMessage) throws Exception { try { BotApiResponse apiResponse = lineMessagingClient .pushMessage(new PushMessage(to, replyMessage, this.notificationDisabled)) .get(); log.info("Sent push messages to: {}", to); log.info("Sent push messages: {}", apiResponse); } catch (InterruptedException | ExecutionException e) { // pushMesageでもだめならExceptionを投げる throw new RuntimeException(e); } } } ちなみにuserIdは以下で取得できる。 String userId = event.getSource().getUserId(); groupIdはグループチャットでない場合は存在しないため、取得方法が少し面倒だがこんな感じ。 String groupId; if (event.getSource() instanceof GroupSource) { groupId = ((GroupSource) event.getSource()).getGroupId(); } else { groupId = ""; } pushMessageの詳細については以下の公式ドキュメントを参照。 https://developers.line.biz/ja/docs/messaging-api/sending-messages/#methods-of-sending-message https://developers.line.biz/ja/reference/messaging-api/#send-reply-message まとめ この記事ではJavaの解決策を書いたが、他の言語でも原因は同じため、pushMessageでリトライするようにすれば解決できる。 関連 line-bot-sdk-javaを使ってSpring bootでLINE Botを作る Spring boot + JPA + Flyway on herokuでLINE Botを作る
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LINE BOT APIのreplyでたまにInvalid reply tokenが発生する

概要 line-bot-sdk-javaを使ってLINE BOTを作ったが、たまにInvalid reply tokenが発生し返信できないことがある。 この記事ではJavaの場合の解決方法を書くが、他の言語でも原因は同じである。 ちなみに、Herokuの無料hobbyブランを使っている。 エラー内容 ERROR 4 --- [io-33152-exec-3] c.l.b.s.b.s.LineMessageHandlerSupport : InvocationTargetException occurred. Caused by: java.lang.RuntimeException: java.util.concurrent.ExecutionException: com.linecorp.bot.client.exception.BadRequestException: Invalid reply token : ErrorResponse(requestId=xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, message=Invalid reply token, details=[]) Caused by: java.util.concurrent.ExecutionException: com.linecorp.bot.client.exception.BadRequestException: Invalid reply token : ErrorResponse(requestId=xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, message=Invalid reply token, details=[]) Caused by: com.linecorp.bot.client.exception.BadRequestException: Invalid reply token : ErrorResponse(requestId=xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, message=Invalid reply token, details=[]) 原因 公式のドキュメントには以下のように書いてある。 応答トークンは一定の期間が経過すると無効になるため、メッセージを受信したらすぐに応答を返す必要があります。応答トークンは1回のみ使用できます。 応答メッセージを送る LINE BOTのreplyMessage(応答メッセージ)は、送信されたメッセージ1つに付きreply tokenが発行され、それを使ってBOTが返信するという仕組みになっている。そして、このreply tokenは一定期間経過すると無効になり、そうするとInvalid reply tokenが発生し返信ができない、というのが原因のようである。 以下の記事で、このtokenがどのくらいの時間で無効になるのか検証されているが、30秒らしい。 LINE Messaging API の replyToken は何秒で無効になるのか検証 たまにInvalid reply token エラーが起こる条件 原因はわかったが、なぜこのエラーが発生するのか。 このBOTはHerokuの無料・ホビーのプランで運用している。 https://jp.heroku.com/pricing この無料のプランは、インスタンスに30分アクセスがない場合自動でsleep状態になる。 そして再度アクセスがあった場合に、起動し処理を実行する。 sleep状態であっても受けたリクエストに対する処理は実行してくれるのだが、sleepからrunning状態になるのに時間がかかるため、sleep開けの初回リクエストに対するレスポンスは、通常よりも少し時間がかかってしまう。 つまり今回のエラーは、LINEでユーザーがBOTにメッセージを送った際、Herokuがsleep状態であるとHerokuが起動する時間がかかるため、その分BOTからの返信に時間がかかる。その間にreply tokenの有効期限が過ぎてしまうので、Invalid reply tokenが発生するということになる。 対策 対策には2つある。 1. Herokuを有料プランにする 有料プランにすればsleep状態にはならないので、お金で解決する。 https://jp.heroku.com/pricing 2. pushMessageを使う Invalid reply tokenが発生したら、pushMessageを使って再送信する方法で解決する。 LINE BOTでメッセージを送信する方法には、大きく分けて、先述したreplyMessage(応答メッセージ)と、このpushMessage(プッシュメッセージ)の2つがある。 replyMessageはreply tokenが必要だが、pushMessageにはreply tokenは不要なので、任意のタイミングでメッセージを送信することができる。 つまり、replyMessageでInvalid reply tokenが発生したら、pushMessageを使ってリトライする、という方法で解決する。 ちなみにpushMesasgeにはreply tokenは不要だが、グループに返信する場合にはgroupId、個別ユーザーへの場合はuserIdが必要なので、送信する際にはいずれかを指定すること。 KitchenSinkController.java @Slf4j @LineMessageHandler public class KitchenSinkController { // 一部省略 // replyMessageでの返信 private void replyMessage(String replyToken, List<Message> replyMessage) throws Exception { try { // 返信処理 BotApiResponse apiResponse = lineMessagingClient .replyMessage(new ReplyMessage(replyToken, replyMessage, this.notificationDisabled)) .get(); log.info("Sent messages: {}", apiResponse); } catch (InterruptedException | ExecutionException e) { //throw new RuntimeException(e); // Invalid reply tokenが発生した場合ここでcatchされるので、pushMessage処理を実行する。 // toにはuserIdもしくはgroupIdを入れる(userId、groupIdをプロパティにsetする処理は省略) String to = (this.groupId.isEmpty()) ? this.userId : this.groupId; pushMessage(to, replyMessage); } } // pushMessageでの送信。toにはuserIdもしくはgroupIdを入れる private void pushMessage(String to, List<Message> replyMessage) throws Exception { try { BotApiResponse apiResponse = lineMessagingClient .pushMessage(new PushMessage(to, replyMessage, this.notificationDisabled)) .get(); log.info("Sent push messages to: {}", to); log.info("Sent push messages: {}", apiResponse); } catch (InterruptedException | ExecutionException e) { // pushMesageでもだめならExceptionを投げる throw new RuntimeException(e); } } } ちなみにuserIdは以下で取得できる。 String userId = event.getSource().getUserId(); groupIdはグループチャットでない場合は存在しないため、取得方法が少し面倒だがこんな感じ。 String groupId; if (event.getSource() instanceof GroupSource) { groupId = ((GroupSource) event.getSource()).getGroupId(); } else { groupId = ""; } pushMessageの詳細については以下の公式ドキュメントを参照。 https://developers.line.biz/ja/docs/messaging-api/sending-messages/#methods-of-sending-message https://developers.line.biz/ja/reference/messaging-api/#send-reply-message まとめ この記事ではJavaの解決策を書いたが、他の言語でも原因は同じため、pushMessageでリトライするようにすれば解決できる。 関連 line-bot-sdk-javaを使ってSpring bootでLINE Botを作る Spring boot + JPA + Flyway on herokuでLINE Botを作る
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Javaのラムダ式とStreamAPIとは

前提 以降の説明で表示するソースは全て GitHub に上げてあります。 本記事の目的 Java8 で追加された機能(関数型プログラミング・ラムダ式・Stream)というものについて、 難しい説明を極力省き、下記のような記述を読みこなせるようになるのが目標です。 persons.stream() .filter(p -> p.getAge() >= 30) .forEach(p -> System.out.println(p.getName())); ラムダ式とは インターフェースに 抽象メソッド を1つだけ定義したものを 関数型インターフェース と呼びます。 この 関数型インターフェース の実装を、とことん簡潔に記述するのがラムダ式です。 たとえば、こんなインターフェースがあるとします。 Sample01.java interface Iface1 { public void method(int i, String s); } 通常クラスの場合 Face1.java class Face1 extends Iface1 { public void method(int i, String s) { System.out.println(i + s); } } Iface1 if1 = new Face1(); 無名クラスの場合 無名クラスとは、一時的に利用するために作られる名前の無いクラスです。 インスタンスの生成と同時にクラス定義も指定します。 Sample01.java Iface1 if1 = new Iface1() { public void method(int i, String s) { System.out.println(i + s); } }; 簡単です。簡単ですけど まだまだ冗長です もっと簡潔に書けないでしょうか? 簡潔に書く上でポイントとなるのが 関数型インターフェース の特徴です。 関数型インターフェース には 抽象メソッド が1つしかありません。 1つだけならメソッド名をわざわざ実装側に書かなくてもコンパイラには判るはずです。 同様に引数の型や戻り値もコンパイラには判るはず。 そういったコンパイラには判るはずの情報を省略して必要な部分だけ記述するのが ラムダ式 です。 ラムダ式の場合 Iface1 if1 = (i, s) -> System.out.println(i + s); 矢印(->)をはさんだ左側が引数で、右側が処理になります。 処理が複数行ある場合 処理が複数行に渡る場合は {} で括ります。 Sample01.java Iface1 if1 = (i, s) -> { System.out.println(i); System.out.println(s); }; if1.method(123, "xyz"); // => 123 // => xyz ラムダ式の使いどころ List を例として、ラムダ式を使うケースを見てみます。 List のデータを処理する場合、ほぼ必ずループが出てきます。 Sample02.java List<Integer> list = Arrays.asList(100, 200, 300, 400, 500); for (Integer i: list) { // 1件ごとの処理 } これはこれでいいのですが、Java8からは forEach メソッドを使うことでより簡潔に書くことができます。 list.forEach(/*1件ごとの処理*/) 上記で全データに対してカッコ内の処理が行われます。 forEach の引数として 1件ごとの処理 を渡していますが、これは具体的に何を指定するのか。 ラムダ式とどんな関係があるのか見ていきましょう。 一件ごとの処理の渡し方 forEach に渡すのは Consumer というインターフェースのオブジェクトです。 Consumer には accept というメソッドが定義されており public interface Consumer<T> { void accept(T t); この accept に1件ごとの処理を記述しておきます。 実際に Consumer を実装してみます。 Sample02.java List<Integer> list = Arrays.asList(100, 200, 300, 400, 500); list.forEach(new Consumer<Integer>() { @Override public void accept(Integer i) { System.out.println(i); } }); // => 100 200 300 400 500 (改行あり) 上記で見たとおり Consumer で実装する抽象メソッドは accept だけです。 抽象メソッドが1つしかないインターフェース(関数型インターフェース)は ラムダ式 で書けます。 そうするとコードは下記のようになり、かなりスッキリします。 Sample02.java List<Integer> list = Arrays.asList(100, 200, 300, 400, 500); list.forEach(i -> System.out.println(i)); // => 100 200 300 400 500 (改行あり) (参考)実行時の forEach の動き forEach は、各データを引数として Consumer の accept を呼び出してくれます。 実装例として、全データを画面表示させてみましたが、実際のプログラミングはそんな単純なことばかりではなく、条件判定を行ったり計算したり、いろいろあるはずです。 そういったいろいろな処理を行うためのメソッドや関数型インターフェースが Java8 では用意されており StreamAPI として提供されています。 Stream StreamAPI というのは、コレクションや配列、数字の集合、ファイル一覧など、データのかたまりを操作するためのライブラリです。 コレクションを StreamAPI で操作するのは非常に簡単で、前項のサンプル list を例にとると list.stream(); で Stream が生成され、以降の処理は Stream のメソッドを呼び出すことで実行します。 様々なメソッドが Stream には用意されており、データ処理が簡単に行えます。 前項の forEach はラムダ式との組み合わせで動作しましたが、 Stream のメソッドもラムダ式と組み合わせて使用するものが多数あります。 中間処理と終端処理 Stream には 中間操作 と 終端操作 という2種類のメソッドがあります。 中間操作 は Stream の抽出や変換を行い、新たな Stream を生成します。 終端操作 は Stream に対する最終的な処理(合計算出やリスト化など)を行います。 文字だけでは分かりにくいので図で描いてみます。 たとえば、ここに性別と年齢のデータがリスト形式であるとします。 ここから男性の合計年齢を計算する場合  ①リストから Stream を生成  ②そこから男性だけを抽出して Stream を生成  ③そこから年齢だけを取り出して Stream を生成  ④その合計を求める という流れになり、②と③が中間操作で④が終端操作に相当します。 Stream を生成し、その Stream を元に新たな Stream を生成するという作業を中間操作でくり返し、 終端操作で最終的なゴール(男性の平均年齢)に到達しています。コーディングは次のような形になります。 // コレクション.stream().中間操作().中間操作().終端操作() // (中間操作の戻り値は Stream なので処理を次々つないでいける。) list.stream().filter((x) -> x.getGender() == Gender.MALE).mapToInt((x) -> x.getAge()).sum(); 中間操作、終端操作はすべて Stream のメソッドとして提供されています。 抽出 条件を指定して抽出を行うのが filter メソッドです。 コレクション.stream().filter (抽出ロジック) と書けば抽出後の Stream が filter から返却されます。 filter の引数が 抽出ロジック となっていますが、それはどういうことかというと、filter に渡す引数は Predicate という関数型インターフェースと決まっており、そこに抽出ロジックを記述します。 Predicate には test というメソッドがあり public interface Predicate<T> { boolean test(T t); この test メソッドに抽出の条件判定ロジック(true/falseを返す)を記述します。 下記サンプルは、数値を 300 以上という条件で抽出しています。 Predicate を 無名クラス で実装したものと ラムダ式 を使ったものと2パターンです。 無名クラス Sample03.java List<Integer> list = Arrays.asList(100, 200, 300, 400, 500); list.stream().filter(new Predicate<Integer>() { @Override public boolean test(Integer i) { return i >= 300; } }).forEach(i -> System.out.println(i)); // => 300 400 500 (改行される) ラムダ式 Sample03.java List<Integer> list = Arrays.asList(100, 200, 300, 400, 500); list.stream().filter(i -> i >= 300) .forEach(i -> System.out.println(i)); // => 300 400 500 (改行される) filter に渡したラムダ式は test メソッド本体として扱われます。 一行で書ける場合は return が省略可能 です。 (参考)実行時の filter の動き filter は Stream の各データを引数として Predicate の test を呼び出し、true となるデータで Stream を新たに生成し、戻り値とします。 集計 Stream の集計を行うときは reduce というメソッドを使います。 しかし、この reduce はちょっと分かりにくいので別の方法で実行します。 IntStream というものを使うのですが、IntStream とはそもそもなんなのか、それをさらっと説明します。 これまで Stream とひとくちに言ってきましたが、実は Stream には下記の4種類あり、その中のひとつが IntStream です。 Stream IntStream LongStream DoubleStream 前項のサンプル list.stream() で生成されるのは Stream<T> です。 Stream<T> は言わば汎用的な Stream で、それに比べて IntStream , LongStream , DoubleStream は、数値(int、long、double)専用の Stream です。 平均を求める average や 合計を求める sum といったメソッドが用意されており、集計を非常に簡単に行えます。 使い方の手順として、いったん Stream<T> を生成し、それを IntStream に変換します。 変換には mapToInt というメソッドを使います。 たとえば、元データとして数値文字列のリストがあるとします。集計をとる際の流れは  ① list.stream() で Stream<String> を生成し  ②そのインスタンスの mapToInt メソッドで IntStream を生成し  ③ IntStream の sum メソッドで合計を求める となります。 mapToInt mapToInt には ToIntFunction という関数型インターフェースを渡します。 ToIntFunction には applyAsInt というメソッドがあり public interface ToIntFunction<T> { int applyAsInt(T value); } この applyAsInt メソッドに 元データ → int への変換プログラムを記述します。 下記は合計を求めるサンプルですが ToIntFunction を 無名クラス で実装したものと ラムダ式 を使ったものと2パターンです。 無名クラス Sample04.java List<String> list = Arrays.asList("100", "200", "300", "400"); int sum = list.stream().mapToInt(new ToIntFunction<String>() { @Override public int applyAsInt(String s) { return Integer.parseInt(s); // 変換 } }).sum(); System.out.println(sum); // => 1000 ラムダ式 Sample04.java List<String> list = Arrays.asList("100", "200", "300", "400"); int sum = list.stream() .mapToInt(s -> Integer.parseInt(s)) .sum(); System.out.println(sum); // => 1000 (参考)実行時の mapToInt の動き mapToInt は Stream<T> の各データを引数として ToIntFunction の applyAsInt を呼び出し、変換された int の値で IntStream を生成し戻り値とします。 filter と一緒に filter と組み合わせれば、条件を指定して集計できます。 300 以上の値を合計してみます。 Sample05.java List<String> list = Arrays.asList("100", "200", "300", "400"); int sum = list.stream() .mapToInt(s -> Integer.parseInt(s)) .filter(i -> i >= 300) .sum(); System.out.println(sum); // => 700 Stream をリストへ戻す ここまでは、リストを Stream に変換して操作をする方法を見てきましたが、今度は逆に Stream をリストに変換する方法を見てみます。 Sample05.java List<Integer> list = Arrays.asList(100, 200, 300, 400, 500); List<Integer> list2 = list.stream() .filter(i -> i >= 300) .collect(Collectors.toList()); for (Integer i : list2) { System.out.println(i); } // 300 400 500 (改行あり) collect(Collectors.toList()) で filter 後の Stream がリストに変換されています。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Kotlin/Java] KotlinのデータクラスをJava で利用するには

記事でわかること KotlinとJavaが併存するプロジェクトでJavaのクラス内でKotlinのデータクラスを利用する方法を記載します。 背景 現在関わっているサーバーサイドのプロジェクトではJavaからKotlinへの移行を行っています。 新規クラスはKotlinで記述することで少しずつKotlinへの移行を進めています。 その際にKotlinのデータクラスをJavaから呼び出す際にどうすればいいのだろうと少し迷ったので、備忘のために記載します。 JavaでKotlinのデータクラスを利用するには 結論から言うと Kotlinのクラスを利用側のJavaクラスでimportして、普通にnewすることで利用できます。 data class User( val id: Int, val name: String, val mailAddress: String ) 例えば、上記のKotlinのデータクラスをJavaで利用する場合には下記のように記述します。 import User; public User createUser(Integer id, String name, String mailAddress) { User user = new User(id, name, mailAddress); return user; } Kotlinのデータクラスをデコンパイルしてみる KotlinのデータクラスをJavaにデコンパイルしてみると、コンストラクタは下記のようになっています。 public User(int id, @NotNull String name, @NotNull String mailAddress) { Intrinsics.checkNotNullParameter(name, "name"); Intrinsics.checkNotNullParameter(mailAddress, "mailAddress"); super(); this.id = id; this.name = name; this.mailAddress = mailAddress; } [ポイント] Intはprimitiveのintに、StringはそのままStringに変換されている JavaでもKotlinで記述したっとり必要な引数を取得するコンストラクタが生成されている val指定すると、パラメーターにはNotNullの制約が付与される まとめ KotlinのデータクラスをJavaで利用する場合は普通にnewすればOK コンストラクタにvalを指定した場合はKotlinの言語仕様通り、null 制約が付与されるので渡すパラメーターには注意が必要 Kotlinのプリミティブな型(Int,String,Booleanなど)を利用している場合はJava側でどのクラス/プリミティブな型に変換されるのか確認したほうが良い それでは素敵なKotlinライフを!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む