目標
-
瞭解lamdba 表達式
參考 java 簡明教程文檔
基礎概念
引入兩個概念: 函數式接口,接口中的默認方法
-
函數式接口:如果一個接口中,只包含一個抽象方法,就可以認爲是一個函數式接口。其中可以使用註解
@FunctionalInterface 標識這個接口是一個函數式接口,當然不使用這個註解標識也是可以的。
/** * 函數式接口,只包含一個抽象方法 */ @FunctionalInterface public interface MyPrint<T> { String output(T str); }
-
默認方法
默認方法用於擴展接口中的方法,jdk8 之後,爲了在接口中引入其他的功能,需要在接口中提供額外的方法,不能直接在原來的接口中,添加方法,這樣會導致實現該接口的類,都要做修改,所以就引入了默認方法,使用 default 標識。默認方法是一個非抽象的方法,實現該接口的類,也都繼承默認方法。也可以在接口中添加靜態方法,用來擴展接口的功能。
// List 接口中的 sort 就是一個默認方法 @SuppressWarnings({"unchecked", "rawtypes"}) default void sort(Comparator<? super E> c) { Object[] a = this.toArray(); Arrays.sort(a, (Comparator) c); ListIterator<E> i = this.listIterator(); for (Object e : a) { i.next(); i.set((E) e); } } // Comparator 接口的中的 靜態方法 public static <T extends Comparable<? super T>> Comparator<T> reverseOrder() { return Collections.reverseOrder(); }
lamdba 表達式
語法:(param1,param2) -> { return xxx }
首先,函數式接口,纔可以用lamdba 表達式,來表示。函數式接口中的默認方法或者靜態方法用來擴展,其他的功能。
示例:
函數式接口:
/**
* 函數式接口,只包含一個抽象方法
*/
@FunctionalInterface
public interface MyPrint<T> {
String output(T str);
default void outputinfo(){
System.out.println("info");
}
}
lamdba 表達式演示:
@Test
public void fun2() {
String strw = "aaa";
// 使用匿名內部類
System.out.println(new MyPrint<String>() {
@Override
public String output(String str) {
return str+"1";
}
}.output(strw));
// 使用lamdba 表達式
MyPrint<String> myPrint = (str) -> str+"2";
System.out.println(myPrint.output(strw));
}
從上面演示效果,lamdba 表達式,相當於把匿名內部類中需要實現的方法實現了。那如果接口中,有兩個需要被實現的方法,就不能使用lamdba 表達式,因爲lamdba表達式不知道你要實現那個方法,所以只能是 函數式接口,才能用lamdba 表達式表示。
再演示一些其他例子:
排序:
對於lamdba 表達式,參數類型,return,或者花括號,在有時,都可以省略,看如下代碼:
List<String> strs = Arrays.asList("c", "a", "d");
// 之前的實現方式
Collections.sort(strs, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.compareTo(o2);
}
});
// lamdba 實現,將匿名內部類代碼改爲 lamdba 表達式
Collections.sort(strs, (String o1, String o2) -> {
return o2.compareTo(o1);
});
// 只有一句,省略 return 和 花括號
Collections.sort(strs, (String o1, String o2) -> o2.compareTo(o1));
// 可以省略參數類型,會自動判斷
Collections.sort(strs, (o1, o2) -> o2.compareTo(o1));
// ---- 上面的lamdba 表達式,idea 會提示 黃色標識,因爲還可以更加簡單
// 自然順序的比較
Collections.sort(strs, Comparator.naturalOrder());
// 自然順序相反的比較
Collections.sort(strs, Comparator.reverseOrder());
線程創建
// 將原來 new Runnable 的代碼,變成了 lamdba 表達式
@Test
public void fun6() {
new Thread(
() -> System.out.println(Thread.currentThread().getName())
,"thread100").start();
}
雙冒號 :: 關鍵字
雙冒號:: 關鍵字,用於引用方法和構造函數
方法引用是與lambda表達式相關的一個重要特性。它提供了一種不執行方法的方法。爲此,方法引用需要由兼容的函數接口組成的目標類型上下文。
oracle官方介紹:
Method References
You use lambda expressions to create anonymous methods. Sometimes, however, a lambda expression does nothing but call an existing method. In those cases, it’s often clearer to refer to the existing method by name. Method references enable you to do this; they are compact, easy-to-read lambda expressions for methods that already have a name.
方法引用
使用lambda表達式創建匿名方法。但是,有時lambda表達式只調用現有方法。在這些情況下,按名稱引用現有方法通常更清楚。方法引用使您能夠做到這一點;對於已經有名稱的方法,它們是緊湊、易於讀取的lambda表達式。
// TODO 對於方法引用,還不是很理解,但是在語義上,其實能看懂做了什麼事情,有了新理解,再補充
具體示例:參考 Java8新特性2:方法引用–深入理解雙冒號::的使用
在代碼中的體現,更突顯在java8 stream 中的應用
實現一個小例子:根據身份證號碼,將性別自動設置到對象的性別字段中
通用的身份證號碼設置性別方法
// 兩個參數一個是 idcard ,一個是 Consumer 接口
// 簡單介紹一下 Consumer 是一個函數式接口,接受一個參數,但是不返回結果。 執行的方法 accept
private void setSexInfo(String idcard, Consumer<String> user) {
int genderByIdCard = IdcardUtil.getGenderByIdCard(idcard); // 根據身份證,獲取性別(參見 hutool 這個類庫)
if (genderByIdCard == 1) { // 男
user.accept("M");
}
user.accept("F"); // 女
}
用戶對象:
public class Person{
String sex;
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
測試:
@Test
public void fun09(){
Person person = new Person();
// 使用匿名函數
setSexInfo("410327188510154456", new Consumer<String>() {
@Override
public void accept(String s) {
person.setSex(s);
}
});
// 使用 lamdba 表達式
setSexInfo("410327188510154456", s -> myComparator.setSex(s));
// 使用 :: 雙冒號
setSexInfo("410327188510154456", person::setSex);
}
對於 Consumer 接口中執行的流程,可以看做是,當 user.accept(“M”); 執行時,M 會作爲參數,調用對接口的實現,就是 person.setSex(s); 的邏輯
在idea 中,會給出對應的優化建議,點代碼左邊提示的 黃色選項 。上述代碼,就可以從匿名函數到lamdba表達式到雙冒號的形式的優化
Lambda的範圍
參見: java8簡明教程文檔
對於lambdab表達式外部的變量,其訪問權限的粒度與匿名對象的方式非常類似。 你能夠訪問局部對應的外部區域的局部final變量,以及成員變量和靜態變量。
一些函數式接口的簡單介紹
具體代碼示例:參考 java8 實戰 3.4.1 使用函數接口 這一章 或者 java8 簡明教程中的介紹
Predicate
接受一個輸入參數,返回一個boolean 的結果。在操作stream中,filter方法中接受的參數就是 Predicate 接口
boolean test(T t);
示例:
/**
* Predicate 斷言,一個布爾類型的函數
* 可以用於 集合類,filter 的過濾等等
*/
@Test
public void fun() {
// Predicate是一個布爾類型的函數
Predicate<String> predicate = (s) -> s.length() > 0;
boolean str1 = predicate.test("hello world");
boolean str2 = predicate.test("");
System.out.println(str1);
System.out.println(str2);
// 短路與 &&
boolean test = predicate.and((s) -> s.equals("hello")).test("aaa");
System.out.println(test);
// 邏輯非
boolean hello_world = predicate.negate().test("hello world");
System.out.println(hello_world);
// 邏輯或
boolean aaa = predicate.or(predicate).test("aaa");
System.out.println(aaa);
// 判斷兩個對象是否相等
boolean test2 = Predicate.isEqual("a2").test("a3");
System.out.println(test2);
boolean test3 = Predicate.isEqual("a3").test("a3");
System.out.println(test3);
boolean test4 = Predicate.isEqual(null).test("a3");
System.out.println(test4);
Predicate<String> ceshi1 = String::isEmpty;
boolean test1 = ceshi1.test("");
System.out.println(test1);
}
Function
接收一個參數,返回一個結果。可以理解爲數學公式 y = f(x),傳入參數x,得到結果y. 在集合stream中,map方法接收的參數就是 Function
R apply(T t);
示例:
/**
* 表示接受一個參數,並返回一個結果
* 可以理解爲 y = f(x)
* 接受x 參數,輸出y,那麼 f(x) 這個函數,我們自己定義就可以了
*/
@Test
public void fun02() {
// 定義 f(x) 函數
Function<Integer, Integer> function = (x) -> x + 1;
// 獲取結果
Integer result = function.apply(5);
System.out.println(result);
// 定義 f(x) 函數
Function<Integer, Integer> function1 = (x) -> x * 5;
// compose 代表先執行 compose 傳入的邏輯,再執行apply 的邏輯
// 6
Integer apply = function.compose(function1).apply(1);
System.out.println(apply);
// andThen 代表先執行當前的邏輯,再執行,andthen 傳入的邏輯
Integer apply1 = function.andThen(function1).apply(1);
System.out.println(apply1);
// ((1+1)+1)*5*5 建造者模式
Integer apply2 = function.andThen(function1).andThen(function1).compose(function).apply(1);
System.out.println(apply2);
Function<String, Integer> toInteger = Integer::valueOf;
Function<String, String> backToString = toInteger.andThen(String
::valueOf);
backToString.apply("123"); // "123"
}
Supplier
不接收參數,返回一個結果。可以理解爲 實體字段的get 方法
T get();
示例:
/**
* 接口產生一個給定類型的結果
*/
@Test
public void fun03() {
Supplier<String> str = String::new;
String s = str.get();
Supplier<Person> str1 = Person::new;
// 在執行get 方法時,纔會拿到person 對象 ,spring beanfactory 在執行 getbean 的時候,纔會創建該對象
// 延遲加載的功能
Person person = str1.get();
}
Consumer
接收一個參數,但是不返回結果。可以理解爲 實體字段的set 方法。在集合forEach 時,傳入的參數就是 Consumer 接口
void accept(T t);
示例:
/**
* 接受一個參數輸入且沒有任何返回值的操作
* 在 集合的 foreach 中 就需要填這個接口
*/
@Test
public void fun04() {
Consumer<Person> personConsumer = (t) -> System.out.println("第一打印" + t.toString());
Consumer<Person> personConsumer2 = (t) -> System.out.println("第二打印" + t.toString());
// 執行
personConsumer.accept(new Person());
// 現在執行 accpect ,再執行 addthen 添加的
personConsumer.andThen(personConsumer2).accept(new Person());
// foreach 代碼中,就調用改的 action.accept(t);
Arrays.asList("1", "2").forEach((x) -> {
System.out.println(x);
});
}
ToIntFunction
接收一個參數,返回一個int 類型的結果,跟Function 類似,這裏限定了返回的結果類型是 int
int applyAsInt(T var1);