IPA(情報処理推進機構)は10月11日、日米企業のDX(デジタルトランスフォーメーション)動向を比較調査し、戦略や人材、技術などを包括的に解説した「DX白書2021」を発刊した。計372ページのPDFをIPAのWebサイトでダウンロードできる。ITとビジネスの関係が密接になっているため、同白書を通じて日本企業のDX推進を支援したい考え。
⇧ 372ページ...超大作ですな。
今回は、Javaの「関数型インターフェイス(Functional Interface)」などの話についてです。
レッツトライ~。
Javaの「関数型インターフェイス(Functional interface)」とは?
手前味噌で恐縮ですが、
⇧ 前回の記事で、Javaの「ラムダ式(Lambda Expression)」を調べていて、Javaの「ラムダ式(Lambda Expression)」を実現するための「型」として、Javaでは「関数型インターフェイス(Functional Interface)」を定義せざるを得なかったという話でした。
パッケージjava.util.functionの説明
関数型インタフェースは、ラムダ式やメソッド参照のターゲットとなる型を提供します。各関数型インタフェースには、その関数型インタフェースの関数メソッドと呼ばれる単一の抽象メソッドが含まれており、ラムダ式のパラメータや戻り値の型のマッチングや適応は、そのメソッドに対して行われます。関数型インタフェースは代入の文脈、メソッド呼び出しの文脈、キャストの文脈など、さまざまな文脈でターゲットの型を提供できます。
⇧ どうやら「ラムダ式(Lambda Expression)」だけでなく「メソッド参照(Method Reference)」なるものでも「関数型インターフェイス(Functional Interface)」は活用できるらしい。
で、「LTS(Long Time Support)」の各バージョンのドキュメントを見てみた感じでは、
「java.util.function」パッケージで追加された「関数型インターフェイス(Functional Interface)」の数は変わっていないっぽくて、全部で43個ほど用意されてました。(「No.」は、数が分かりやすいように勝手に採番してます。)
No. | 関数型インターフェイス名 |
---|---|
1 | BiConsumer |
2 | BiFunction |
3 | BinaryOperator |
4 | BiPredicate |
5 | BooleanSupplier |
6 | Consumer |
7 | DoubleBinaryOperator |
8 | DoubleConsumer |
9 | DoubleFunction |
10 | DoublePredicate |
11 | DoubleSupplier |
12 | DoubleToIntFunction |
13 | DoubleToLongFunction |
14 | DoubleUnaryOperator |
15 | Function |
16 | IntBinaryOperator |
17 | IntConsumer |
18 | IntFunction |
19 | IntPredicate |
20 | IntSupplier |
21 | IntToDoubleFunction |
22 | IntToLongFunction |
23 | IntUnaryOperator |
24 | LongBinaryOperator |
25 | LongConsumer |
26 | LongFunction |
27 | LongPredicate |
28 | LongSupplier |
29 | LongToDoubleFunction |
30 | LongToIntFunction |
31 | LongUnaryOperator |
32 | ObjDoubleConsumer |
33 | ObjIntConsumer |
34 | ObjLongConsumer |
35 | Predicate |
36 | Supplier |
37 | ToDoubleBiFunction |
38 | ToDoubleFunction |
39 | ToIntBiFunction |
40 | ToIntFunction |
41 | ToLongBiFunction |
42 | ToLongFunction |
43 | UnaryOperator |
ちなみに、
Java 8のjava.util.Comparatorインタフェースは、その宣言からは2つのメソッドを持つようにみえるが関数型インタフェース(functional interface)である。
⇧まさかの「java.util.function」パッケージ以外でも「関数型インターフェイス(Functional Interface)」が用意されてるということらしい。
確かに、APIドキュメントを見てみると、
- Interface Comparator<T>
- Type Parameters:
T
- the type of objects that may be compared by this comparator
- All Known Implementing Classes:
- Collator, RuleBasedCollator
- Functional Interface:
- This is a functional interface and can therefore be used as the assignment target for a lambda expression or method reference.
https://docs.oracle.com/javase/8/docs/api/java/util/Comparator.html
⇧ となっていますと。
ただ、「Java SE 7」のドキュメントでは「関数型インターフェイス(Functional Interface)」という扱いになっていない、何故なら「関数型インターフェイス(Functional Interface)」は「Java SE 8」で導入されたから。
で、俄然、気になるのが、「java.util.function」と「java.util」パッケージの「Comparator」以外にも「関数型インターフェイス(Functional Interface)」にされてる「インターフェイス」って存在するのか?ってことですよね...
そして、
- ただし、インタフェース宣言に
FunctionalInterface
注釈型が存在しているかどうかにかかわらず、コンパイラは関数型インタフェースの定義を満たすどのインタフェースも関数型インタフェースとして扱います。
https://docs.oracle.com/javase/jp/8/docs/api/java/lang/FunctionalInterface.html
⇧ 終わった...
「関数型インターフェイス(Functional Interface)」がどれだけ用意されてるのか、中の人以外は誰もその全量が把握できん状況になってるやつだ...
「stackoverflow」でも、
-
I'm trying to see if there is any way to get a list of all the interfaces in Java 8 that are functional interfaces. I'm not talking about the list on this page:
https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html
Rather, I'm talking about interfaces like Comparator, FileFilter, and Runnable - interfaces that the API document shows are functional like this:
@FunctionalInterface public interface Runnable
Is there a full list of these somewhere?
Thank you!
⇧ 同じような疑問を持った方がいて、「問いかけ」てますが、これといった有効的な方法が無いっぽい模様...
脱線しましたが、「java.util.function」パッケージで追加された「関数型インターフェイス(Functional Interface)」で、それぞれ、「引数」と「戻り値」がどうなってるかについて、
⇧ 上記サイト様がまとめてくださっていましたので、参考に整理してみると、
No. | 関数型インターフェイス名 | 型 | ||
---|---|---|---|---|
戻り値※ | 第一引数 | 第二引数 | ||
1 | Runnable | void | なし | なし |
2 | intSupplier | int | なし | なし |
3 | LongSupplier | long | なし | なし |
4 | DoubleSupplier | double | なし | なし |
5 | BooleanSupplier | boolean | なし | なし |
6 | Supplier | Object R | なし | なし |
7 | IntConsumer | void | int | なし |
8 | IntUnaryOperator | int | int | なし |
9 | IntToLongFunction | long | int | なし |
10 | IntToDoubleFunction | double | int | なし |
11 | IntPredicate | boolean | int | なし |
12 | IntFunction | Object R | int | なし |
13 | IntBinaryOperator | int | int | int |
14 | LongConsumer | void | long | なし |
15 | LongToIntFunction | int | long | なし |
16 | LongUnaryOperator | long | long | なし |
17 | LongToDoubleFunction | double | long | なし |
18 | LongPredicate | boolean | long | なし |
19 | LongFunction | Object R | long | なし |
20 | LongBinaryOperator | long | long | long |
21 | DoubleConsumer | void | double | なし |
22 | DoubleToIntFunction | int | double | なし |
23 | DoubleToLongFunction | long | double | なし |
24 | DoubleUnaryOperator | double | double | なし |
25 | DoublePredicate | boolean | double | なし |
26 | DoubleFunction | Object R | double | なし |
27 | DoubleBinaryOperator | double | double | double |
28 | Consumer | void | Object T | なし |
29 | ToIntFunction | int | Object T | なし |
30 | ToLongFunction | long | Object T | なし |
31 | ToDoubleFunction | double | Object T | なし |
32 | Predicate | boolean | Object T | なし |
33 | Function | Object R | Object T | なし |
34 | UnaryOperator | (T=U=R) | Object T | なし |
35 | BiConsumer | void | Object T | Object U |
36 | ToIntBiFunction | int | Object T | Object U |
37 | ToLongBiFunction | long | Object T | Object U |
38 | ToDoubleBiFunction | double | Object T | Object U |
39 | BiPredicate | boolean | Object T | Object U |
40 | BiFunction | Object R | Object T | Object U |
41 | BiUnaryOperator | (T=U=R) | Object T | Object U |
42 | ObjIntConsumer | void | Object T | int |
43 | ObjLongConsumer | void | Object T | long |
44 | ObjDoubleConsumer | void | Object T | double |
※厳密には、各「関数型インターフェイス(Functional Interface)」で用意されてる「メソッド」が渡された「引数」で実行された結果の「型」ということらしい(ドキュメントだと「メソッド」の説明で「関数の結果」を返すってなってるので、『「メソッド」と「関数」は別物なんじゃなかったっけ?』という疑問が拭えない今日この頃です...)
⇧ というような一覧になりました。
44個になってるやんけ~!、って言うのには理由があるらしく、
引数をとらず戻り値もない場合に使えるインタフェースはfunctionパッケージに用意されていないようなので、Runnableインタフェースを使うことになります。
⇧ 上記サイト様によりますと、「Runnable」って「関数型インターフェイス(Functional Interface)」を使うことになるからだそうです。
ちなみに、「java.lang」パッケージの「Runnable」についても、
- Interface Runnable
- All Known Subinterfaces:
- RunnableFuture<V>, RunnableScheduledFuture<V>
- All Known Implementing Classes:
- AsyncBoxView.ChildState, ForkJoinWorkerThread, FutureTask, RenderableImageProducer, SwingWorker, Thread, TimerTask
- Functional Interface:
- This is a functional interface and can therefore be used as the assignment target for a lambda expression or method reference.
https://docs.oracle.com/javase/8/docs/api/java/lang/Runnable.html
⇧「java.util.function」パッケージ以外で追加されてる「関数型インターフェイス(Functional Interface)」ということになるらしい、正確には、「java.util」パッケージの「Comparator」と同じく「Java SE 8」で「関数型インターフェイス(Functional Interface)」に変更されたということかと。
ちなみに、
Java8のFunctionインターフェースにはインプットが3つ以上のインターフェースが提供されていません。
ならば、Functionインターフェースを自作してみましょう。
実は、ありがたいことに、Java8がただ単純にインプットが3つ以上の関数型インターフェースを提供してないだけで、(少なくてもOracleとOpenJDKのJavaでは)複数の引数へ対応する準備が整っています。
JavaScriptの経験者であれば、既に思い付いたかもしれません。カリー化すればいいです。
⇧ という感じで、「自作」or「カリー化」のどっちかで、3つ以上の「引数」で「関数型インターフェイス(Functional Interface)」を実施することも可能らしい。
実際に「java.util.function」パッケージで用意されてる「関数型インターフェイス(Functional interface)」を使ってみる
というわけで、改めて、「java.util.function」パッケージのAPIドキュメントを確認してみると、
パッケージjava.util.functionの説明
関数型インタフェースは、ラムダ式やメソッド参照のターゲットとなる型を提供します。各関数型インタフェースには、その関数型インタフェースの関数メソッドと呼ばれる単一の抽象メソッドが含まれており、ラムダ式のパラメータや戻り値の型のマッチングや適応は、そのメソッドに対して行われます。関数型インタフェースは代入の文脈、メソッド呼び出しの文脈、キャストの文脈など、さまざまな文脈でターゲットの型を提供できます。
https://docs.oracle.com/javase/jp/8/docs/api/java/util/function/package-summary.html
// Assignment context
Predicate<String> p = String::isEmpty;
// Method invocation context
stream.filter(e -> e.getSize() > 10)...
// Cast context
stream.map((ToIntFunction) e -> e.getSize())...
https://docs.oracle.com/javase/jp/8/docs/api/java/util/function/package-summary.html
⇧ ってな感じで、「関数型インターフェイス(Functional interface)」は、少なくとも、
- Assigment context
変数として使う - Method invocation context
メソッドの引数として使う - Cast context
「型」のキャストとして使う
3つの使い方ができるらしい。
とは言え、「java.util.function」パッケージで用意されてる「関数型インターフェイス(Functional interface)」は43個もあるので、
Java SE 8では、ラムダ式を適用することのできる43個の関数型インタフェースが新たに追加されました。それらをいきなりすべて覚える必要はありません。初めは、次に示す4つの関数型インタフェースから覚えるとよいでしょう。これらの関数型インタフェースを引数に取る部分でラムダ式を書くことができます。
ラムダ式とストリームAPIでJavaプログラミングはここまでシンプルになる!--Java SE 8に今すぐ移行すべき理由 - page2 - builder by ZDNet Japan
⇧上記サイト様も仰っているように、まずは、基本形っぽい上記の4つから始めるのが吉、ということで4つに絞って使ってみますか。
その前に、この「引数」とか「戻り値」でしれっと何の説明もなく「T」とか「R」とか記載されてるんだけど、これは、
<T>が総称型の一種であることがわかりましたが<T>のTがなんなのか掘り下げていきます。
インターフェースを作成する際など具体的なデータ型がまだ決まっていない時があります。
そんな時に型パラメータ(Type Parameter)が使用されます。型パラメータは<T>のように書き、実際に使用する際に<String>などに代入されて使用されます。
型パラメータは型変数や仮型パラメータなどとも呼ばれているようです。
総称型 (Generics type)とは?TとかEについて学ぶ! Java初心者の勉強 | Programmer Life
一般的に使用されている型パラメータ名を紹介します。
- E - Element(要素)(Javaのコレクション フレームワークで多用されている)
- K - Key(キー)
- N - Number(数値)
- T - Type(タイプ)
- V - Value(値)
- S,U,V etc. - 2番目、3番目、4番目... types
Oracleのサイトでは上記が紹介されていましたが、この他にもR - Return (戻す型)、P - Parameter(パラメータ) が使われているのを見かけます。
総称型 (Generics type)とは?TとかEについて学ぶ! Java初心者の勉強 | Programmer Life
⇧ 上記サイト様が詳しいです。
Oracleさんのサイトを見てみると、
You can also substitute a type parameter (that is, K or V) with a parameterized type (that is, List<String>). For example, using the OrderedPair<K, V> example:
OrderedPair<String, Box<Integer>> p = new OrderedPair<>("primes", new Box<Integer>(...));
https://docs.oracle.com/javase/tutorial/java/generics/types.html
⇧「型パラメータ」が「V」の場合は、「総称型(Generics)」を「引数」にできるみたいね。
ただ、「java.util.function」パッケージで定義されてる「関数型インターフェイス(Functional Interface)」については、「型パラメータ」が「T」「U」「R」だから「総称型(Generics)」を「引数」にはできないかと。(わざわざ、「型パラメータ」が「K」と「V」の時は、「総称型(Generics)」を「引数」にできるよ、と説明しているので。)
というわけで、今度こそ、実際に「java.util.function」で用意されてる「関数型インターフェイス(Functional Interface)」を使ってみますか。
で、ここで、早速、疑問が浮かびましたと。
Interface Predicate<T>
- Type Parameters:
T
- the type of the input to the predicate
- Functional Interface:
- This is a functional interface and can therefore be used as the assignment target for a lambda expression or method reference.
https://docs.oracle.com/javase/8/docs/api/java/util/function/Predicate.html
⇧「Predicate」のAPIドキュメントを確認すると、『This is a functional interface and can therefore be used as the assignment target for a lambda expression or method reference.』って説明になっていて、『「ラムダ式(lambda expression)」または「メソッド参照」の割当ターゲットとして使用できる』ってことらしいんだけど、「ラムダ式(lambda expression)」と「メソッド参照(Method Reference)」の実装方法については、どこを参照すれば良いか記載してくれていないんですな、流石は安定の不親切さのOracleさん。
で、探したところ、
Outline
- Part A: Functional Interfaces: Provides a definition of functional interfaces, which lambda expressions and method references can be used to instantiate.
- Part B: Lambda Expressions: Defines the syntax of lambda expressions, including rules for the new kinds of parameter lists and statement lists.
- Part C: Method References: Defines the syntax of method references.
- Part D: Poly Expressions: Describes poly expressions, which are a category of expressions that can adapt their typing to conform to a particular context.
- Part E: Typing and Evaluation: Specifies the typing rules and evaluation behavior of lambda expressions and method references.
- Part F: Overload Resolution: Adapts overload resolution to handle lambda expressions and other poly expressions.
- Part G: Type Inference: Redefines type inference to support lambda expressions and method references, and to allow context to be pushed down to nested poly expressions.
- Part H: Default Methods: Describes the syntax and inheritance behavior of default methods, which are members of interfaces.
- Part J: Java Virtual Machine: Enhances the JVM specification to support code-carrying methods in interfaces.
https://docs.oracle.com/javase/8/docs/api/java/util/function/Predicate.html
⇧ 上記のドキュメントの「Part B:Lambda Expressions」「Type C:Method References」「Part E:Typing and Evaluation」を見るしか無さそう。
で、見に行ってみました。
「ラムダ式(Lambda Expression)」については、
15.27 Lambda Expressions [New]
-
A lambda expression is like a method: it provides a list of formal parameters and a body—an expression or block—expressed in terms of those parameters.
LambdaExpression: LambdaParameters '->' LambdaBody LambdaParameters: Identifier '(' FormalParameterListopt ')' '(' InferredFormalParameterList ')' InferredFormalParameterList: Identifier InferredFormalParameterList ',' Identifier LambdaBody: Expression Block
http://cr.openjdk.java.net/~dlsmith/jsr335/jsr335-0.6.2/B.html
⇧ 上記のような記述ルールがありますと。
フォーマットは決まってるようで、
引数 –> 式
って形になるらしい。
で、肝心の「引数」に使えるもの、「式」に使えるものってのについての説明が判然としないんだけど、「Examples of lambda expressions:」って部分に書き方の例が載ってました。
() -> {} // No parameters; result is void () -> 42 // No parameters, expression body () -> null // No parameters, expression body () -> { return 42; } // No parameters, block body with return () -> { System.gc(); } // No parameters, void block body () -> { if (true) return 12; else { int result = 15; for (int i = 1; i < 10; i++) result *= i; return result; } } // Complex block body with returns (int x) -> x+1 // Single declared-type parameter (int x) -> { return x+1; } // Single declared-type parameter (x) -> x+1 // Single inferred-type parameter x -> x+1 // Parens optional for single inferred-type case (String s) -> s.length() // Single declared-type parameter (Thread t) -> { t.start(); } // Single declared-type parameter s -> s.length() // Single inferred-type parameter t -> { t.start(); } // Single inferred-type parameter (int x, int y) -> x+y // Multiple declared-type parameters (x,y) -> x+y // Multiple inferred-type parameters (final int x) -> x+1 // Modified declared-type parameter (x, final y) -> x+y // Illegal: can't modify inferred-type parameters (x, int y) -> x+y // Illegal: can't mix inferred and declared types
⇧ 上記がJavaにおける「ラムダ式(Lambda Expression)」の書き方の全量なのかが分からんけども、他に情報が見つからんので、これを基準にコーディングしていくしか無さそう。
そんでは、早速、「統合開発環境」の「Eclipse」を起動して、適当な「Java プロジェクト」を作成しましょう。
ちなみに「java.util.function」パッケージは、標準の「Java API(JRE Library)」に含まれてるので、「Java SE」をインストールしていれば使えます、ただし、「Java SE 8」以上。
で、「Eclipse」は「JDK(Java Development Kit)」が内蔵されてるので、「Java SE」をインストールしなくても、「Java」を利用はできますと。
「Java SE」は「Java Standard Edition」の略で、
⇧ Wikipediaさんの「Jakarta EE(旧:Java EE)」の説明にある図を見た感じでは、「Java SE」をインストールすると「JDK(Java Development Kit)」なんかも同梱されてくるようです。
脱線しましたが、「Eclipse」が起動できたら、「ファイル(F)」>「新規(N)」>「Java プロジェクト」を選択。(「Java プロジェクト」が選択肢に無い場合は、「その他(O)...」を選択で、見つかるはず)
で、「プロジェクト名(P):」を入力して、「次へ(N)>」を選択。
特に何も変更せず「完了(F)」を選択。
「モジュール」の作成を聞かれたら、今回は「作成しない(D)」を選択。
そうすると、「パッケージ・エクスプローラー」に、作成した「Java プロジェクト」が表示されます。
ちなみに、「パッケージ・エクスプローラー」は、「パースペクティブ」が「Java」を選択してる場合で、
「パースペクティブ」が「Java EE」だと「プロジェクト・エクスプローラー」がデフォルトになるかと。
脱線しましたが、作成した「Java プロジェクト」の中にある「JRE システム・ライブラリー」の中に、標準の「Java API」のライブラリがあって「java.util.function」パッケージも存在することが確認できたので、あとは、「src」フォルダに「拡張子」が「.java」のファイルを作成してコーディングしていく感じで。
というわけで、「パッケージ・エクスプローラー」上で、作成した「Java プロジェクト」内の「src」フォルダを選択した状態で右クリックし、「新規(W)」>「クラス」を選択。(「クラス」が選択肢に無い場合は、「その他(O)...」を選択で、見つかるはず)
で、「パッケージ(K):」、「名前(M):」を適当に入力し、「どのメソッド・スタブを作成しますか?」で「public static void main(String[] args)(V)」のチェックボックスにチェックを入れて、「完了(F)」を選択。
「クラス」ファイルが作成されるので、コーディングしていきます。
で、まずは「関数型インターフェイス(Functional Interface)」の「Predicate」は、「第1引数」に「型パラメーター」である「T」、「第2引数」は「なし」で、「戻り値」が「boolean」になるように「ラムダ式(Lambda Expression)」を構成してあげれば良いらしいですと。
せっかくなんで、「T」のクラスを自作してみますか。
「Predicate」は、最終的に「boolean」が「戻り値」になれば良いので、用途としては、「T」に持たせた「フィールド(メンバ変数)」の値が、条件に合うか合わないかみたいなことを想定してるんじゃないかと。
というわけで、先ほど作成した「メインクラス(今回だとPracticsPredict.java)」以外にも、「Skill.java」というものをクラスを作りました。
というか、「PracticsPredict.java」の名前についてはタイピングミスってました、本当は「PracticsPredicate.java」にするつもりだったんで、「名前」の変更もしておきます。
「パッケージ・エクスプローラ」上で、名前を修正したいファイルを選択した状態で右クリックし、「リファクタリング(T)」>「名前変更(N)...」を選択。
「新しい名前(M):」を入力して、「完了(F)」を選択。
もし、「コンパイル単位名の変更」とか聞かれたら、「完了(F)」を選択でOKかと。
最終的に、2つのクラスを作成したことになります。
コーディングは以下のような感じになりました。
■/Java_Functional_Practics/src/predict/entity/Skill.java
package predict.entity; public class Skill { private String skillId; private String skillName; private int skillLevel; private String skillAttribute; private double YearsOfSkillLearning; public String getSkillId() { return skillId; } public void setSkillId(String skillId) { this.skillId = skillId; } public String getSkillName() { return skillName; } public void setSkillName(String skillName) { this.skillName = skillName; } public int getSkillLevel() { return skillLevel; } public void setSkillLevel(int skillLevel) { this.skillLevel = skillLevel; } public String getSkillAttribute() { return skillAttribute; } public void setSkillAttribute(String skillAttribute) { this.skillAttribute = skillAttribute; } public double getYearsOfSkillLearning() { return YearsOfSkillLearning; } public void setYearsOfSkillLearning(double yearsOfSkillLearning) { YearsOfSkillLearning = yearsOfSkillLearning; } }
■/Java_Functional_Practics/src/predict/PracticsPredicate.java
package predict; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Optional; import java.util.function.Predicate; import java.util.stream.Collectors; import predict.entity.Skill; public class PracticsPredicate { public static void main(String[] args) { // データ(外部からJSONとかで渡されてくる想定) String[][] skillInputTwoDimArr = { { "1", "Drawing", "8", "Artist", "10" }, { "2", "Building", "20", "Builder", "13" }, { "3", "Hacking", "50", "Hacker", "20" }, { "4", "Study", "1000", "Scinetist", "40" }, { "5", "Learning", "70", "Enginner", "50" } }; // 2次元配列から2次元のリストへ変換 List<List<String>> skillList = Arrays.stream(skillInputTwoDimArr).map(Arrays::asList) .collect(Collectors.toList()); // Skillエンティティ格納用 List<Skill> skillEntityList = new ArrayList<>(); // Skillエンティティを作成して、データを設定 for (List<String> list : skillList) { Skill skill = new Skill(); for (int index = 0; index < list.size(); index++) { skill.setSkillId(list.get(0)); skill.setSkillName(list.get(1)); skill.setSkillLevel(Integer.valueOf(list.get(2))); skill.setSkillAttribute(list.get(3)); skill.setYearsOfSkillLearning(Double.valueOf(list.get(4))); } skillEntityList.add(skill); } // 条件を用意 Predicate<Skill> predicateNoviceLevel = skill -> skill.getSkillLevel() < 10; Predicate<Skill> predicateNormalLevel = skill -> skill.getSkillLevel() >= 10 && skill.getSkillLevel() < 30; Predicate<Skill> predicateHighLevel = skill -> skill.getSkillLevel() >= 30 && skill.getSkillLevel() < 60; Predicate<Skill> predicateSuperLevel = skill -> skill.getSkillLevel() >= 60 && skill.getSkillLevel() < 100; Predicate<Skill> predicateGodLevel = skill -> skill.getSkillLevel() >= 100; // 標準クラスの条件 List<Predicate<Skill>> predicateStandardCourseList = Arrays.asList(predicateNoviceLevel, predicateNormalLevel, predicateHighLevel); // 特殊クラスの条件 List<Predicate<Skill>> predicateSpecialCourseList = Arrays.asList(predicateSuperLevel, predicateGodLevel); // 標準クラスで分類 List<Optional<Skill>> standardCourseSkillList = new ArrayList<>(); predicateStandardCourseList.forEach( predicate ->{ standardCourseSkillList.add(skillEntityList.stream().filter(predicate).findAny()); }); // 特殊クラスで分類 List<Optional<Skill>> specialCourseSkillList = new ArrayList<>(); predicateSpecialCourseList.forEach( predicate ->{ specialCourseSkillList.add(skillEntityList.stream().filter(predicate).findAny()); }); // 結果を確認 System.out.println("■標準コース"); standardCourseSkillList.forEach(skill -> { System.out.println(skill.get().getSkillAttribute() + ":" + skill.get().getSkillName()); }); System.out.println("■特殊コース"); specialCourseSkillList.forEach(skill -> { System.out.println(skill.get().getSkillAttribute() + ":" + skill.get().getSkillName()); }); } }
⇧ パッケージ名も変えるべきでしたね...
で、「main」メソッドを持った「メインクラス」を選択した状態で右クリックし、「実行」>「Java アプリケーション」で実行してみると、
「コンソール」に結果が表示されました。
続きまして、「java.util.function」パッケージの「Consumer」を使ってみました。
⇧ 上記サイト様を参考にさせていただきました。
パッケージとファイルを追加してます。
コーディングは以下のような感じになりました。(「Predicate」の時に作成してる「Skill」クラスのコードは掲載を割愛してます。)
■/Java_Functional_Practics/src/consumer/PracticsConsumer.java
package consumer; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.function.Consumer; import java.util.stream.Collectors; import predict.entity.Skill; public class PracticsConsumer { public static void main(String[] args) { // データ(外部からJSONとかで渡されてくる想定) String[][] skillInputTwoDimArr = { { "1", "Drawing", "8", "Artist", "10" }, { "2", "Building", "20", "Builder", "13" }, { "3", "Hacking", "50", "Hacker", "20" }, { "4", "Study", "1000", "Scinetist", "40" }, { "5", "Learning", "70", "Enginner", "50" } }; // 2次元配列から2次元のリストへ変換 List<List<String>> skillList = Arrays.stream(skillInputTwoDimArr).map(Arrays::asList) .collect(Collectors.toList()); // Skillエンティティ格納用 List<Skill> skillEntityList = new ArrayList<>(); // Skillエンティティを作成して、データを設定 for (List<String> list : skillList) { Skill skill = new Skill(); for (int index = 0; index < list.size(); index++) { skill.setSkillId(list.get(0)); skill.setSkillName(list.get(1)); skill.setSkillLevel(Integer.valueOf(list.get(2))); skill.setSkillAttribute(list.get(3)); skill.setYearsOfSkillLearning(Double.valueOf(list.get(4))); } skillEntityList.add(skill); } // skillAtributeだけ、大文字変換する処理 Consumer<List<Skill>> upperCaseConsumer = list -> { for(int index = 0; index < list.size(); index++) { list.get(index).setSkillAttribute(list.get(index).getSkillAttribute().toUpperCase()); list.set(index, list.get(index)); } }; // skillAtributeとskillNameを出力する処理 Consumer<List<Skill>> printConsumer = list -> list.stream().forEach(skill -> { StringBuilder sb = new StringBuilder(); sb.append(skill.getSkillAttribute()).append(":").append(skill.getSkillName()); System.out.println(sb.toString()); sb.setLength(0); }); // 処理順を決めて、skillListEntityに対して、実際に処理を実行する upperCaseConsumer.andThen(printConsumer).accept(skillEntityList); } }
⇧ 結果はちゃんと出力されたけども、う~ん、「Consumer」は使い道を考えるのが難しそう...リストで使うんであれば、並び替えとかの用途になるんかな。
続きまして、「java.util.function」パッケージの「Function」を使ってみました。
⇧ 上記サイト様を参考にさせていただきました。
コーディングは以下のような感じになりました。(「Predicate」の時に作成してる「Skill」クラスのコードは掲載を割愛してます。)
■/Java_Functional_Practics/src/function/PracticsFunction.java
package function; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.function.Function; import java.util.stream.Collectors; import predict.entity.Skill; public class PracticsFunction { public static void main(String[] args) { // データ(外部からJSONとかで渡されてくる想定) String[][] skillInputTwoDimArr = { { "1", "Drawing", "8", "Artist", "10" }, { "2", "Building", "20", "Builder", "13" }, { "3", "Hacking", "50", "Hacker", "20" }, { "4", "Study", "1000", "Scinetist", "40" }, { "5", "Learning", "70", "Enginner", "50" } }; // 2次元配列から2次元のリストへ変換 List<List<String>> skillList = Arrays.stream(skillInputTwoDimArr).map(Arrays::asList) .collect(Collectors.toList()); // Skillエンティティ格納用 List<Skill> skillEntityList = new ArrayList<>(); // Skillエンティティを作成して、データを設定 for (List<String> list : skillList) { Skill skill = new Skill(); for (int index = 0; index < list.size(); index++) { skill.setSkillId(list.get(0)); skill.setSkillName(list.get(1)); skill.setSkillLevel(Integer.valueOf(list.get(2))); skill.setSkillAttribute(list.get(3)); skill.setYearsOfSkillLearning(Double.valueOf(list.get(4))); } skillEntityList.add(skill); } Function<Skill, Double> getLearningCostFunction = skill -> skill.getYearsOfSkillLearning(); Function<Double, Double> costIncrementFunction = cost -> cost + 1; skillEntityList.stream() .map(skill -> getLearningCostFunction.andThen(costIncrementFunction) .apply(skill)) .forEach(System.out::println); } }
⇧「Function」はさらに使い方を考えるのが難しい気がしますね...
そもそも「引数」を1つしか渡せないって縛りがあるから、オブジェクトを格納したリスト渡しても、定数を足したりとかしかでき無さそうなのよね...
消費税とかみたいに、一律で決まった値とかであれば定数を加算とかでも問題ないとは思うけども。
最後に、「java.util.function」パッケージの「Supplier」を使ってみました。
⇧ 上記サイト様を参考にさせていただきました。
「パッケージ・エクスプローラー」でパッケージを選択した状態で右クリックし「新規(W)」>「インターフェイス」を選択で。
「名前(M):」を入力し、「完了(F)」を選択。
そしたらば、いま作成した「インターフェイス」を「implements」する「クラス」である、「Retangle」「Square」「Circle」の3つの「クラス」を作ります。
「パッケージ・エクスプローラー」でパッケージを選択した状態で右クリックし「新規(W)」>「クラス」を選択で。
「名前(M):」を入力し、「インターフェイス(I):」の「追加(A)...」を選択。
先ほど作成した「インターフェイス」である「Shape」を選択して「OK」を押下。(「Java SE」で用意されてる標準の「API」で、「java.awt.Shape」ってのもあるので紛らわしいけども...)
「完了(F)」を選択。
割愛しますが、他の2つのクラス「Square」「Circle」も同じ感じで作成しておきます。
続きまして、「パッケージ・エクスプローラー」でパッケージを選択した状態で右クリックし「新規(W)」>「列挙型」を選択で。
「名前(M):」を適当に入力して、「完了(F)」を選択で。
残りは、「クラス」を2つ作成しますが、手順のキャプチャ画像は割愛させていただきます。
最終的なファイル構成は、以下のような感じになりました。
コーディングは以下のような感じになりました。(参考サイト様のまんまですが...)
■/Java_Functional_Practics/src/supplier/Shape.java
package supplier; public interface Shape { void draw(); }
■/Java_Functional_Practics/src/supplier/Rectangle.java
package supplier; public class Rectangle implements Shape { @Override public void draw() { System.out.println("Inside Rectangle::draw() method."); } }
■/Java_Functional_Practics/src/supplier/Square.java
package supplier; public class Square implements Shape { @Override public void draw() { System.out.println("Inside Square::draw() method."); } }
■/Java_Functional_Practics/src/supplier/Circle.java
package supplier; public class Circle implements Shape { @Override public void draw() { System.out.println("Inside Circle::draw() method."); } }
■/Java_Functional_Practics/src/supplier/ShapeType.java
package supplier; public enum ShapeType { CIRCLE, RECTANGLE, SQUARE; }
■/Java_Functional_Practics/src/supplier/ShapeFactory.java
package supplier; import java.util.HashMap; import java.util.Map; import java.util.function.Supplier; public class ShapeFactory { final static Map<ShapeType, Supplier<Shape>> map = new HashMap<>(); static { map.put(ShapeType.CIRCLE, Circle::new); map.put(ShapeType.RECTANGLE, Rectangle::new); map.put(ShapeType.SQUARE, Square::new); } public Shape getShape(ShapeType shapeType) { Supplier<Shape> shape = map.get(shapeType); if (shape != null) { return shape.get(); } throw new IllegalArgumentException("No such shape " + shapeType.name()); } }
■/Java_Functional_Practics/src/supplier/PracticsSupplier.java
package supplier; import java.util.function.Supplier; public class PracticsSupplier { public static void main(String[] args) { Supplier<ShapeFactory> shapeFactory = ShapeFactory::new; // get an object of Circle and call its draw method. Shape shape1 = shapeFactory.get().getShape(ShapeType.CIRCLE); // call draw method of Circle shape1.draw(); // get an object of Rectangle and call its draw method. Shape shape2 = shapeFactory.get().getShape(ShapeType.RECTANGLE); // call draw method of Rectangle shape2.draw(); // get an object of Square and call its draw method. Shape shape3 = shapeFactory.get().getShape(ShapeType.SQUARE); // call draw method of square shape3.draw(); } }
⇧ ってな感じで編集して保存したらば、「実行(R)」>「Java アプリケーション」を選択で実行。
⇧「Supplier」については、「デザインパターン」とかも知っておかないと、有効活用が難しそうですね、他の「関数型インターフェイス」でも言えるのかもしれませんが。
一応、「Predicate」「Consumer」「Function」「Supplier」の4つを使ってみましたが、どんな時に使えばよいかのベストプラクティス的な情報が分かりにくい感じがしましたが、このあたりの情報については「関数型インターフェイス」を推してる有識者の方が情報発信してくれることに期待ですかね。
今回はこのへんで。