LoginSignup
0
0

More than 3 years have passed since last update.

【Java発展学習3日目】関数インタフェースと関数オブジェクトの扱い方

Last updated at Posted at 2021-07-01

標準関数インタフェース(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)

Collection.png

配列(array)

Array.png

マップ(map)

Map.png

定義(ストリームの生成)

ストリームの生成
// コレクションからストリームを生成
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

DeclarativeStream.java
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) プログラムを「高階関数の集まり」と捉えるプログラミング手法。
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0