標準関数インタフェース(standard function interface)
参考: Functional Interface(Java SE 16 & JDK 16)
関数オブジェクト
を格納するために用意されたインタフェース
であり、条件に応じて使い分ける必要がある。
なお、標準関数インタフェース
の命名規則は以下の通り。
インタフェース | 内容 | 引数 | 戻り値 |
---|---|---|---|
*Function |
型 の変換
|
T | T |
*Operator |
値 の変換
|
T | R |
*Consumer |
値 の操作 コンソール出力
|
T | - (=void ) |
*Supplier |
値 の出力
|
- | R |
*Predicate |
条件 の指定 |
T | boolean |
なお、関数インタフェース(func interface)
を独自に定義する場合も、上記の命名規則に従うのが望ましい。
ラムダ式(lambda expression)
メソッド
が実体化されるのは「メソッドを定義したクラスがJVMに読み込まれた時」であるのに対し、
ラムダ式
で記述した関数が実体化されるのは「関数の実行時」となる。
また、ラムダ式
はクロージャ(closure)
を利用することで、
ラムダ式
の外部で定義された「実質的にfinal
」な変数
を参照することができる。
関数オブジェクトとラムダ式の有用性
関数オブジェクト
やラムダ式
を用いて「関数
をオブジェクト
化する」ことによって、
処理(アルゴリズム)
をデータ
として受け渡すことが可能となる。
また、関数インタフェース
の実装クラスオブジェクト
の代わりに関数オブジェクト
やラムダ式
を用いることができる。
ラムダ式の省略記法
// 省略記法を用いない場合
IntToDoubleFunction func = (int x) -> {
return x * x * 3.14;
}
// 代入式の「右辺」では「引数の型省略」が可能
IntToDoubleFunction func = (x) -> {
return x * x * 3.14;
}
// 引数が「1つ」の場合は「丸括弧」が省略可能
IntToDoubleFunction func = x -> {
return x * x * 3.14;
}
// ラムダ式が「単一のreturn文」の場合は「波括弧」と「return」が省略可能
IntToDoubleFunction func = x -> x * x * 3.14;
サンプルコード(関数インタフェースとラムダ式)
class MainA {
public static void main(String[] args) {
// 「静的メソッド」(=関数オブジェクト)の「ポインタ」を関数インタフェースに格納
// -> 「静的メソッド」のポインタは"<クラス>::<静的メソッド>"で表現
TernaryStringToStringFunction<String, String, String, String> staticFunc = FuncInterfaceTest::staticUnion;
// 動的メソッドの利用にあたってインスタンスを生成
FuncInterfaceTest fit = new FuncInterfaceTest();
// 「動的メソッド」(=関数オブジェクト)の「ポインタ」を関数インタフェースに格納
// -> 「動的メソッド」のポインタは"<クラスオブジェクト>::<動的メソッド>"で表現
TernaryStringToStringFunction<String, String, String, String> dynamicFunc = fit::dynamicUnion;
// ラムダ式による関数の記述
TernaryStringToStringFunction<String, String, String, String> lambdaFunc =
(String str1, String str2, String str3) -> { return str1 + str2 + str3; };
// 関数オブジェクト・ラムダ式関数の呼び出し
// -> 関数オブジェクト・ラムダ式関数(のポインタ)の格納先は「関数インタフェース」であるため、
// "<関数インタフェース>.<抽象メソッド>"で関数オブジェクトを呼び出す
String staticUnionStr = staticFunc.union("A", "B", "C");
String dynamicUnionStr = dynamicFunc.union("1", "2", "3");
String lambdaUnionStr = lambdaFunc.union("あ", "い", "う");
System.out.println("Static Method: " + staticUnionStr);
System.out.println("Dynamic Method: " + dynamicUnionStr);
System.out.println("Lambda Method: " + lambdaUnionStr);
}
}
class FuncInterfaceTest {
// 静的メソッド
public static String staticUnion(String str1, String str2, String str3) {
return str1 + str2 + str3;
}
// 動的メソッド
public String dynamicUnion(String str1, String str2, String str3) {
return str1 + str2 + str3;
}
}
// 独自定義した関数インタフェース
// -> "@FunctionalInterface"アナテイションを付与することで、「関数インタフェース」であることを明示的に宣言
// -> 「関数インタフェース」であるため、「ただ1つの抽象メソッド」のみを定義
@FunctionalInterface
interface TernaryStringToStringFunction<F, S, T, R> {
public abstract String union(String str1, String str2, String str3);
}
Static Method: ABC
Dynamic Method: 123
Lambda Method: あいう
Streamインタフェース(=Stream API)
ストリーム(stream)
に対して宣言的(declarative)
に処理を行う高階関数(high-order function)
をまとめたインタフェース
。
ストリーム
は処理データ
を格納したコレクション
や配列
を通じて生成され、ポインタ
に格納された処理データ
を直接操作することができる。
なお、Stream
インタフェースはAutoCloseable
インタフェースを継承するBaseStream
インタフェースを継承しており、
ストリーム
が生成されてから一度でも返却型がStream<T>
型でない終端処理メソッド
が実行されると、
自動的にBaseStream
インタフェースのclose()
メソッドが呼び出される。
ただし、返却型がStream<T>
型である中間処理メソッド
はストリーム
をクローズ
しない。
コレクション(collection)/配列(array)/マップ(map)
参考1: コレクション
参考2: ArrayListとLinkedListの違い
参考3: ArrayListとLinkedListの使い分け
参考4: Java発展学習1日目
コレクション(collection)
配列(array)
マップ(map)
定義(ストリームの生成)
// コレクションからストリームを生成
Stream<E> Collection<E>.stream()
// 配列からストリームを生成
Stream<T> Arrays.stream(T[] array)
// パラメータ
// array: T型データの配列
// 「可変長引数」で構成要素を指定してストリームを生成
@SafeVarargs Stream<T> Stream.of(T... values)
// パラメータ
// values: ストリームのT型構成要素
定義(中間処理メソッド)
// 要素の値に「重複」がないストリームを返却
Stream<T> Stream<T>.distinct()
// 「条件」を表す関数を満たす要素で構成されるストリームを返却
Stream<T> Stream<T>.filter(Predicate<? super T> predicate)
// パラメータ
// predicate: 「条件」を表すPredicate型の関数
// 要素数を「制限」したストリームを返却
Stream<T> Stream<T>.limit(long maxSize)
// パラメータ
// maxSize: 「要素数」の上限値
// 「自然順序付け」されたストリームを返却
Stream<T> Stream<T>.sorted()
// 関数の「戻り値」で構成されたストリームを返却
Stream<R> Stream<T>.map(Function<? super T, ? extends R> mapper)
// パラメータ
// mapper: 「型の変換」を表すFunction型の関数
// 順次ストリーム -> 並列ストリーム への変換
// -> Stream<T>はBaseStream<T, Stream<T>>を継承
S BaseStream<T, S extends BaseStream<T, S>>.parallel()
定義(終端処理メソッド)
// 「全要素」が「条件」を表す関数を満たしている場合はtrueを返却
// ※ストリームが「空」である場合もtrueが返却される
boolean Stream<T>.allMatch(Predicate<? super T> predicate)
// パラメータ
// predicate: 「条件」を表すPredicate型の関数
// 「少なくとも1要素」が「条件」を表す関数を満たしている場合はtrueを返却
// ※ストリームが「空」である場合もtrueが返却される
boolean Stream<T>.anyMatch(Predicate<? super T> predicate)
// パラメータ
// predicate: 「条件」を表すPredicate型の関数
// 「全要素」が「条件」を表す関数を満たしていない場合はtrueを返却
// ※ストリームが「空」である場合もtrueが返却される
boolean Stream<T>.noneMatch(Predicate<? super T> predicate)
// パラメータ
// predicate: 「条件」を表すPredicate型の関数
// 「各要素」に関数を適用
void Stream<T>.forEach(Consumer<? super T> action)
// パラメータ
// action: 「値の操作」または「コンソール出力」を行うConsumer型の関数
// ストリームの「最初の要素」を返却
Optional<T> Stream<T>.findFirst()
// ストリームの「最初に見つかった要素」を返却
// -> 「マルチスレッド」でストリームを処理する場合(=並列ストリーム)は結果が異なる"可能性"がある
// => スレッドの負荷が大きい場合は必ずしも「最初の要素」が返却されるというわけではない
Optional<T> Stream<T>.findAny()
// ストリームの「要素数」を返却
long Stream<T>.count()
// 関数(=コンパレータ)を満たす「最大値の要素」を返却
Optional<T> Stream<T>.max(Comparator<? super T> comparator)
// パラメータ
// comparator: 「比較対象」を表すComparator型のコンパレータ
// 関数(=コンパレータ)を満たす「最大値の要素」を返却
Optional<T> Stream<T>.min(Comparator<? super T> comparator)
// パラメータ
// comparator: 「比較対象」を表すComparator型のコンパレータ
サンプルコード
参考1: findFirst()とfindAny()
参考2: Stream API
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
public class DeclarativeStream {
public static void main(String[] args) {
// ストリームの構成要素をリスト(コレクション)で定義
List<CompareAWithB> list = new ArrayList<>();
list.add(new CompareAWithB("A", 9));
list.add(new CompareAWithB("B", 7));
list.add(new CompareAWithB("C", 12));
// コレクションからストリームを生成
Stream<CompareAWithB> stm = list.stream();
// ストリームの要素数
// -> Streamは一度でも「終端処理」が行われるとクローズされるため、再利用不可
long c = stm.count();
System.out.println("components in Stream: " + c);
// プロパティiの値が10未満である各要素に対して行う処理
list.stream()
// 中間処理
.filter( i -> i.getInt() < 10)
// 終端処理
.forEach(i -> i.addString("(less than 10)"));
// Optional<T>型は「Optional.empty」で初期化される
// <- 「ローカル変数」は初期化必須
Optional<CompareAWithB> comOpt;
CompareAWithB com = new CompareAWithB();
System.out.println("-- findAny() by Multi Thread--");
for (int i = 0; i < 100; i++) {
// 順次ストリーム -> 並列ストリーム への変換
Stream<CompareAWithB> palStm = list.stream().parallel();
// 並列ストリームで最初に見つかった要素を返却
comOpt = palStm.findAny();
// Optional<T>型を取り扱う際はnull安全に配慮
if (comOpt.isPresent()) {
// Optional<CompareAWithB>型 -> CompareAWithB型 への変換
com = comOpt.get();
}
if (com != null) {
System.out.println( (i + 1) + ": " + com );
}
}
// 全要素のプロパティiの値が3より大きいである場合の処理
if (list.stream().allMatch( (CompareAWithB i) -> {
return i.getInt() > 3;
})) {
System.out.println("All components have properties with 3 or greater.");
}
// プロパティiの値が最小である要素
// -> 「比較対象」(=コンパレータ)を指定
Optional<CompareAWithB> minCon = list.stream().min((x, y) -> x.getInt() - y.getInt());
System.out.println("component with the minimum value: " + minCon.get());
}
}
class CompareAWithB {
private String str;
private int i;
// デフォルトコンストラクタ
public CompareAWithB() {}
// コンストラクタ
public CompareAWithB(String str, int i) {
this.str = str;
this.i = i;
}
// ゲッタ(=アクセサ)
public String getString() {
return this.str;
}
public int getInt() {
return this.i;
}
// 文字列を追加
public void addString(String s) {
this.str += s;
}
@Override
public String toString() {
return "CompareAWithB(" + this.str + ", " + this.i + ")";
}
}
components in Stream: 3
-- findAny() by Multi Thread--
1: CompareAWithB(B(less than 10), 7)
...
13: CompareAWithB(A(less than 10), 9)
14: CompareAWithB(C, 12)
...
All components have properties with 3 or greater.
component with the minimum value: CompareAWithB(B(less than 10), 7)
用語集
用語 | 内容 |
---|---|
第1級オブジェクト(first-class object) |
基本的操作 (=生成 ・代入 ・演算 ・受け渡し など)を制限なく実行できるもの。 |
関数(function) |
入力(input) を受け取り、処理(process) を行った後に実行結果を出力(output) するもの。= IPO構造 を伴う処理ロジック 。 |
関数インタフェース(func interface) |
1つの抽象メソッド のみを有する(=SAM; Single Abstract Method )インタフェース 。@FunctionalInterface アナテイションを用いて明示的に宣言することができる。 |
クロージャ(closure) |
関数 の実体化時に、その時点でアクセス可能 な変数を参照 できる特性。 |
高階関数(high-order function) | 引数として関数 を受け取る関数。 |
宣言的(declarative) |
目的 のみを記述し、その方法 を記述しないこと。 |
ストリーム(stream) |
宣言的 に処理を行うためのデータ の並び。 |
順次ストリーム(sequential stream) |
シングルスレッド で処理されるストリーム 。 |
並列ストリーム(parallel stream) |
マルチスレッド で処理されるストリーム 。 |
手続き型プログラミング(procedural programming) |
プログラム を「順番に実行される命令文 の集まり」と捉えるプログラミング手法。 |
オブジェクト指向プログラミング(object-oriented programming) |
プログラム を「責務をもつ部品 の集まり」と捉えるプログラミング手法。 |
関数型プログラミング(functional programming) |
プログラム を「高階関数 の集まり」と捉えるプログラミング手法。 |