「java.util.function」パッケージで追加された「関数型インターフェイス(Functional interface)」などの話

f:id:ts0818:20211013100530j:plain

www.itmedia.co.jp

 IPA情報処理推進機構)は10月11日、日米企業のDX(デジタルトランスフォーメーション)動向を比較調査し、戦略や人材、技術などを包括的に解説した「DX白書2021」を発刊した。計372ページのPDFをIPAのWebサイトでダウンロードできる。ITとビジネスの関係が密接になっているため、同白書を通じて日本企業のDX推進を支援したい考え。

日米企業のDX動向を372ページで比較、IPAが「DX白書2021」を無償公開 - ITmedia NEWS

⇧ 372ページ...超大作ですな。

今回は、Javaの「関数型インターフェイス(Functional Interface)」などの話についてです。

レッツトライ~。

 

Javaの「関数型インターフェイス(Functional interface)」とは?

手前味噌で恐縮ですが、

ts0818.hatenablog.com

⇧ 前回の記事で、Javaの「ラムダ式(Lambda Expression)」を調べていて、Javaの「ラムダ式(Lambda Expression)」を実現するための「型」として、Javaでは「関数型インターフェイス(Functional Interface)」を定義せざるを得なかったという話でした。

Java SE 8」のAPIドキュメントの説明でも、

docs.oracle.com

パッケージjava.util.functionの説明 

関数型インタフェースは、ラムダ式やメソッド参照のターゲットとなる型を提供します。各関数型インタフェースには、その関数型インタフェースの関数メソッドと呼ばれる単一の抽象メソッドが含まれており、ラムダ式のパラメータや戻り値の型のマッチングや適応は、そのメソッドに対して行われます。関数型インタフェースは代入の文脈、メソッド呼び出しの文脈、キャストの文脈など、さまざまな文脈でターゲットの型を提供できます。

https://docs.oracle.com/javase/jp/8/docs/api/java/util/function/package-summary.html#package.description

⇧ どうやら「ラムダ式(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

ちなみに、

yohhoy.hatenadiary.jp

Java 8のjava.util.Comparatorインタフェースは、その宣言からは2つのメソッドを持つようにみえるが関数型インタフェース(functional interface)である。

java.util.Comparatorは関数型インタフェース - yohhoyの日記

⇧まさかの「java.util.function」パッケージ以外でも「関数型インターフェイス(Functional Interface)」が用意されてるということらしい。

確かに、APIドキュメントを見てみると、

docs.oracle.com

Interface Comparator<T>
Type Parameters:
T - the type of objects that may be compared by this comparator
All Known Implementing Classes:
CollatorRuleBasedCollator
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)」にされてる「インターフェイス」って存在するのか?ってことですよね...

そして、

docs.oracle.com

ただし、インタフェース宣言にFunctionalInterface注釈型が存在しているかどうかにかかわらず、コンパイラは関数型インタフェースの定義を満たすどのインタフェースも関数型インタフェースとして扱います。

https://docs.oracle.com/javase/jp/8/docs/api/java/lang/FunctionalInterface.html

⇧ 終わった...

「関数型インターフェイス(Functional Interface)」がどれだけ用意されてるのか、中の人以外は誰もその全量が把握できん状況になってるやつだ...

「stackoverflow」でも、

stackoverflow.com

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!

https://stackoverflow.com/questions/42942351/do-you-have-a-list-of-java-8-functional-interfaces-not-the-ones-listed-in-java

⇧ 同じような疑問を持った方がいて、「問いかけ」てますが、これといった有効的な方法が無いっぽい模様...

脱線しましたが、「java.util.function」パッケージで追加された「関数型インターフェイス(Functional Interface)」で、それぞれ、「引数」と「戻り値」がどうなってるかについて、

nowokay.hatenablog.com

⇧ 上記サイト様がまとめてくださっていましたので、参考に整理してみると、

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個になってるやんけ~!、って言うのには理由があるらしく、

nowokay.hatenablog.com

引数をとらず戻り値もない場合に使えるインタフェースはfunctionパッケージに用意されていないようなので、Runnableインタフェースを使うことになります。

Java8 Lambdaの文法拡張まとめ - きしだのHatena

⇧ 上記サイト様によりますと、「Runnable」って「関数型インターフェイス(Functional Interface)」を使うことになるからだそうです。

ちなみに、「java.lang」パッケージの「Runnable」についても、

docs.oracle.com

Interface Runnable
All Known Subinterfaces:
RunnableFuture<V>, RunnableScheduledFuture<V>
All Known Implementing Classes:
AsyncBoxView.ChildStateForkJoinWorkerThreadFutureTaskRenderableImageProducerSwingWorkerThreadTimerTask
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)」に変更されたということかと。

ちなみに、

qiita.com

Java8のFunctionインターフェースにはインプットが3つ以上のインターフェースが提供されていません。
ならば、Functionインターフェースを自作してみましょう。
実は、ありがたいことに、Java8がただ単純にインプットが3つ以上の関数型インターフェースを提供してないだけで、(少なくてもOracleとOpenJDKのJavaでは)複数の引数へ対応する準備が整っています。

ラムダ式で3つ以上の引数に対応しよう - Qiita

JavaScriptの経験者であれば、既に思い付いたかもしれません。カリー化すればいいです。

ラムダ式で3つ以上の引数に対応しよう - Qiita

⇧ という感じで、「自作」or「カリー化」のどっちかで、3つ以上の「引数」で「関数型インターフェイス(Functional Interface)」を実施することも可能らしい。

 

実際に「java.util.function」パッケージで用意されてる「関数型インターフェイス(Functional interface)」を使ってみる

というわけで、改めて、「java.util.function」パッケージのAPIドキュメントを確認してみると、

docs.oracle.com

パッケージ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個もあるので、

builder.japan.zdnet.com

 Java SE 8では、ラムダ式を適用することのできる43個の関数型インタフェースが新たに追加されました。それらをいきなりすべて覚える必要はありません。初めは、次に示す4つの関数型インタフェースから覚えるとよいでしょう。これらの関数型インタフェースを引数に取る部分でラムダ式を書くことができます。

ラムダ式とストリームAPIでJavaプログラミングはここまでシンプルになる!--Java SE 8に今すぐ移行すべき理由 - page2 - builder by ZDNet Japan

⇧上記サイト様も仰っているように、まずは、基本形っぽい上記の4つから始めるのが吉、ということで4つに絞って使ってみますか。

その前に、この「引数」とか「戻り値」でしれっと何の説明もなく「T」とか「R」とか記載されてるんだけど、これは、

programmer-life.work

<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)」を使ってみますか。

で、ここで、早速、疑問が浮かびましたと。

docs.oracle.com

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さん。

で、探したところ、

cr.openjdk.java.net

Outline

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)」については、

cr.openjdk.java.net

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 APIJRE Library)」に含まれてるので、「Java SE」をインストールしていれば使えます、ただし、「Java SE 8」以上。

で、「Eclipse」は「JDKJava Development Kit)」が内蔵されてるので、「Java SE」をインストールしなくても、「Java」を利用はできますと。

Java SE」は「Java Standard Edition」の略で、

Wikipediaさんの「Jakarta EE(旧:Java EE)」の説明にある図を見た感じでは、「Java SE」をインストールすると「JDKJava Development Kit)」なんかも同梱されてくるようです。

脱線しましたが、「Eclipse」が起動できたら、「ファイル(F)」>「新規(N)」>「Java プロジェクト」を選択。(「Java プロジェクト」が選択肢に無い場合は、「その他(O)...」を選択で、見つかるはず)

f:id:ts0818:20211011222509p:plain

で、「プロジェクト名(P):」を入力して、「次へ(N)>」を選択。

f:id:ts0818:20211011222843p:plain

特に何も変更せず「完了(F)」を選択。

f:id:ts0818:20211011222919p:plain

「モジュール」の作成を聞かれたら、今回は「作成しない(D)」を選択。

f:id:ts0818:20211011223142p:plain

そうすると、「パッケージ・エクスプローラー」に、作成した「Java プロジェクト」が表示されます。

f:id:ts0818:20211011223337p:plain

ちなみに、「パッケージ・エクスプローラー」は、「パースペクティブ」が「Java」を選択してる場合で、

f:id:ts0818:20211011224350p:plain

パースペクティブ」が「Java EE」だと「プロジェクト・エクスプローラー」がデフォルトになるかと。

f:id:ts0818:20211011224607p:plain

脱線しましたが、作成した「Java プロジェクト」の中にある「JRE システム・ライブラリー」の中に、標準の「Java API」のライブラリがあって「java.util.function」パッケージも存在することが確認できたので、あとは、「src」フォルダに「拡張子」が「.java」のファイルを作成してコーディングしていく感じで。

f:id:ts0818:20211011223543p:plain

というわけで、「パッケージ・エクスプローラー」上で、作成した「Java プロジェクト」内の「src」フォルダを選択した状態で右クリックし、「新規(W)」>「クラス」を選択。(「クラス」が選択肢に無い場合は、「その他(O)...」を選択で、見つかるはず)

f:id:ts0818:20211011224036p:plain

で、「パッケージ(K):」、「名前(M):」を適当に入力し、「どのメソッド・スタブを作成しますか?」で「public static void main(String[] args)(V)」のチェックボックスにチェックを入れて、「完了(F)」を選択。

f:id:ts0818:20211011225031p:plain

「クラス」ファイルが作成されるので、コーディングしていきます。

で、まずは「関数型インターフェイス(Functional Interface)」の「Predicate」は、「第1引数」に「型パラメーター」である「T」、「第2引数」は「なし」で、「戻り値」が「boolean」になるように「ラムダ式(Lambda Expression)」を構成してあげれば良いらしいですと。

せっかくなんで、「T」のクラスを自作してみますか。

「Predicate」は、最終的に「boolean」が「戻り値」になれば良いので、用途としては、「T」に持たせた「フィールド(メンバ変数)」の値が、条件に合うか合わないかみたいなことを想定してるんじゃないかと。

というわけで、先ほど作成した「メインクラス(今回だとPracticsPredict.java)」以外にも、「Skill.java」というものをクラスを作りました。

というか、「PracticsPredict.java」の名前についてはタイピングミスってました、本当は「PracticsPredicate.java」にするつもりだったんで、「名前」の変更もしておきます。

f:id:ts0818:20211012161456p:plain

「パッケージ・エクスプローラ」上で、名前を修正したいファイルを選択した状態で右クリックし、「リファクタリング(T)」>「名前変更(N)...」を選択。

f:id:ts0818:20211012162108p:plain

「新しい名前(M):」を入力して、「完了(F)」を選択。

f:id:ts0818:20211012162201p:plain

もし、「コンパイル単位名の変更」とか聞かれたら、「完了(F)」を選択でOKかと。

f:id:ts0818:20211012162311p:plain

最終的に、2つのクラスを作成したことになります。

f:id:ts0818:20211012162350p:plain

コーディングは以下のような感じになりました。

■/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 アプリケーション」で実行してみると、

f:id:ts0818:20211012163201p:plain

「コンソール」に結果が表示されました。

f:id:ts0818:20211012163723p:plain


続きまして、「java.util.function」パッケージの「Consumer」を使ってみました。

medium.com

⇧ 上記サイト様を参考にさせていただきました。

パッケージとファイルを追加してます。

f:id:ts0818:20211012184255p:plain

コーディングは以下のような感じになりました。(「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);
  }
}

f:id:ts0818:20211012184404p:plain

⇧ 結果はちゃんと出力されたけども、う~ん、「Consumer」は使い道を考えるのが難しそう...リストで使うんであれば、並び替えとかの用途になるんかな。

続きまして、「java.util.function」パッケージの「Function」を使ってみました。

www.javainterviewpoint.com

⇧ 上記サイト様を参考にさせていただきました。

f:id:ts0818:20211012221719p:plain

コーディングは以下のような感じになりました。(「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);
  }
}

f:id:ts0818:20211012220828p:plain

⇧「Function」はさらに使い方を考えるのが難しい気がしますね...

そもそも「引数」を1つしか渡せないって縛りがあるから、オブジェクトを格納したリスト渡しても、定数を足したりとかしかでき無さそうなのよね...

消費税とかみたいに、一律で決まった値とかであれば定数を加算とかでも問題ないとは思うけども。

最後に、「java.util.function」パッケージの「Supplier」を使ってみました。

www.javatips.net

stackoverflow.com

www.javaguides.net

⇧ 上記サイト様を参考にさせていただきました。

「パッケージ・エクスプローラー」でパッケージを選択した状態で右クリックし「新規(W)」>「インターフェイス」を選択で。

f:id:ts0818:20211013085405p:plain

「名前(M):」を入力し、「完了(F)」を選択。

f:id:ts0818:20211013085507p:plain

そしたらば、いま作成した「インターフェイス」を「implements」する「クラス」である、「Retangle」「Square」「Circle」の3つの「クラス」を作ります。

「パッケージ・エクスプローラー」でパッケージを選択した状態で右クリックし「新規(W)」>「クラス」を選択で。

f:id:ts0818:20211013090736p:plain

「名前(M):」を入力し、「インターフェイス(I):」の「追加(A)...」を選択。

f:id:ts0818:20211013091202p:plain

先ほど作成した「インターフェイス」である「Shape」を選択して「OK」を押下。(「Java SE」で用意されてる標準の「API」で、「java.awt.Shape」ってのもあるので紛らわしいけども...)

f:id:ts0818:20211013091302p:plain

「完了(F)」を選択。

f:id:ts0818:20211013091337p:plain

割愛しますが、他の2つのクラス「Square」「Circle」も同じ感じで作成しておきます。

続きまして、「パッケージ・エクスプローラー」でパッケージを選択した状態で右クリックし「新規(W)」>「列挙型」を選択で。

f:id:ts0818:20211013084544p:plain

「名前(M):」を適当に入力して、「完了(F)」を選択で。

f:id:ts0818:20211013084647p:plain

残りは、「クラス」を2つ作成しますが、手順のキャプチャ画像は割愛させていただきます。

最終的なファイル構成は、以下のような感じになりました。

f:id:ts0818:20211013093145p:plain

コーディングは以下のような感じになりました。(参考サイト様のまんまですが...)

■/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 アプリケーション」を選択で実行。

f:id:ts0818:20211013093923p:plain

f:id:ts0818:20211013094554p:plain

⇧「Supplier」については、「デザインパターン」とかも知っておかないと、有効活用が難しそうですね、他の「関数型インターフェイス」でも言えるのかもしれませんが。

一応、「Predicate」「Consumer」「Function」「Supplier」の4つを使ってみましたが、どんな時に使えばよいかのベストプラクティス的な情報が分かりにくい感じがしましたが、このあたりの情報については「関数型インターフェイス」を推してる有識者の方が情報発信してくれることに期待ですかね。

今回はこのへんで。