こんにちは! Java Syntax Pro のクエストでは、ラムダ式について学び、ラムダ式は関数型インターフェイスからの関数型メソッドの実装にすぎないと言いました。言い換えれば、これは何らかの匿名 (未知の) クラス、その未実現のメソッドの実装です。そして、コースの講義でラムダ式を使った操作を詳しく調べた場合、今度はいわば反対側、つまりこれらのインターフェイスそのものについて考えてみましょう。Java の 8 番目のバージョンでは、関数インターフェイスの概念が導入されました。これは何ですか?1 つの未実装 (抽象) メソッドを含むインターフェイスは、機能しているとみなされます。たとえば、前述のインターフェイスなど、多くのすぐに使用できるインターフェイスがこの定義に該当します。また、次のような自分たちで作成したインターフェイスもあります。 述語
消費者
サプライヤー
関数
単項演算子
Comparator
@FunctionalInterface
public interface Converter<T, N> {
N convert(T t);
}
あるタイプのオブジェクトを別のタイプのオブジェクトに変換することをタスクとするインターフェース (一種のアダプター) があります。アノテーションの@FunctionalInterface
目的は、このインターフェイスが機能し、メソッドを 1 つだけ含めるべきであることをコンパイラーに伝えることであるため、アノテーションはそれほど複雑でも重要なものでもありません。このアノテーションを持つインターフェースに複数の未実装 (抽象) メソッドがある場合、コンパイラーはこのインターフェースを誤ったコードとして認識するため、このインターフェースをスキップしません。このアノテーションのないインターフェイスは機能しているとみなされ、動作しますが、 @FunctionalInterface
これは追加の保険にすぎません。クラスに戻りましょうComparator
。そのコード (またはドキュメント)を見ると、複数のメソッドがあることがわかります。それでは、どうやってそれを機能的なインターフェースとみなすことができるのでしょうか? 抽象インターフェイスには、単一メソッドのスコープ内にないメソッドを含めることができます。
- 静的
@FunctionalInterface
public interface Converter<T, N> {
N convert(T t);
static <T> boolean isNotNull(T t){
return t != null;
}
}
このメソッドを受け取ったコンパイラーは何も文句を言わなかったので、インターフェースはまだ機能していることを意味します。
- デフォルトのメソッド
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 8 関数インターフェイス
それでは、Java 8 によってもたらされ、Stream API と組み合わせて積極的に使用されているいくつかの関数インターフェイスを見てみましょう。述語
Predicate
— 特定の条件が満たされているかどうかを確認するための機能インターフェイス。条件が満たされた場合は を返し、true
それ以外の場合は -を返しますfalse
。
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
例として、Predicate
type の数値のパリティをチェックする を作成することを検討してください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);
}
例として、数値を文字列形式 ( ) から数値形式 ( ) に変換する を考えてみましょう。 Function
String
Integer
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 と密接に結合されています。どうやって?そして、多くのメソッドがStream
これらの関数インターフェイスで特に機能するようになります。メソッド内で関数型インターフェイスをどのように使用できるかを見てみましょうStream
。
述語付きメソッド
たとえば、クラス メソッドを考えてみましょうStream
。filter
このメソッドは引数として受け取りPredicate
、Stream
条件を満たす要素のみを返しますPredicate
。-aのコンテキストでは、これは、インターフェイスメソッドで使用されたときにStream
返される要素のみを通過することを意味します。これは、 の例では次のようになりますが、 の要素のフィルターの場合は次のようになります。 true
test
Predicate
Predicate
Stream
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
が ですpeek
。Consumer
inの例は次のようになります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
関数型インターフェイスを使用するメソッドの例はSupplier
、generate
渡された関数型インターフェイスに基づいて無限シーケンスを生成する です。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
を使用した例は次のようになります。 Function
Stream
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 の Stream API を理解して習得することに一歩近づいたら幸いです。
GO TO FULL VERSION