メインコンテンツ
カスタム関数型インターフェイス
関数型プログラミング
共通関数型インターフェイス
教育目的
@FunctionalInterfaceアノテーションを使用
する機能パラメーターなしで戻り値のない機能インターフェース
をカスタマイズする機能パラメーターおよび戻り値なしで機能インターフェースをカスタマイズする機能
Lambda遅延実行の特性を理解する機能
メソッドパラメーター
としてLambdaを使用する機能メソッド戻り値としてLambda
を使用する機能サプライヤーを使用する機能機能インターフェース
消費者機能インターフェースを
使用できます機能機能インターフェースを
使用できます述語機能インターフェースを使用できます
第1章機能インターフェース
1.1コンセプト
Javaの機能インターフェースとは、1つだけの抽象メソッドを持つインターフェースを指します。
関数型インターフェイスは、関数型プログラミングのシナリオに適したインターフェイスです。Javaの関数型プログラミングはLambdaであるため、関数型インターフェイスは
Lambdaに適用できるインターフェイスです。インターフェースに抽象メソッドが1つしかないことを確認することによってのみ、JavaのLambdaをスムーズに推定できます。
备注:“语法糖”是指使用更加方便,但是原理不变的代码语法。例如在遍历集合时使用的for-each语法,其实
底层的实现原理仍然是迭代器,这便是“语法糖”。从应用层面来讲,Java中的Lambda可以被当做是匿名内部
类的“语法糖”,但是二者在原理上是不同的。
1.2フォーマット
インターフェイスに抽象メソッドが1つだけあることを確認してください。
修饰符^ interface^ 接口名称 {
public abstract 返回值类型 方法名称(可选参数信息);
// 其他非抽象方法内容
}
インターフェイスの抽象メソッドのパブリック抽象は省略できるので、関数型インターフェイスを定義するのは非常に簡単です。
1.3 @FunctionalInterfaceアノテーション
@Overrideアノテーションの機能と同様に、Java 8では、機能インターフェイス専用の新しいアノテーション@FunctionalInterfaceが導入されました。この注釈
は、インターフェースの定義で使用できます。
アノテーションを使用してインターフェースを定義すると、コンパイラーはインターフェースに実際に1つだけの抽象メソッドがあるかどうかを強制的にチェックします。そうでない場合、エラーが報告されます。なお
、このアノテーションを使用しなくても、機能インターフェースの定義を満たしていれば機能インターフェースであり、使い方は同じです。
1.4カスタム機能インターフェース
定義したばかりのMyFunctionalInterface関数インターフェースの場合、一般的な使用シナリオはメソッドパラメーターとして次のとおりです。
第2章関数型プログラミング
オブジェクト指向の特性を考慮した上で、Java言語は、ラムダ式とメソッド参照を通じて開発者に関数型プログラミングへの扉を開きます。
予備調査をしましょう。
2.1 Lambdaの実行の遅延
public interface MyFunctionalInterface {
void myMethod();
}
@FunctionalInterface
public interface MyFunctionalInterface {
void myMethod();
}
public class Demo09FunctionalInterface {
// 使用自定义的函数式接口作为方法参数
private static void doSomething(MyFunctionalInterface inter) {
inter.myMethod(); // 调用自定义的函数式接口方法
}
public static void main(String[] args) {
// 调用使用函数式接口的方法
doSomething(() ‐> System.out.println("Lambda执行啦!"));
}
}
一部のシナリオのコードが実行された後、結果が使用されない可能性があり、これによりパフォーマンスが低下します。ラムダ式は遅延して実行されるため、
パフォーマンスを向上させるソリューションとして使用できます。
無駄なパフォーマンスのログケース
注:このログは、プロジェクトの監視と最適化のためのプログラムの動作中に問題をすばやく見つけて状況を記録するのに役立ちます。
典型的なシナリオは、条件付きでパラメーターを使用することです。たとえば、ログメッセージをつなぎ合わせた後、条件が満たされている場合は出力します。
このコードには問題があります。レベルが要件を満たしているかどうかに関係なく、ログメソッドの2番目のパラメーターとして、3つの文字列をスプライスして
メソッドに最初に渡してから、レベルの判断を実行する必要があります。レベルが要件を満たしていない場合、ストリングのスプライシング操作は無駄に行われ、パフォーマンスの無駄になります。
备注:SLF4J是应用非常广泛的日志框架,它在记录日志时为了解决这种性能浪费的问题,并不推荐首先进行
字符串的拼接,而是将字符串的若干部分作为可变参数传入方法中,仅在日志级别满足要求的情况下才会进
行字符串拼接。例如:LOGGER.debug("变量{}的取值为{}。", "os", "macOS"),其中的大括号{}为占位
符。如果满足日志级别要求,则会将“os”和“macOS”两个字符串依次拼接到大括号的位置;否则不会进行字
符串拼接。这也是一种可行解决方案,但Lambda可以做到更好。
Lambdaの優れた記述を体験してください
Lambdaを使用するには、機能的なインターフェースが必要です。
次に、ログメソッドを変更します。
public class Demo01Logger {
private static void log(int level, String msg) {
if (level == 1 ) {
System.out.println(msg);
}
}
public static void main(String[] args) {
String msgA = "Hello";
String msgB = "World";
String msgC = "Java";
log( 1 , msgA + msgB + msgC);
}
}
@FunctionalInterface
public interface MessageBuilder {
String buildMessage();
}
public class Demo02LoggerLambda {
private static void log(int level, MessageBuilder builder) {
if (level == 1 ) {
System.out.println(builder.buildMessage());
}
}
このようにして、レベルが要件を満たしている場合にのみ、3つの文字列が接合されます。それ以外の場合、3つの文字列は接合されません。
ラムダ遅延の証明
次のコードは、結果によって確認できます。
結果から、レベル要件が満たされない場合、Lambdaは実行されないことがわかります。パフォーマンスを節約する効果を達成するように。
扩展:实际上使用内部类也可以达到同样的效果,只是将代码操作延迟到了另外一个对象当中通过调用方法
来完成。而是否调用其所在方法是在条件判断之后才执行的。
2.2パラメータと戻り値としてLambdaを使用する
実装原理がわからない場合、JavaのLambda式は匿名の内部クラスの代用と見なすことができます。メソッドのパラメーターが関数
型のインターフェイスの場合は、代わりにラムダ式を使用できます。Lambda式をメソッドパラメータとして使用することは、実際には関数
インターフェイスをメソッドパラメータとして使用することです。
たとえば、java.lang.Runnableインターフェースは機能的なインターフェースであり、このインターフェースをパラメーターとして使用するstartThreadメソッドがある場合、
Lambdaを使用してパラメーターを渡すことができます。この場合、ThreadクラスのRunnableパラメーターとの本質的な違いはありません。
public static void main(String[] args) {
String msgA = "Hello";
String msgB = "World";
String msgC = "Java";
log( 1 , () ‐> msgA + msgB + msgC );
}
}
public class Demo03LoggerDelay {
private static void log(int level, MessageBuilder builder) {
if (level == 1 ) {
System.out.println(builder.buildMessage());
}
}
public static void main(String[] args) {
String msgA = "Hello";
String msgB = "World";
String msgC = "Java";
log( 2 , () ‐> {
System.out.println("Lambda执行!");
return msgA + msgB + msgC;
});
}
}
同様に、メソッドの戻り値の型が関数型インターフェイスの場合、Lambda式を直接返すことができます。
java.util.Comparatorインターフェースタイプのオブジェクトをソーターとして取得するためにメソッドが必要な場合、このメソッドを呼び出して取得できます。
これは直接ラムダ式を返します。
第3章一般的に使用される機能インターフェース
JDKは、Lambdaの一般的な使用シナリオを充実させるために一般的に使用される多数の機能インターフェースを提供します。これらは主にjava.util.functionパッケージで提供されます。
以下は、最も単純なインターフェースと使用例です。
3.1サプライヤーインターフェース
java.util.function.Supplierインターフェースには、パラメーターなしの1つのメソッド、T get()のみが含まれています。ジェネリックパラメータで指定されたタイプの
オブジェクトデータを取得するために使用されます。これは関数型インターフェイスであるため、対応するLambda式は、ジェネリック型に準拠するオブジェクト
データを「外部に提供」する必要があることを意味します。
public class Demo04Runnable {
private static void startThread(Runnable task) {
new Thread(task).start();
}
public static void main(String[] args) {
startThread(() ‐> System.out.println("线程任务执行!"));
}
}
import java.util.Arrays;
import java.util.Comparator;
public class Demo06Comparator {
private static Comparator<String> newComparator() {
return (a, b) ‐> b.length() ‐ a.length();
}
public static void main(String[] args) {
String[] array = { "abc", "ab", "abcd" };
System.out.println(Arrays.toString(array));
Arrays.sort(array, newComparator());
System.out.println(Arrays.toString(array));
}
}
3.2演習:配列要素の最大値を見つける
トピック
メソッドのパラメータータイプとしてサプライヤーインターフェイスを使用し、ラムダ式を使用してint配列の最大値を検索します。注意:ジェネリックインターフェイスには
java.lang.Integerクラスを使用してください。
回答
3.3消費者インターフェース
import java.util.function.Supplier;
public class Demo08Supplier {
private static String getString(Supplier<String> function) {
return function.get();
}
public static void main(String[] args) {
String msgA = "Hello";
String msgB = "World";
System.out.println(getString(() ‐> msgA + msgB));
}
}
public class Demo02Test {
//定一个方法,方法的参数传递Supplier,泛型使用Integer
public static int getMax(Supplier<Integer> sup){
return sup.get();
}
public static void main(String[] args) {
int arr[] = { 2 , 3 , 4 , 52 , 333 , 23 };
//调用getMax方法,参数传递Lambda
int maxNum = getMax(()‐>{
//计算数组的最大值
int max = arr[ 0 ];
for(int i : arr){
if(i>max){
max = i;
}
}
return max;
});
System.out.println(maxNum);
}
}
java.util.function.Consumerインターフェースは、Supplierインターフェースの正反対です。データの一部を生成する代わりに、データの一部を消費します。
データ型は、ジェネリック型によって決定されます。
抽象メソッド:受け入れる
Consumer接口中包含抽象方法void accept(T t),意为消费一个指定泛型的数据。基本使用如:
もちろん、より良い方法は、メソッド参照を使用することです。
デフォルトの方法:andThen
メソッドのパラメーターと戻り値がすべてコンシューマー型である場合、効果を得ることができます。データを消費するときは、最初に
操作を実行してから、操作を実行して組み合わせを実現します。そして、このメソッドはデフォルトのメソッドであり、次にコンシューマーインターフェースのメソッドです。JDKのソースコードは次のとおりです。
备注:java.util.Objects的requireNonNull静态方法将会在参数为null时主动抛出
NullPointerException异常。这省去了重复编写if语句和抛出空指针异常的麻烦。
組み合わせを実現するには、2つ以上のLambda式が必要であり、andThenのセマンティクスはまさに「段階的な」操作です。たとえば、2つのステップの組み合わせ
:
import java.util.function.Consumer;
public class Demo09Consumer {
private static void consumeString(Consumer<String> function) {
function.accept("Hello");
}
public static void main(String[] args) {
consumeString(s ‐> System.out.println(s));
}
}
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) ‐> { accept(t); after.accept(t); };
}
import java.util.function.Consumer;
public class Demo10ConsumerAndThen {
private static void consumeString(Consumer<String> one, Consumer<String> two) {
one.andThen(two).accept("Hello");
}
public static void main(String[] args) {
consumeString(
s ‐> System.out.println(s.toUpperCase()),
s ‐> System.out.println(s.toLowerCase()));
}
}
操作の結果は、最初にHELLOを完全に大文字で出力し、次にhelloを完全に小文字で出力します。もちろん、チェーンライティングにより、さらに多くのステップを
組み合わせることができます。
3.4演習:印刷情報のフォーマット
トピック
次の文字列配列には複数の情報があります。「名前:XX。性別:XX。」の形式に従って情報を印刷してください。姓の印刷をリクエスト
名前のアクションは最初のConsumerインターフェースのLambdaインスタンスとして扱われ、性別を印刷するアクションは2番目のConsumerインターフェースのLambdaインスタンスとして扱われ
、2つのConsumerインターフェースは順番に「スプライス」されます。
回答
3.5述語インターフェース
ブール値の結果を得るために、特定のタイプのデータを判断する必要がある場合があります。現時点では、
java.util.function.Predicateインターフェースを使用できます。
抽象メソッド:テスト
Predicate接口中包含一个抽象方法:boolean test(T t)。用于条件判断的场景:
public static void main(String[] args) {
String[] array = { "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男" };
}
import java.util.function.Consumer;
public class DemoConsumer {
public static void main(String[] args) {
String[] array = { "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男" };
printInfo(s ‐> System.out.print("姓名:" + s.split(",")[ 0 ]),
s ‐> System.out.println("。性别:" + s.split(",")[ 1 ] + "。"),
array);
}
private static void printInfo(Consumer<String> one, Consumer<String> two, String[] array) {
for (String info : array) {
one.andThen(two).accept(info); // 姓名:迪丽热巴。性别:女。
}
}
}
条件付き判断の基準は、ラムダ式の入力のロジックです。文字列の長さが5を超えている限り、非常に長いと見なされます。
デフォルトの方法:および
これは条件付きの判断であるため、or、or、notの3つの一般的な論理関係があります。2つのPredicate条件を「and」ロジックで接続して「and」
の効果を達成する場合、デフォルトのメソッドandを使用できます。JDKソースコードは次のとおりです。
文字列に大文字の「H」と大文字の「W」の両方が含まれていると判断する場合は、次のようにします。
デフォルトの方法:または
andの「and」と同様に、デフォルトのメソッドは、論理関係で「or」を実装します。JDKソースコードは次のとおりです。
import java.util.function.Predicate;
public class Demo15PredicateTest {
private static void method(Predicate<String> predicate) {
boolean veryLong = predicate.test("HelloWorld");
System.out.println("字符串很长吗:" + veryLong);
}
public static void main(String[] args) {
method(s ‐> s.length() > 5 );
}
}
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) ‐> test(t) && other.test(t);
}
import java.util.function.Predicate;
public class Demo16PredicateAnd {
private static void method(Predicate<String> one, Predicate<String> two) {
boolean isValid = one.and(two).test("Helloworld");
System.out.println("字符串符合要求吗:" + isValid);
}
public static void main(String[] args) {
method(s ‐> s.contains("H"), s ‐> s.contains("W"));
}
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) ‐> test(t) || other.test(t);
}
「文字列に大文字のHまたは大文字のWが含まれる」というロジックを実装する場合、コードは名前「and」を「or」に変更するだけでよく、それ以外
はすべて同じままです。
デフォルトの方法:否定
「および」および「または」はすでに理解されており、残りの「ない」(否定)は単純です。デフォルトのメソッドnegateのJDKソースコードは次のとおりです。
テストメソッドが実行された後、結果のブール値が「!」で逆になっていることが実装からわかります。
andメソッドやorメソッドと同様に、テストメソッドを呼び出す前にnegateメソッドを呼び出す必要があります。
3.6演習:コレクション情報のフィルタリング
トピック
配列には、次のように複数の「名前+性別」情報があります。Predicateインターフェースのアセンブリを使用して、要件を満たす文字列をコレクション
ArrayListにフィルターしてください。2つの条件を同時に満たす必要があります。
1. 必须为女生;
2. 姓名为 4 个字。
import java.util.function.Predicate;
public class Demo16PredicateAnd {
private static void method(Predicate<String> one, Predicate<String> two) {
boolean isValid = one.or(two).test("Helloworld");
System.out.println("字符串符合要求吗:" + isValid);
}
public static void main(String[] args) {
method(s ‐> s.contains("H"), s ‐> s.contains("W"));
}
}
default Predicate<T> negate() {
return (t) ‐> !test(t);
}
import java.util.function.Predicate;
public class Demo17PredicateNegate {
private static void method(Predicate<String> predicate) {
boolean veryLong = predicate.negate().test("HelloWorld");
System.out.println("字符串很长吗:" + veryLong);
}
public static void main(String[] args) {
method(s ‐> s.length() < 5 );
}
}
回答
3.7関数インターフェース
java.util.function.Function <T、R>インターフェースは、あるタイプのデータを別のタイプのデータに従って取得するために使用されます。前者は前提条件と
呼ばれ、後者は事後条件と呼ばれます。
抽象メソッド:適用
Function接口中最主要的抽象方法为:R apply(T t),根据类型T的参数获取类型R的结果。
使用されるシナリオは、たとえば、文字列型を整数型に変換することです。
public class DemoPredicate {
public static void main(String[] args) {
String[] array = { "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男", "赵丽颖,女" };
}
}
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
public class DemoPredicate {
public static void main(String[] args) {
String[] array = { "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男", "赵丽颖,女" };
List<String> list = filter(array,
s ‐> "女".equals(s.split(",")[ 1 ]),
s ‐> s.split(",")[ 0 ].length() == 4 );
System.out.println(list);
}
private static List<String> filter(String[] array, Predicate<String> one,
Predicate<String> two) {
List<String> list = new ArrayList<>();
for (String info : array) {
if (one.and(two).test(info)) {
list.add(info);
}
}
return list;
}
}
もちろん、メソッド参照を使用するのが最善です。
デフォルトの方法:andThen
Function接口中有一个默认的andThen方法,用来进行组合操作。JDK源代码如:
このメソッドは、「最初に何をして、次に何をする」シナリオでも使用されます。
最初の演算は文字列をint数に解析することであり、2番目の演算は10を乗算することです。2つの操作はandThenによって順番に組み合わされます
。
请注意,Function的前置条件泛型和后置条件泛型可以相同。
3.8演習:カスタム関数モデルのステッチ
トピック
関数を使用して関数モデルを結合してください。順番に実行する必要がある複数の関数操作は次のとおりです:
String str = "Zhao Liying、20";
import java.util.function.Function;
public class Demo11FunctionApply {
private static void method(Function<String, Integer> function) {
int num = function.apply("10");
System.out.println(num + 20 );
}
public static void main(String[] args) {
method(s ‐> Integer.parseInt(s));
}
}
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) ‐> after.apply(apply(t));
}
import java.util.function.Function;
public class Demo12FunctionAndThen {
private static void method(Function<String, Integer> one, Function<Integer, Integer> two) {
int num = one.andThen(two).apply("10");
System.out.println(num + 20 );
}
public static void main(String[] args) {
method(str‐>Integer.parseInt(str)+ 10 , i ‐> i *= 10 );
}
}
1.文字列の年齢の数値部分をインターセプトして文字列を取得します。
2. 将上一步的字符串转换成为int类型的数字;
3. 将上一步的int数字累加 100 ,得到结果int数字。
回答
import^ java.util.function.Function;
public class DemoFunction {
public static void main(String[] args) {
String str = "赵丽颖,20";
int age = getAgeNum(str, s ‐> s.split(",")[ 1 ],
s ‐>Integer.parseInt(s),
n ‐> n += 100 );
System.out.println(age);
}
private static int getAgeNum(String str, Function<String, String> one,
Function<String, Integer> two,
Function<Integer, Integer> three) {
return one.andThen(two).andThen(three).apply(str);
}
}