深入了解Java 8 新特性:lambda表達式進階
前言
Java 8中的Lambda表達式是一種匿名函數,它允許你將函數作為方法參數進行傳遞,或者把代碼更簡潔地定義在你的應用程序里。另外Java的函數式編程就是Lambda表達式,java的函數式接口的有一個明顯特征:有且僅有一個抽象方法的接口。下面是一些常見的Java內置的函數式接口梳理,掌握這些內置的函數式接口是相當有必要的,因為作為一種更簡潔、更靈活和更易于維護的編程方式,這在很多的java相關的框架技術中有大量的應用,相信有喜歡鉆研源碼小伙伴應該深有體會。
Java內置的函數式接口
Function
Function接口是Java 8中引入的一個函數式接口,它位于java.util.function包中。這個接口表示一個輸入參數能夠產生一個結果的函數。Function接口只有一個抽象方法,即apply(T t),它接受一個參數并返回一個結果。
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
static <T> Function<T, T> identity() {
return t -> t;
}
}
下面是一個使用Function接口的簡單示例:
@Test
public void test5() {
Function<String, Student> function = str -> new Student(str);
Student student = function.apply("張三");
log.info(student.getName());
}
在Java中,Function接口的默認實現包括以下幾種:
- Function. andThen(Function after):這個方法返回一個新函數,它首先應用原始函數,然后應用另一個函數。也就是說,這個新函數首先將輸入映射到某個結果,然后將結果映射到另一個結果。假設我們有一個Function<Integer,Integer>,它接受一個整數作為輸入,并返回一個整數作為輸出。現在,我們想在這個函數的基礎上添加一個新的函數,將得到的整數加倍。
@Test
public void test7() {
Function<Integer, Integer> originalFunction = x -> x * 2;
Function<Integer, Integer> newFunction = x -> x * 2;
Function<Integer, Integer> composedFunction = originalFunction.andThen(newFunction);
int input = 5;
int result = composedFunction.apply(input);
System.out.println(result); // 輸出:20
}
在上面的示例中,我們定義了一個原始函數originalFunction,它接受一個整數作為輸入,并將其乘以2。然后,我們定義了一個新的函數newFunction,它也接受一個整數作為輸入,并將其乘以2。接下來,我們使用andThen方法將這兩個函數組合成一個新的函數composedFunction。最后,我們將輸入整數5傳遞給composedFunction,并打印結果。在這個例子中,首先將輸入整數5乘以2得到10,然后將10乘以2得到20,最終輸出結果為20。
- Function. compose(Function before):這個方法返回一個新函數,它首先應用另一個函數,然后將結果映射到原始函數的輸入。也就是說,這個新函數首先將輸入映射到另一個輸入,然后將這個輸入映射到結果。假設我們有兩個函數,一個將字符串轉換為大寫,另一個將字符串長度截斷為5個字符。這兩個函數可以組合成一個新函數,首先將字符串轉換為大寫,然后將其長度截斷為5個字符。
@Test
public void test6(){
Function<String, String> toUpperCase = str -> str.toUpperCase();
Function<String, String> truncate = str -> str.substring(0, 6);
Function<String, String> composedFunction = toUpperCase.compose(truncate);
String input = "hello world!";
String result = composedFunction.apply(input);
System.out.println(result); // 輸出:"HEL"
}
在上面的示例中,我們首先定義了兩個函數:toUpperCase和truncate。然后,我們使用compose方法將它們組合成一個新函數composedFunction。最后,我們調用composedFunction對輸入字符串進行操作,并打印結果。
BiFunction
BiFunction接口是Java 8中引入的一個函數式接口,它位于java.util.function包中。BiFunction接口表示一個接受兩個輸入參數并返回一個結果的函數。它與Function接口類似,但多了一個輸入參數。
BiFunction接口的定義如下:
@FunctionalInterface
public interface BiFunction<T, U, R> {
R apply(T t, U u);
default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t, U u) -> after.apply(apply(t, u));
}
}
其中,T和U是輸入參數的類型,R是返回結果的類型。apply()方法是抽象的,需要具體的實現來提供具體的邏輯。
下面是一個使用BiFunction接口的示例:
@Test
public void test8() {
BiFunction<Integer, Integer, Integer> add = (x, y) -> x + y;
int result = add.apply(2, 3);
System.out.println(result); // 輸出:5
}
在上面的示例中,我們定義了一個add函數,它接受兩個整數作為輸入,并將它們相加得到一個整數作為結果。我們使用apply()方法調用該函數并傳遞兩個參數2和3,然后打印結果5。
BiFunction接口包含一個默認實現,即andThen方法,用于將兩個函數組合在一起。它接受一個BiFunction作為參數,并返回一個新的函數,該函數將先應用原始函數,然后應用給定的函數。
@Test
public void test9() {
BiFunction<Integer, Integer, String> addAndToString = (x, y) -> Integer.toString(x + y);
Function<String, String> toUpperCase = str -> str.toUpperCase();
BiFunction<Integer, Integer, String> composedFunction = addAndToString.andThen(toUpperCase);
int input1 = 2;
int input2 = 3;
String result = composedFunction.apply(input1, input2);
System.out.println(result); // 輸出:"5"
}
在上面的示例中,我們定義了一個addAndToString函數,它接受兩個整數作為輸入,并將它們的和轉換為字符串。然后,我們定義了一個toUpperCase函數,它接受一個字符串作為輸入,并將其轉換為大寫。接下來,我們使用andThen方法將兩個函數組合成一個新的函數composedFunction。最后,我們調用composedFunction對輸入整數2和3進行操作,并打印結果"5"。首先將輸入整數2和3相加得到5,然后將5轉換為字符串"5",最后將字符串"5"轉換為大寫"5",最終輸出結果為"5"。
Predicate
Predicate 是 Java 中的一個函數式接口,它位于 java.util.function 包中。這個接口用于表示一個參數的謂詞(即,條件判斷),接收一個參數并返回一個布爾值,通常用于判斷參數是否滿足指定的條件
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate<T> negate() {
return (t) -> !test(t);
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
@SuppressWarnings("unchecked")
static <T> Predicate<T> not(Predicate<? super T> target) {
Objects.requireNonNull(target);
return (Predicate<T>)target.negate();
}
}
Predicate接口的主要方法是 test(),它接收一個參數并返回一個布爾值。下面是一個簡單的例子:
@Test
public void test3(){
Student student = new Student("張三");
Predicate<Student> predicate= obj -> "張三".equals(obj.getName());
boolean flag = predicate.test(student);
Assert.isTrue(flag,"test fail");
boolean flag2 = predicate.test(new Student("李四"));
Assert.isTrue(flag2,"test fail");
}
Predicate:常用的四個方法:
- boolean test(T t):對給定的參數進行判斷(判斷邏輯由Lambda表達式實現),返回一個布爾值。
- default Predicate negate():返回一個邏輯的否定,對應邏輯非。
- default Predicate and(Predicate other):返回一個組合判斷,對應短路與。
- default Predicate or(Predicate other):返回一個組合判斷,對應短路或。
@Test
public void test4(){
Student student = new Student("張三");
Predicate<Student> predicate= obj -> "張三".equals(obj.getName());
Predicate<Student> predicate2= obj-> "李四".equals(obj.getName());
Predicate<Student> or = predicate2.or(predicate);
boolean flag = or.test(student);
Assert.isTrue(flag,"test fail");
student.setName("李四");
boolean flag2 = or.test(student);
Assert.isTrue(flag2,"test fail");
Predicate<Student> predicate3=obj->18==obj.getAge();
student.setAge(18);
Predicate<Student> and = predicate3.and(predicate2);
boolean flag3 = and.test(student);
Assert.isTrue(flag3,"test fail");
student.setName("鐵蛋");
boolean test = and.test(student);
Assert.isTrue(test,"學生姓名不等于張三或者李四");
}
Consumer
Consumer接口代表了一個接受輸入參數并返回void的函數。它的主要作用是消費輸入數據,也就是說,對輸入進行某種操作,但不返回任何結果。
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
Consumer接口的主要方法是void accept(T t):對給定的參數執行此操作。
@Test
public void test2() {
Student student = new Student("zhangsan");
Consumer<String> nameConsumer = str -> student.setName(str);
nameConsumer.accept("lisi");//設置學生的姓名為拼音-小寫
log.info(student.getName());
Assert.isTrue("lisi".equals(student.getName()), "test fail!");
}
Consumer接口的默認實現,andThen方法,用于將兩個Consumer合并在一起。它允許你將一個操作(即第二個Consumer)附加到另一個操作(即第一個Consumer)的后面,以便在原始操作完成之后執行附加操作。
下面是一個簡單的示例,演示如何使用andThen方法:
@Test
public void test2() {
Student student = new Student("zhangsan");
Consumer<String> nameConsumer = str -> student.setName(str);
Consumer<String> nameConsumer2 = name -> student.setName(name.toUpperCase());
Consumer<String> nameConsumer3 = nameConsumer.andThen(nameConsumer2);//設置學生的姓名為拼音-大寫
nameConsumer3.accept("lisi");
log.info(student.getName());
Assert.isTrue("LISI".equals(student.getName()), "test fail!");
}
Supplier
這個接口表示一個不接受任何參數但返回結果的函數
@FunctionalInterface public interface Supplier<T> { T get(); }
特點:
- 該方法不需要參數,它會按照某種實現邏輯(由Lambda表達式實現)返回一個數據。
- Supplier接口也稱為生產型接口,如果我們指定了接口的泛型是什么類型,那么接口中的get方法就會生產什么類型的數據供我們使用。
使用示例:
private String getStuName(Supplier<Student> supplier) {
String name = supplier.get().getName();
return name;
}
@Test
public void test() {
String name = this.getStuName(() -> new Student("zhangsan"));
log.info(name);
Assert.isTrue(name.equals("zhangsan"), "test fail");
}