JavaRush /Java Blog /Random-JA /Java の関数インターフェイス

Java の関数インターフェイス

Random-JA グループに公開済み
こんにちは! Java Syntax Pro のクエストでは、ラムダ式について学び、ラムダ式は関数型インターフェイスからの関数型メソッドの実装にすぎないと言いました。言い換えれば、これは何らかの匿名 (未知の) クラス、その未実現のメソッドの実装です。そして、コースの講義でラムダ式を使った操作を詳しく調べた場合、今度はいわば反対側、つまりこれらのインターフェイスそのものについて考えてみましょう。Java の 8 番目のバージョンでは、関数インターフェイスJava の関数インターフェイス - 1の概念が導入されました。これは何ですか?1 つの未実装 (抽象) メソッドを含むインターフェイスは、機能しているとみなされます。たとえば、前述のインターフェイスなど、多くのすぐに使用できるインターフェイスがこの定義に該当します。また、次のような自分たちで作成したインターフェイスもあります。 Comparator
@FunctionalInterface
public interface Converter<T, N> {
   N convert(T t);
}
あるタイプのオブジェクトを別のタイプのオブジェクトに変換することをタスクとするインターフェース (一種のアダプター) があります。アノテーションの@FunctionalInterface目的は、このインターフェイスが機能し、メソッドを 1 つだけ含めるべきであることをコンパイラーに伝えることであるため、アノテーションはそれほど複雑でも重要なものでもありません。このアノテーションを持つインターフェースに複数の未実装 (抽象) メソッドがある場合、コンパイラーはこのインターフェースを誤ったコードとして認識するため、このインターフェースをスキップしません。このアノテーションのないインターフェイスは機能しているとみなされ、動作しますが、 @FunctionalInterfaceこれは追加の保険にすぎません。クラスに戻りましょうComparatorそのコード (またはドキュメント)を見ると、複数のメソッドがあることがわかります。それでは、どうやってそれを機能的なインターフェースとみなすことができるのでしょうか? 抽象インターフェイスには、単一メソッドのスコープ内にないメソッドを含めることができます。
  • 静的
インターフェイスの概念は、特定のコード単位にはメソッドを実装できないことを意味します。しかし、Java 8 以降、インターフェイスで静的メソッドとデフォルト メソッドを使用できるようになりました。 静的メソッドはクラスに直接バインドされており、そのようなメソッドを呼び出すためにそのクラスの特定のオブジェクトを必要としません。つまり、これらのメソッドはインターフェイスの概念に調和して適合します。例として、オブジェクトの null をチェックする静的メソッドを前のクラスに追加してみましょう。
@FunctionalInterface
public interface Converter<T, N> {

   N convert(T t);

   static <T> boolean isNotNull(T t){
       return t != null;
   }
}
このメソッドを受け取ったコンパイラーは何も文句を言わなかったので、インターフェースはまだ機能していることを意味します。
  • デフォルトのメソッド
Java 8 より前は、他のクラスによって継承されるインターフェース内にメソッドを作成する必要がある場合、特定のクラスごとに実装される抽象メソッドしか作成できませんでした。しかし、このメソッドがすべてのクラスで同じだったらどうなるでしょうか? この場合、抽象クラスが最もよく使用されました。ただし、Java 8 以降では、実装されたメソッド (デフォルトのメソッド) とのインターフェイスを使用するオプションがあります。インターフェイスを継承する場合、これらのメソッドをオーバーライドすることも、すべてをそのままにすることもできます (デフォルトのロジックのままにする)。デフォルトのメソッドを作成するときは、キーワード - を追加する必要がありますdefault
@FunctionalInterface
public interface Converter<T, N> {

   N convert(T t);

   static <T> boolean isNotNull(T t){
       return t != null;
   }

   default void writeToConsole(T t) {
       System.out.println("Текущий an object - " + t.toString());
   }
}
繰り返しますが、コンパイラが不平を言い始めたわけではなく、関数インターフェイスの制限を超えていないことがわかります。
  • オブジェクトクラスのメソッド
「オブジェクトの比較」の講義では、すべてのクラスがそのクラスを継承するという事実について話しましたObject。これはインターフェースには適用されません。ただし、クラスのメソッドのシグネチャと一致する抽象メソッドがインターフェイスにある場合Object、そのようなメソッド (または複数のメソッド) は関数インターフェイスの制限を破ることはありません。
@FunctionalInterface
public interface Converter<T, N> {

   N convert(T t);

   static <T> boolean isNotNull(T t){
       return t != null;
   }

   default void writeToConsole(T t) {
       System.out.println("Текущий an object - " + t.toString());
   }

   boolean equals(Object obj);
}
繰り返しになりますが、コンパイラは文句を言わないので、インターフェイスConverterは依然として機能していると見なされます。ここで疑問なのは、なぜ関数型インターフェイスの未実装メソッドを 1 つに限定する必要があるのか​​ということです。そして、ラムダを使用して実装できるようにします。これを例で見てみましょうConverter。これを行うには、クラスを作成しましょうDog
public class Dog {
  String name;
  int age;
  int weight;

  public Dog(final String name, final int age, final int weight) {
     this.name = name;
     this.age = age;
     this.weight = weight;
  }
}
そして同様のものRaccoon(アライグマ):
public class Raccoon {
  String name;
  int age;
  int weight;

  public Raccoon(final String name, final int age, final int weight) {
     this.name = name;
     this.age = age;
     this.weight = weight;
  }
}
オブジェクトがありDog、そのフィールドに基づいてオブジェクトを作成する必要があるとしますRaccoon。つまり、Converterあるタイプのオブジェクトを別のタイプに変換します。それは次のようになります:
public static void main(String[] args) {
  Dog dog = new Dog("Bobbie", 5, 3);

  Converter<Dog, Raccoon> converter = x -> new Raccoon(x.name, x.age, x.weight);

  Raccoon raccoon = converter.convert(dog);

  System.out.println("Raccoon has parameters: name - " + raccoon.name + ", age - " + raccoon.age + ", weight - " + raccoon.weight);
}
これを実行すると、コンソールに次の出力が表示されます。

Raccoon has parameters: name - Bobbbie, age - 5, weight - 3
これは、私たちの方法が正しく機能したことを意味します。Java の関数インターフェイス - 2

基本的な Java 8 関数インターフェイス

それでは、Java 8 によってもたらされ、Stream API と組み合わせて積極的に使用されているいくつかの関数インターフェイスを見てみましょう。

述語

Predicate— 特定の条件が満たされているかどうかを確認するための機能インターフェイス。条件が満たされた場合は を返し、trueそれ以外の場合は -を返しますfalse
@FunctionalInterface
public interface Predicate<T> {
   boolean test(T t);
}
例として、Predicatetype の数値のパリティをチェックする を作成することを検討してくださいInteger
public static void main(String[] args) {
   Predicate<Integer> isEvenNumber = x -> x % 2==0;

   System.out.println(isEvenNumber.test(4));
   System.out.println(isEvenNumber.test(3));
}
コンソール出力:

true
false

消費者

Consumer(英語から - 「consumer」) - 型 T のオブジェクトを入力引数として受け取り、いくつかのアクションを実行しますが、何も返しません。
@FunctionalInterface
public interface Consumer<T> {
   void accept(T t);
}
例として、 を考えてみましょう。このタスクのタスクは、渡された文字列引数を使用してコンソールに挨拶を出力することです。 Consumer
public static void main(String[] args) {
   Consumer<String> greetings = x -> System.out.println("Hello " + x + " !!!");
   greetings.accept("Elena");
}
コンソール出力:

Hello Elena !!!

サプライヤー

Supplier(英語から - プロバイダー) - 引数をとらず、型 T のオブジェクトを返す関数インターフェイス:
@FunctionalInterface
public interface Supplier<T> {
   T get();
}
例として、Supplierリストからランダムな名前を生成する を考えてみましょう。
public static void main(String[] args) {
   ArrayList<String> nameList = new ArrayList<>();
   nameList .add("Elena");
   nameList .add("John");
   nameList .add("Alex");
   nameList .add("Jim");
   nameList .add("Sara");

   Supplier<String> randomName = () -> {
       int value = (int)(Math.random() * nameList.size());
       return nameList.get(value);
   };

   System.out.println(randomName.get());
}
これを実行すると、コンソールに名前のリストからランダムな結果が表示されます。

関数

Function— この関数インターフェイスは引数 T を受け取り、それを型 R のオブジェクトにキャストし、結果として返されます。
@FunctionalInterface
public interface Function<T, R> {
   R apply(T t);
}
例として、数値を文字列形式 ( ) から数値形式 ( ) に変換する を考えてみましょう。 FunctionStringInteger
public static void main(String[] args) {
   Function<String, Integer> valueConverter = x -> Integer.valueOf(x);
   System.out.println(valueConverter.apply("678"));
}
これを実行すると、コンソールに次の出力が表示されます。

678
PS: 数値だけでなく他の文字も文字列に渡すと、例外がスローされますNumberFormatException

単項演算子

UnaryOperator— 型 T のオブジェクトをパラメータとして受け取り、それに対していくつかの操作を実行し、同じ型 T のオブジェクトの形式で操作の結果を返す関数インターフェイス。
@FunctionalInterface
public interface UnaryOperator<T> {
   T apply(T t);
}
UnaryOperator、そのメソッドを使用してapply数値を 2 乗します。
public static void main(String[] args) {
   UnaryOperator<Integer> squareValue = x -> x * x;
   System.out.println(squareValue.apply(9));
}
コンソール出力:

81
5 つの機能インターフェイスを検討しました。Java 8 以降で利用できるのはこれだけではありません。これらが主なインターフェイスです。残りの利用可能なものは、それらの複雑な類似物です。完全なリストは、Oracle の公式ドキュメントに記載されています。

ストリームの機能インターフェイス

上で説明したように、これらの機能インターフェイスは Stream API と密接に結合されています。どうやって?Java の関数インターフェイス - 3そして、多くのメソッドがStreamこれらの関数インターフェイスで特に機能するようになります。メソッド内で関数型インターフェイスをどのように使用できるかを見てみましょうStream

述語付きメソッド

たとえば、クラス メソッドを考えてみましょうStreamfilterこのメソッドは引数として受け取りPredicateStream条件を満たす要素のみを返しますPredicate。-aのコンテキストでは、これは、インターフェイスメソッドで使用されたときにStream返される要素のみを通過することを意味します。これは、 の例では次のようになりますが、 の要素のフィルターの場合は次のようになります。 truetestPredicatePredicateStream
public static void main(String[] args) {
   List<Integer> evenNumbers = Stream.of(1, 2, 3, 4, 5, 6, 7, 8)
           .filter(x -> x % 2==0)
           .collect(Collectors.toList());
}
その結果、リストはevenNumbers要素 {2, 4, 6, 8} で構成されます。そして、覚えているように、collectすべての要素を特定のコレクションに収集します。List

消費者とのメソッド

Stream関数インターフェイスを使用する のメソッドの 1 つConsumerが ですpeekConsumerinの例は次のようになりますStream
public static void main(String[] args) {
   List<String> peopleGreetings = Stream.of("Elena", "John", "Alex", "Jim", "Sara")
           .peek(x -> System.out.println("Hello " + x + " !!!"))
           .collect(Collectors.toList());
}
コンソール出力:

Hello Elena !!!
Hello John !!!
Hello Alex !!!
Hello Jim !!!
Hello Sara !!!
ただし、このメソッドはpeekで動作するためConsumer、 の文字列の変更はStream行われませんが、元の要素peekを返しますStream。元の要素は、元の要素と同じになります。したがって、リストはpeopleGreetings「エレナ」、「ジョン」、「アレックス」、「ジム」、「サラ」の要素で構成されます。foreach次のような一般的に使用される方法もありますpeekが、最終的な点が異なります。

サプライヤーとの方法

Stream関数型インターフェイスを使用するメソッドの例はSuppliergenerate渡された関数型インターフェイスに基づいて無限シーケンスを生成する です。Supplierこの例を使用して、5 つのランダムな名前をコンソールに出力してみ ましょう。
public static void main(String[] args) {
   ArrayList<String> nameList = new ArrayList<>();
   nameList.add("Elena");
   nameList.add("John");
   nameList.add("Alex");
   nameList.add("Jim");
   nameList.add("Sara");

   Stream.generate(() -> {
       int value = (int) (Math.random() * nameList.size());
       return nameList.get(value);
   }).limit(5).forEach(System.out::println);
}
そして、これがコンソールに表示される出力です。

John
Elena
Elena
Elena
Jim
ここでは、メソッドを使用してlimit(5)メソッドに制限を設定しましたgenerate。そうしないと、プログラムはランダムな名前をコンソールに無期限に出力します。

関数付きメソッド

Stream引数を持つメソッドの典型的な例は、あるタイプの要素を取得し、それに対して何かを行って渡すFunctionメソッドですが、これらはすでに別のタイプの要素である可能性があります。inmapを使用した例は次のようになります。 FunctionStream
public static void main(String[] args) {
   List<Integer> values = Stream.of("32", "43", "74", "54", "3")
           .map(x -> Integer.valueOf(x)).collect(Collectors.toList());
}
その結果、数値のリストが得られますがInteger

UnaryOperator を使用したメソッド

UnaryOperator引数として使用するメソッドとして、クラスメソッドStream-を取り上げますiterate。このメソッドは メソッド に似ていますgenerate。これも無限シーケンスを生成しますが、引数が 2 つあります。
  • 1 つ目はシーケンスの生成が開始される要素です。
  • 2 つ目は でUnaryOperator、最初の要素から新しい要素を生成する原理を示します。
この例は次のようになりますUnaryOperatorが、メソッドは次のとおりですiterate
public static void main(String[] args) {
   Stream.iterate(9, x -> x * x)
           .limit(4)
           .forEach(System.out::println);
}
これを実行すると、コンソールに次の出力が表示されます。

9
81
6561
43046721
つまり、各要素がそれ自体で乗算され、最初の 4 つの数値についても同様に乗算されます。Java の関数インターフェース - 4それだけです!この記事を読んで、Java の Stream API を理解して習得することに一歩近づいたら幸いです。
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION