JDK8新特性之Lambda表達式(一)
函數式接口
在了解Lambda表達式之前我們必須了解一下函數式接口,Lambda表達式實際上是依賴于函數式接口的。
定義
如果一個接口中,只聲明了一個抽象方法,那么這個接口就成為函數式接口。
我們一般使用@FunctionalInterface來標注,這個注解的作用是檢驗一個接口是否是函數式接口,這個注解是非必須的,換句話說如果一個不加這個注解但是只有一個抽象方法那么這個接口仍然是函數式接口。但是但最好在接口上使用注解@FunctionalInterface進行聲明,以免團隊的其他人員錯誤地往接口中添加新的方法。
需要注意的是,在JDK8后接口中允許有方法實現,像默認方法、靜態(tài)方法這些都不屬于抽象方法。還有就是重寫 Object 類里的方法不會導致函數式接口失效,如果一個接口聲明了抽象方法,但該抽象方法重寫了 Object 類里的一個公有方法,那么對于 Java 編譯器來說,它并不會認為該方法符合函數式接口的抽象方法(即不把該方法當作函數式接口的抽象方法)。因為接口的實現類都會直接或間接繼承 Object 這個根類,所以在函數式接口中定義與 Object 類中簽名一樣的方法是不會導致函數式接口失效的。
代碼示例
下面通過幾個例子來說明:
只有一個抽象方法的函數式接口:
/**
* @Author winston
* @Date 2021/9/11
*/
// 聲明函數式接口的注解
@FunctionalInterface
public interface FunctionInterfaceExample {
/**
* 抽象方法
*/
public void demo01();
/**
* 接口中的靜態(tài)方法,不屬于抽象方法
*/
public static void staticMethod(){
System.out.println("靜態(tài)方法");
}
/**
* 接口中的默認方法,不屬于抽象方法
*/
default void defaultMethod(){
System.out.println("默認方法");
}
}
當我們在上面定義的函數式接口中再增加一個抽象方法時,如果加了@FunctionalInterface注解的話,實際上編譯是會報錯的,因為這就不屬于函數式接口了,下面這個代碼就會編譯報錯,大家可以試一下
/**
* @Author winston
* @Date 2021/9/11
*/
// 聲明函數式接口的注解
@FunctionalInterface
public interface FunctionInterfaceExample {
/**
* 抽象方法
*/
public void demo01();
/**
* 抽象方法2
*/
public void demo02();
/**
* 接口中的靜態(tài)方法
*/
public static void staticMethod(){
System.out.println("靜態(tài)方法");
}
/**
* 接口中的默認方法
*/
default void defaultMethod(){
System.out.println("默認方法");
}
}
重寫 Object 類里的方法不會導致函數式接口失效,下面這個代碼表明上看上去是一個抽象方法,實際上它是重寫了Object類里面的方法,因此是不會報錯的
/**
* @Author winston
* @Date 2021/9/11
*/
// 聲明函數式接口的注解
@FunctionalInterface
public interface FunctionInterfaceExample {
/**
* 抽象方法
*/
public void demo01();
/**
* 重寫object中的方法
*/
public String toString();
/**
* 接口中的靜態(tài)方法
*/
public static void staticMethod(){
System.out.println("靜態(tài)方法");
}
/**
* 接口中的默認方法
*/
default void defaultMethod(){
System.out.println("默認方法");
}
}
Lambda表達式
Lambda表達式介紹
首先我們先了解一下什么是Lambda表達式,在JDK8中引入的一種新的語法元素和操作符,這個符號為->,該操作符被稱為Lambda操作符或者叫箭頭操作符,它將Lambd分為兩個部分:
左側:指定了Lambda表達式需要的參數列表
右側:指定了Lambda體,是抽象方法的實現邏輯,也就是Lambda表達式要執(zhí)行的功能
Lambda表達式的本質是作為函數式接口的實現
Lambda表達式語法
語法格式一:無參、無返回值
假設我們接口中有個抽象方法print
@FunctionalInterface
public interface LambdaDemo1 {
public void print();
}
傳統(tǒng)使用匿名函數實現的方式:
public static void main(String[] args) {
LambdaDemo1 lambdaDemo1 = new LambdaDemo1() {
@Override
public void print() {
System.out.println("我是LambdaDemo1的print方法");
}
};
lambdaDemo1.print();
}
使用Lambda表達式的方式:
public static void main(String[] args) {
LambdaDemo1 lambdaDemo1 = ()->{
System.out.println("我是LambdaDemo1的print方法");
};
lambdaDemo1.print();
}
public static void main(String[] args) {
// 方法體只有一行代碼時可以省略{}
LambdaDemo1 lambdaDemo1 = () -> System.out.println("我是LambdaDemo1的print方法");
lambdaDemo1.print();
}
語法格式二:有參、無返回值
@FunctionalInterface
public interface LambdaInterface1 {
public void print(String str);
}
傳統(tǒng)使用匿名函數實現的方式:
public static void main(String[] args) {
LambdaInterface1 lambdaInterface1 = new LambdaInterface1() {
@Override
public void print(String str) {
System.out.println(str);
}
};
lambdaInterface1.print("哈哈哈");
}
使用Lambda表達式的方式:
public static void main(String[] args) {
LambdaInterface1 lambdaInterface1 = (String str) -> {
System.out.println(str);
};
lambdaInterface1.print("哈哈哈");
}
在上面Lambda表達式的基礎上我們還可以將參數數據類型進行省略,因為參數類型可由編譯器推斷出來(因為LambdaInterface1是函數式接口有且只有一個抽象方法,因此實現的時候只能實現該方法,所以對應的參數也必然是該接口的print方法參數,所以可以省略參數類型)
public static void main(String[] args) {
// 省略方法參數類型
LambdaInterface1 lambdaInterface1 = (str) -> {System.out.println(str)};
lambdaInterface1.print("哈哈哈");
}
當方法參數只有一個時()可以省略,當方法體代碼只有一句時{}可以省略,我們在上面例子的基礎上進行改造
public static void main(String[] args) {
// 省略方法參數中的()和方法體的{}
LambdaInterface1 lambdaInterface1 = str -> System.out.println(str);
lambdaInterface1.print("哈哈哈");
}
語法格式三:Lambda需要兩個或者兩個以上的參數,多條執(zhí)行語句,并且可能有返回值
我們以JDK自帶的Comparator接口為例,傳統(tǒng)寫法:
public static void main(String[] args) {
Comparator<Integer> comparator = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
System.out.println(o1);
System.out.println(o2);
return o1.compareTo(o2);
}
};
int result = comparator.compare(2, 3);
}
使用lambda表達式的寫法:
public static void main(String[] args) {
// 不是一個參數的時候()不能省略,由于類型推斷參數類型可以省略,方法體中只有一行代碼時{}可以省略
Comparator<Integer> comparator = (o1, o2) -> {
System.out.println(o1);
System.out.println(o2);
return o1.compareTo(o2);
};
int result = comparator.compare(2, 3);
}
Lambda表達式使用小結
在使用Lambda的接口必須是函數式接口
在->左側:Lambda形參列表的參數類型可以省略,如果Lambda形參列表只有一個參數,其()也可以省略
在->右側:Lambda體應該使用一對{}包裹,如果Lambda體里面只有一句執(zhí)行語句(可能是return語句)那么{}可以省略
JAVA內置四大核心函數式接口
在JDK8中為我們提供了幾種類型的函數式接口,它們分別是消費型接口Consumer<T>,供給型接口Supplier<T>,函數型接口Function<T>,斷定型接口Predicate<T>,下面分別介紹這幾種接口的基本使用。
消費型接口Consumer
參數類型T,返回類型void,對類型T的對象應用操作,包含方法void accept(T t),簡單來說就是接收一個T,然后將它進行消費。
Consumer接口
@FunctionalInterface
public interface Consumer<T> {
// 消費方法
void accept(T t);
// 這個是用來需要兩個Consumer接口,可以把兩個Consumer接口組合到一起,在對數據進行消費
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
// 這一步就是相當于對accept方法的具體實現,返回一個consumer
return (T t) -> { accept(t); after.accept(t); };
}
}
使用accept方法
public class LambdaDemoTest {
public static void main(String[] args) {
Consumer<String> consumer = str -> System.out.println("打印信息:" + str);
consumer.accept("哈哈哈");
}
}
使用andThen方法組合消費
public static void main(String[] args) {
Consumer<String> consumer1 = str -> System.out.println("consumer1打印信息:" + str);
Consumer<String> consumer2 = str -> System.out.println("consumer2打印信息:" + str);
consumer1.andThen(consumer2).accept("哈哈哈");
}
效果:
consumer1打印信息:哈哈哈
consumer2打印信息:哈哈哈
供給型接口Supplier
無參,返回值類型T,簡單來說就是會提供一個特定對象,Supplier接口包含方法T get()
Supplier接口
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
通過使用Lambda表達式來使用get方法
public static void main(String[] args) {
// 想要一個默認值是0的Double對象
Supplier<Double> supplier = ()-> new Double("0");
Double zeroDouble = supplier.get();
}
函數型接口Function
入參T,返回值R,簡單來說就是將入參T轉成結果R
Function接口(這里主要是R apply(T t)方法,其他方法先省略)
@FunctionalInterface
public interface Function<T, R> {
...
R apply(T t);
....
}
通過Lambda表達式使用apply方法
public static void main(String[] args) {
// 想要將傳入的Integer類型轉成String類型
Function<Integer,String> function = (paramInteger) -> String.valueOf(paramInteger);
String strResult = function.apply(12);
}
斷定型接口Predicate
入參T,返回值是boolean,簡單來說就是根據入參做判斷處理
Predicate接口(這里主要是boolean test(T t)方法,其他方法先省略)
@FunctionalInterface
public interface Predicate<T> {
...
boolean test(T t);
....
}
這里我們舉個例子,給定一個存有數字的List,我們要過濾掉小于10的數字
不使用lambda表達式的方式使用test方法進行過濾:
public static void main(String[] args) {
List<Integer> numList = new ArrayList<>();
numList.add(3);
numList.add(11);
numList.add(10);
numList.add(5);
numList.add(18);
numList.add(7);
numList.add(22);
/**
* 自定義方法,用來過濾獲取想要的結果集
* 這里的第一個參數是要進行過濾的List
* 第二個參數是寫過濾規(guī)則的Predicate接口,使用了匿名內部類的實現方式實現test方法
*/
filterNum(numList, new Predicate<Integer>() {
@Override
public boolean test(Integer num) {
if(10 < num){
return true;
}
return false;
}
});
}
/**
*
* @param numList 要過濾的Integer集合
* @param integerPredicate 過濾條件
* @return
*/
private static List<Integer> filterNum(List<Integer> numList, Predicate<Integer> integerPredicate) {
List<Integer> result = new ArrayList<>();
for (Integer num : numList) {
// 如果滿足條件的就放到result集合中
boolean flag = integerPredicate.test(num);
if (flag) {
result.add(num);
}
}
return result;
}
使用lambda表達式的方式使用test方法進行過濾:
public static void main(String[] args) {
List<Integer> numList = new ArrayList<>();
numList.add(3);
numList.add(11);
numList.add(10);
numList.add(5);
numList.add(18);
numList.add(7);
numList.add(22);
/**
* 自定義方法,用來過濾獲取想要的結果集
* 這里的第一個參數是要進行過濾的List
* 第二個參數是寫過濾規(guī)則的Predicate接口,這里使用的是lambda表達式的方式
*/
filterNum(numList,num -> {
if(10 < num){
return true;
}
return false;
});
}
/**
*
* @param numList 要過濾的Integer集合
* @param integerPredicate 過濾條件
* @return
*/
private static List<Integer> filterNum(List<Integer> numList, Predicate<Integer> integerPredicate) {
List<Integer> result = new ArrayList<>();
for (Integer num : numList) {
// 如果滿足條件的就放到result集合中
boolean flag = integerPredicate.test(num);
if (flag) {
result.add(num);
}
}
return result;
}
小結
剛開始接觸Lambda表達式的話可能會覺得不太好理解,實際上Lambda表達式就是函數式接口的實現,通過上面的一些例子我們可以看出我們能夠通過使用傳統(tǒng)的匿名內部類來實現這些函數式接口,Lambda表達式通過一系列的簡化操作減少了代碼的數量,看起來簡潔了很多但是對于他人的理解難度卻是增加了不少,大家還是要慢慢學習Lambda表達式。其次就是JAVA內置四大核心函數式接口,當我們需要用到上面所說的功能時可以考慮使用而不是自己新建一個接口。