Java8の機能インターフェイス

はじめに

このチュートリアルでは、Java8に存在するさまざまな機能インターフェイスと、その一般的なユースケース、および標準JDKライブラリでの使用方法についてのガイドです。Iterable To Stream in Java

Iterable To Stream in Java

この記事では、IterableをStreamに変換する方法と、Iterableインターフェイスがそれを直接サポートしていない理由について説明します。
続きを読む→

Java8Streamsでif/elseロジックを使用する方法

Java8Streamsにif/elseロジックを適用する方法を学びます。
続きを読む→

Java8のラムダ

Java8は、ラムダ式の形で強力な新しい構文の改善をもたらしました。 ラムダは、ファーストクラスの言語市民として扱うことができる無名関数です。 たとえば、メソッドに渡したり、メソッドから返したりすることができます。

Java8より前は、通常、単一の機能をカプセル化する必要があるすべてのケースに対してクラスを作成していました。 これは、プリミティブな関数表現として機能するものを定義するための不必要な定型コードをたくさん暗示していました。

記事”Lambda Expressions and Functional Interfaces:Tips and Best Practices”では、lambdaを使用する際の機能インターフェイスとベストプラクティスについて詳しく説明しています。 このガイドでは、javaに存在する特定の機能インターフェイスに焦点を当てています。ユーティル機能パッケージ。

Functional Interfaces

すべてのfunctional interfacesには有益な@FunctionalInterfaceアノテーションがあることをお勧めします。 これはインターフェイスの目的を明確に伝え、注釈付きインターフェイスが条件を満たさない場合にコンパイラがエラーを生成することを可能にします。

Sam(Single Abstract Method)を持つ任意のインターフェイスは機能的なインターフェイスであり、その実装はラムダ式として扱うことができます。

Java8のデフォルトメソッドは抽象的ではなく、カウントされないことに注意してください。 関数のドキュメントを見ることでこれを観察することができます。

Functions

ラムダの最も単純で一般的なケースは、ある値を受け取り、別の値を返すメソッドを持つ関数型インターフェイスです。 単一の引数のこの関数は、その引数の型と戻り値によってパラメータ化される関数インターフェイスによって表されます。

public interface Function<T, R> { … }

標準ライcomputeIfAbsentメソッド。 このメソッドは、キーによってマップから値を返しますが、キーがマップにまだ存在しない場合は値を計算します。 値を計算するには、渡された関数の実装を使用します。

Map<String, Integer> nameMap = new HashMap<>();Integer value = nameMap.computeIfAbsent("John", s -> s.length());

この場合、キーに関数を適用し、マップ内に入れ、メソッド呼び出しからも返され ラムダを、渡された値と返された値の型に一致するメソッド参照に置き換えることができます。

メソッドを呼び出すオブジェクトは、実際にはメソッドの暗黙の最初の引数であることを覚えておいてください。 これにより、インスタンスメソッドの長さの参照を関数インターフェイスにキャストできます:

Integer value = nameMap.computeIfAbsent("John", String::length);

関数インターフェイスには、いくつかの関数を一つに結合して順番に実行できるデフォルトのcomposeメソッドもあります。

Function<Integer, String> intToString = Object::toString;Function<String, String> quote = s -> "'" + s + "'";Function<Integer, String> quoteIntToString = quote.compose(intToString);assertEquals("'5'", quoteIntToString.apply(5));

quoteIntToString関数は、intToString関数の結果に適用されるquote関数の組み合わせです。

プリミティブ関数の特殊化

プリミティブ型はジェネリック型の引数にすることはできないため、最も使用されるプリミティブ型double、int、long、:

  • IntFunction, LongFunction, DoubleFunction: arguments are of specified type, return type is parameterized
  • ToIntFunction, ToLongFunction, ToDoubleFunction: return type is of specified type, arguments are parameterized
  • DoubleToIntFunction, DoubleToLongFunction, IntToDoubleFunction, IntToLongFunction, LongToIntFunction, LongToDoubleFunction: 引数と戻り値の型の両方をプリミティブ型として定義し、その名前で指定した

例として、shortを取り、byteを返す関数のためのすぐに使える関数インタ

public byte transformArray(short array, ShortToByteFunction function) { byte transformedArray = new byte; for (int i = 0; i < array.length; i++) { transformedArray = function.applyAsByte(array); } return transformedArray;}

これを使用して、shortの配列を2を掛けたバイトの配列に変換する方法は次のとおりです:

short array = {(short) 1, (short) 2, (short) 3};byte transformedArray = transformArray(array, s -> (byte) (s * 2));byte expectedArray = {(byte) 2, (byte) 4, (byte) 6};assertArrayEquals(expectedArray, transformedArray);

二項関数の特殊化

二つの引数を持つラムダを定義するには、BiFunction、ToDoubleBiFunction、ToIntBiFunction、およびToLongBiFunctionという名前に”Bi”キーワードを含む追加のインターフェイス

BiFunctionには引数と戻り値の型の両方が一般化されていますが、ToDoubleBiFunctionなどはプリミティブ値を返すことができます。

標準APIでこのインターフェイスを使用する典型的な例の一つは、マップ内にあります。replaceAllメソッドは、マップ内のすべての値を計算された値に置き換えることができます。キーと古い値を受け取るBiFunction実装を使用して、給与の新しい値を計算して返しましょう。

Map<String, Integer> salaries = new HashMap<>();salaries.put("John", 40000);salaries.put("Freddy", 30000);salaries.put("Samuel", 50000);salaries.replaceAll((name, oldValue) -> name.equals("Freddy") ? oldValue : oldValue + 10000);

Suppliers

Supplier関数インターフェイスは、引数を取らないさらに別の関数特殊化です。 通常、値の遅延生成に使用します。 たとえば、double値を二乗する関数を定義しましょう。 値自体を受け取るのではなく、この値のサプライヤを受け取ることになります:

public double squareLazy(Supplier<Double> lazyValue) { return Math.pow(lazyValue.get(), 2);}

これにより、Supplier実装を使用してこの関数を呼び出すための引数を遅延的に生成することができます。 これは、引数の生成にかなりの時間がかかる場合に便利です。 GuavaのsleepUninterruptiblyメソッドを使用することをシミュレートします。

Supplier<Double> lazyValue = () -> { Uninterruptibles.sleepUninterruptibly(1000, TimeUnit.MILLISECONDS); return 9d;};Double valueSquared = squareLazy(lazyValue);

サプライヤーの別のユースケースは、シーケンス生成のロジックを定義することです。 それを実証するために、静的ストリームを使用してみましょう。フィボナッチ数のストリームを作成するメソッドを生成します。

int fibs = {0, 1};Stream<Integer> fibonacci = Stream.generate(() -> { int result = fibs; int fib3 = fibs + fibs; fibs = fibs; fibs = fib3; return result;});

ストリームに渡す関数。generateメソッドは、Supplier機能インターフェイスを実装します。 発電機として有用であるためには、供給者は通常、何らかの外部状態を必要とすることに注意してください。 この場合、その状態は最後の2つのフィボナッチ数列で構成されます。

この状態を実装するには、ラムダ内で使用されるすべての外部変数が事実上最終的でなければならないため、いくつかの変数の代わりに配列を使

Supplier関数インターフェイスの他の特殊化には、BooleanSupplier、DoubleSupplier、LongSupplier、IntSupplierがあり、戻り値の型は対応するプリミティブです。

Consumers

Supplierとは対照的に、Consumerは一般化された引数を受け入れ、何も返しません。 これは副作用を表している関数です。

たとえば、コンソールに挨拶を印刷して、名前のリストでみんなに挨拶しましょう。 リストに渡されたラムダ。forEachメソッドは、消費者の機能インターフェイスを実装します。

List<String> names = Arrays.asList("John", "Freddy", "Samuel");names.forEach(name -> System.out.println("Hello, " + name));

消費者の特殊なバージョンもあります—DoubleConsumer、IntConsumerとLongConsumer—引数としてプリミティブ値を受 より興味深いのは、BiConsumerインターフェイスです。 そのユースケースの一つは、マップのエントリを反復処理することです。

Map<String, Integer> ages = new HashMap<>();ages.put("John", 25);ages.put("Freddy", 24);ages.put("Samuel", 30);ages.forEach((name, age) -> System.out.println(name + " is " + age + " years old"));

別の特殊なBiConsumerバージョンのセットはObjDoubleConsumer、ObjIntConsumer、およびObjLongConsumerで構成され、二つの引数を受け取ります。

述語

数学的論理学では、述語は値を受け取り、ブール値を返す関数です。

述語関数インターフェイスは、一般化された値を受け取り、ブール値を返す関数の特殊化です。 述語lambdaの典型的な使用例は、値のコレクションをフィルタリングすることです。

List<String> names = Arrays.asList("Angela", "Aaron", "Bob", "Claire", "David");List<String> namesWithA = names.stream() .filter(name -> name.startsWith("A")) .collect(Collectors.toList());

上記のコードでは、Stream APIを使用してリストをフィ 述語の実装では、フィルター処理ロジックがカプセル化されます。

前のすべての例と同様に、プリミティブ値を受け取るこの関数のIntPredicate、DoublePredicate、およびLongPredicateバージョンがあります。

演算子

演算子インターフェイスは、同じ値の型を受け取って返す関数の特殊なケースです。 UnaryOperatorインターフェイスは、単一の引数を受け取ります。 Collections APIでの使用例の1つは、リスト内のすべての値を同じ型の計算値に置き換えることです。

List<String> names = Arrays.asList("bob", "josh", "megan");names.replaceAll(name -> name.toUpperCase());

リスト。replaceAll関数は、所定の位置にある値を置き換えるときにvoidを返します。 目的に合うように、リストの値を変換するために使用されるラムダは、受信したのと同じ結果型を返す必要があります。 これが、ここでUnaryOperatorが便利な理由です。もちろん、名前の代わりに->名前。toUpperCase()、我々は単にメソッド参照を使用することができます:

names.replaceAll(String::toUpperCase);

BinaryOperatorの最も興味深いユースケースの一つは、削減操作です。 すべての値の合計で整数のコレクションを集計したいとします。 Stream APIでは、コレクタを使用してこれを行うことができますが、より一般的な方法はreduceメソッドを使用することです。

List<Integer> values = Arrays.asList(3, 5, 8, 9, 12);int sum = values.stream() .reduce(0, (i1, i2) -> i1 + i2);

reduceメソッドは、初期アキュムレータ値とBinaryOperator関数を受け取ります。 この関数の引数は、同じ型の値のペアです; 関数自体には、同じ型の単一の値でそれらを結合するためのロジックも含まれています。 つまり、次の条件が保持される必要があります。

op.apply(a, op.apply(b, c)) == op.apply(op.apply(a, b), c)

BinaryOperator演算子関数の連想プロパティにより、削減プロセスを簡単に並列化できます。もちろん、unaryoperatorとBinaryOperatorの特殊化もあり、これはdoubleunaryoperator、IntUnaryOperator、LongUnaryOperator、DoubleBinaryOperator、IntBinaryOperator、およびLongBinaryOperatorのプリミティブ値で使用できます。

従来の機能インターフェイス

すべての機能インターフェイスがJava8に登場したわけではありません。 以前のバージョンのJavaの多くのインターフェイスはFunctionalInterfaceの制約に準拠しており、それらをラムダとして使用できます。 著名な例としては、並行性Apiで使用されるRunnableインターフェイスとCallableインターフェイスがあります。 Java8では、これらのインターフェイスにも@FunctionalInterface注釈が付いています。 これにより、同時実行コードを大幅に簡素化できます。

Thread thread = new Thread(() -> System.out.println("Hello From Another Thread"));thread.start();

結論

Related Posts

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です