我的Lambda的學習筆記
前述
Lambda表示式是 Java 8 的新特性。許多語言都有 Lambda 的特性。
因此使用的 Java 環境一定要 8 以上的環境。
Lambda
到底什麼是 Lambda 表示式呢?
Lambda 表示式,也可稱為閉包。Lambda 允許把函式作為一個方法的引數直接傳遞到方法中去。可以讓我們不用費神去給函式起名。但是 Lambda 也只適合於簡單的函式,對於複雜的函式,寫成 Lambda 的形式反而會讓人更加看不懂。
例項
接下來用一個例項慢慢匯入 Lambda,要完成的是一個 判斷Person的id是否大於90的功能
首先建立一個Person類,包含 id 屬性
1 package person; 2 3 /** 4* Person類 5* @author jyroy 6* 7*/ 8 public class Person { 9@SuppressWarnings("unused") 10public int id; 11 12public Person() { 13 14} 15 16public Person(int id) { 17this.id = id; 18} 19 20@Override 21public String toString() { 22return "Person [id=" + id + "]"; 23} 24 25 }
普通方法
用普通方法,在 for 迴圈中通過 if 進行條件判斷 if(person.id>90)
1 package normal; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 import java.util.Random; 6 7 import lambda.Check; 8 import person.Person; 9 10 public class TestLambda { 11@SuppressWarnings({ "rawtypes", "unused", "unchecked" }) 12public static void main(String[] args) { 13Random r = new Random();//隨機生成100個數 14List<Person> lists = new ArrayList<Person>(); 15for(int i=0;i<100;i++) { 16lists.add(new Person(r.nextInt(100))); 17} 18//使用 Lambda 篩選出大於90的資料 19judge(lists); 20 21} 22@SuppressWarnings("rawtypes") 23private static void judge(List<Person> lists) { 24for(Person person:lists) { 25if(person.id>90) {//判斷是否大於90 26System.out.println(person); 27} 28} 29} 30 }
結果
匿名類方法
建立一個匿名類,通過匿名類來實現這個判斷 id 大於90的功能。
首先提供匿名類需要的介面,用於建立一個判斷的類
1 package anonymity; 2 3 import person.Person; 4 5 public interface PersonCheck { 6public boolean test(Person person); 7 }
通過匿名類實現介面
1 //使用 匿名類的方式 篩選出大於90的資料 2 PersonCheck personCheck = new PersonCheck() { 3@Override 4public boolean test(Person person) { 5return person.id>90; 6} 7 };
實現之後,就可以在judge函式中呼叫personCheck例項的test函式進行處理
1 package anonymity; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 import java.util.Random; 6 7 import lambda.Check; 8 import person.Person; 9 10 /** 11* 匿名類方式 12* @author jyroy 13* 14*/ 15 public class TestLambda { 16@SuppressWarnings({ "rawtypes", "unused", "unchecked" }) 17public static void main(String[] args) { 18Random r = new Random(); 19List<Person> lists = new ArrayList<Person>(); 20for(int i=0;i<100;i++) { 21lists.add(new Person(r.nextInt(100))); 22} 23 24//使用 匿名類的方式 篩選出大於90的資料 25PersonCheck personCheck = new PersonCheck() { 26@Override 27public boolean test(Person person) { 28return person.id>90; 29} 30}; 31 32judge(lists, personCheck); 33 34} 35@SuppressWarnings("rawtypes") 36private static void judge(List<Person> lists, PersonCheck personCheck) { 37for(Person person:lists) { 38if(personCheck.test(person)) { 39System.out.println(person); 40} 41} 42} 43 }
結果
Lambda方式
接下來就是Lambda方式了
先上 Lambda表示式的寫法,執行過程式之後再做總結
1 person->person.id>90
這便是一個Lambda表示式的形式,先記住這個形式,這個形式就是判斷 person.id>90。
1 package lambda; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 import java.util.Random; 6 7 import anonymity.PersonCheck; 8 import person.Person; 9 10 /** 11* Lambda表示式 12* @author jyroy 13* 14*/ 15 public class TestLambda { 16 17@SuppressWarnings({ "rawtypes", "unused", "unchecked" }) 18public static void main(String[] args) { 19Random r = new Random(); 20List<Person> lists = new ArrayList<Person>(); 21for(int i=0;i<100;i++) { 22lists.add(new Person(r.nextInt(100))); 23} 24//使用 Lambda 篩選出大於90的資料 25filter(lists, person -> person.id > 90); 26 27} 28@SuppressWarnings("rawtypes") 29private static void filter(List<Person> lists, PersonCheck personCheck) { 30for(Person person:lists) { 31if(personCheck.test(person)) { 32System.out.println(person); 33} 34} 35} 36 }
結果
實現了同樣的效果
總結
上面的程式中使用了 Lambda表示式,完成了判斷的功能,而且相比匿名類的寫法要簡單非常多,使用 Lambda 表示式可以使程式碼變的更加簡潔緊湊。
Lambda 表示式的語法格式:
1 (parameters) -> expression 2 或 3 (parameters) ->{ statements; }
以下是lambda表示式的重要特徵:
- 可選型別宣告: 不需要宣告引數型別,編譯器可以統一識別引數值。
- 可選的引數圓括號: 一個引數無需定義圓括號,但多個引數需要定義圓括號。
- 可選的大括號: 如果主體包含了一個語句,就不需要使用大括號。
- 可選的返回關鍵字: 如果主體只有一個表示式返回值則編譯器會自動返回值,大括號需要指定明表示式返回了一個數值。
簡單的例子
1 // 1. 不需要引數,返回值為 5 2 () -> 5 3 4 // 2. 接收一個引數(數字型別),返回其2倍的值 5 x -> 2 * x 6 7 // 3. 接受2個引數(數字),並返回他們的差值 8 (x, y) -> x – y 9 10 // 4. 接收2個int型整數,返回他們的和 11 (int x, int y) -> x + y 12 13 // 5. 接受一個 string 物件,並在控制檯列印,不返回任何值(看起來像是返回void) 14 (String s) -> System.out.print(s)
現在就可以解釋上面的例項中的 Lambda表示式 的含義
person->person.id>90 即接收一個person引數,並返回大於person.id>90的資料
Lambda方法引用
方法引用是用來直接訪問類或者例項的已經存在的方法或者構造方法。方法引用提供了一種引用而不執行方法的方式,它需要由相容的函式式介面構成的目標型別上下文。計算時,方法引用會建立函式式介面的一個例項。
可以看作是lambda的一種快捷寫法,顯式的指定方法的名稱更具可讀性。格式:目標引用+分隔符::+方法,例如,Dog::getAge就是引用了Dog類中定義的方法getAge。
注意方法引用是一個Lambda表示式,其中方法引用的操作符是雙冒號"::"。
Lambda方法引用包含一下四種:
- 靜態方法引用
- 物件方法引用
- 類成員方 法引用
- 構造方法引用
靜態方法引用
首先需要有一個靜態方法
1 public static boolean test(Person person) { 2return person.id>90 && person.id<95; 3 }
在Lambda表示式中呼叫這個靜態方法,因為是靜態方法可以不用建立物件,所以直接用類名進行呼叫
1 judge(lists, person -> TestLambda.test(person));
利用方法引用呼叫靜態方法的形式
1 judge(lists, TestLambda::test);
主程式為
1 package references; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 import java.util.Random; 6 7 import anonymity.PersonCheck; 8 import person.Person; 9 10 /** 11* Lambda表示式 12* @author jyroy 13* 14*/ 15 public class TestLambda { 16 17@SuppressWarnings({ "rawtypes", "unused", "unchecked" }) 18public static void main(String[] args) { 19Random r = new Random(); 20List<Person> lists = new ArrayList<Person>(); 21for(int i=0;i<100;i++) { 22lists.add(new Person(r.nextInt(100))); 23} 24//使用 Lambda 篩選出大於90的資料 25judge(lists, person -> person.id < 10); 26 27//在Lambda表示式中使用靜態方法 28judge(lists, person -> TestLambda.test(person)); 29 30//直接使用靜態方法 31judge(lists, TestLambda::test); 32 33} 34 35public static boolean test(Person person) { 36return person.id>90 && person.id<95; 37} 38 39@SuppressWarnings("rawtypes") 40private static void judge(List<Person> lists, PersonCheck personCheck) { 41for(Person person:lists) { 42if(personCheck.test(person)) { 43System.out.println(person); 44} 45} 46} 47 }
物件方法引用
和靜態方法相似,,但是在傳遞方法的時候,因為不是靜態方法,所以必須要利用物件進行傳送
1 package references; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 import java.util.Random; 6 7 import anonymity.PersonCheck; 8 import person.Person; 9 10 /** 11* Lambda表示式 12* @author jyroy 13* 14*/ 15 public class TestLambda { 16 17@SuppressWarnings({ "rawtypes", "unused", "unchecked" }) 18public static void main(String[] args) { 19Random r = new Random(); 20List<Person> lists = new ArrayList<Person>(); 21for(int i=0;i<100;i++) { 22lists.add(new Person(r.nextInt(100))); 23} 24 25//使用引用物件方法 26TestLambda testLambda = new TestLambda(); 27judge(lists, testLambda::test2); 28 29} 30 31public boolean test2(Person person) { 32return person.id>90 && person.id<95; 33} 34 35@SuppressWarnings("rawtypes") 36private static void judge(List<Person> lists, PersonCheck personCheck) { 37for(Person person:lists) { 38if(personCheck.test(person)) { 39System.out.println(person); 40} 41} 42} 43 }
類成員方法引用
Person類要新增一個成員方法,才能夠進行成員方法的引用
1public boolean test3() { 2return this.id>90 && this.id<95; 3}
在Lambda表示式中使用 test3方法
1 judge(lists, person -> person.test3());
利用方法引用的寫法為
1 judge(lists,Person::test3);
主程式為
1 package references; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 import java.util.Random; 6 7 import anonymity.PersonCheck; 8 import person.Person; 9 10 /** 11* Lambda表示式 12* @author jyroy 13* 14*/ 15 public class TestLambda { 16 17@SuppressWarnings({ "rawtypes", "unused", "unchecked" }) 18public static void main(String[] args) { 19Random r = new Random(); 20List<Person> lists = new ArrayList<Person>(); 21for(int i=0;i<100;i++) { 22lists.add(new Person(r.nextInt(100))); 23} 24 25 26//使用類成員方法 27judge(lists, person -> person.test3()); 28 29//可改寫為 30judge(lists,Person::test3); 31 32 33} 34 35public static boolean test(Person person) { 36return person.id>90 && person.id<95; 37} 38 39public boolean test2(Person person) { 40return person.id>90 && person.id<95; 41} 42 43@SuppressWarnings("rawtypes") 44private static void judge(List<Person> lists, PersonCheck personCheck) { 45for(Person person:lists) { 46if(personCheck.test(person)) { 47System.out.println(person); 48} 49} 50} 51 }
構造方法引用
需要有返回一個物件的方法
構造方法引用形式為
1 ArrayList::new 2 Person::new
1 package lambda; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 import java.util.function.Supplier; 6 7 public class TestLambda { 8public static void main(String[] args) { 9Supplier<List> s = new Supplier<List>() { 10public List get() { 11return new ArrayList(); 12} 13}; 14//引用構造器 15List list3 = getList(ArrayList::new); 16 17} 18 19public static List getList(Supplier<List> s){ 20return s.get(); 21}
聚合操作
引入例項
在上面的程式中,遍歷輸出資料利用的是for迴圈的方式
1 for(Person person:lists) { 2if(personCheck.test(person)) { 3System.out.println(person); 4} 5 }
我們可以利用聚合操作來進行資料的輸出
1 lists 2.stream() 3.filter(person -> person.id>95) 4.forEach(person -> System.out.println(person.id));
主程式為
1 package normal; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 import java.util.Random; 6 7 import lambda.Check; 8 import person.Person; 9 10 public class TestLambda { 11@SuppressWarnings({ "rawtypes", "unused", "unchecked" }) 12public static void main(String[] args) { 13Random r = new Random(); 14List<Person> lists = new ArrayList<Person>(); 15for(int i=0;i<100;i++) { 16lists.add(new Person(r.nextInt(100))); 17} 18System.out.println("使用傳統方式----"); 19for(Person person:lists) { 20if(person.id>90) { 21System.out.println(person); 22} 23} 24System.out.println("聚合操作方式"); 25lists 26.stream() 27.filter(person -> person.id>95) 28.forEach(person -> System.out.println(person.id)); 29} 30 }
結果
Stream和管道
我們對應上面的程式碼,可以看出,聚合操作分為三步
- 生成
- 操作、變換(可以多次)
- 消耗(只有一次)
當然我們還要知道stream和管道的概念
Stream :Java 8 API添加了一個新的抽象稱為流Stream,可以讓你以一種宣告的方式處理資料。 Stream 使用一種類似用 SQL 語句從資料庫查詢資料的直觀方式來提供一種對 Java 集合運算和表達的高階抽象。Stream API可以極大提高Java程式設計師的生產力,讓程式設計師寫出高效率、乾淨、簡潔的程式碼。這種風格將要處理的元素集合看作一種流, 流在管道中傳輸, 並且可以在管道的節點上進行處理, 比如篩選, 排序,聚合等。元素流在管道中經過中間操作(intermediate operation)的處理,最後由最終操作(terminal operation)得到,前面處理的結果。 注意 :這個Stream和I/O中的InputStream,OutputStream是不一樣的概念。
管道:指的是一系列的聚合操作。
管道又分3個部分:管道源、中間操作、結束操作
管道源 :在這個例子裡,源是一個List,可以用 .stream() 方法切換成管道源。但是陣列沒有stream() 方法,需要用 Arrays.stream(hs) 或者 Stream.of(hs)
1 package lambda; 2 3 import java.util.ArrayList; 4 import java.util.Arrays; 5 import java.util.HashMap; 6 import java.util.List; 7 import java.util.Random; 8 9 import charactor.Hero; 10 11 public class TestAggregate { 12 13public static void main(String[] args) { 14Random r = new Random(); 15List<Hero> heros = new ArrayList<Hero>(); 16for (int i = 0; i < 5; i++) { 17heros.add(new Hero("hero " + i, r.nextInt(1000), r.nextInt(100))); 18} 19//管道源是集合 20heros 21.stream() 22.forEach(h->System.out.println(h.name)); 23 24//管道源是陣列 25Hero hs[] = heros.toArray(new Hero[heros.size()]); 26Arrays.stream(hs) 27.forEach(h->System.out.println(h.name)); 28 29} 30 }
中間操作 : 每個中間操作,又會返回一個Stream,比如.filter()又返回一個Stream, 中間操作是“懶”操作,並不會真正進行遍歷。 中間操作比較多,主要分兩類 對元素進行篩選 和 轉換為其他形式的流
對元素進行篩選:
filter 匹配
distinct 去除重複(根據equals判斷)
sorted 自然排序
sorted(Comparator<T>) 指定排序
limit 保留
skip 忽略
轉換為其他形式的流
mapToDouble 轉換為double的流
map 轉換為任意型別的流
1 package lambda; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 import java.util.Random; 6 7 import charactor.Hero; 8 9 public class TestAggregate { 10 11public static void main(String[] args) { 12Random r = new Random(); 13List<Hero> heros = new ArrayList<Hero>(); 14for (int i = 0; i < 5; i++) { 15heros.add(new Hero("hero " + i, r.nextInt(1000), r.nextInt(100))); 16} 17//製造一個重複資料 18heros.add(heros.get(0)); 19System.out.println("初始化集合後的資料 (最後一個數據重複):"); 20System.out.println(heros); 21System.out.println("滿足條件hp>100&&damage<50的資料"); 22 23heros 24.stream() 25.filter(h->h.hp>100&&h.damage<50) 26.forEach(h->System.out.print(h)); 27 28System.out.println("去除重複的資料,去除標準是看equals"); 29heros 30.stream() 31.distinct() 32.forEach(h->System.out.print(h)); 33System.out.println("按照血量排序"); 34heros 35.stream() 36.sorted((h1,h2)->h1.hp>=h2.hp?1:-1) 37.forEach(h->System.out.print(h)); 38 39System.out.println("保留3個"); 40heros 41.stream() 42.limit(3) 43.forEach(h->System.out.print(h)); 44 45System.out.println("忽略前3個"); 46heros 47.stream() 48.skip(3) 49.forEach(h->System.out.print(h)); 50 51System.out.println("轉換為double的Stream"); 52heros 53.stream() 54.mapToDouble(Hero::getHp) 55.forEach(h->System.out.println(h)); 56 57System.out.println("轉換任意型別的Stream"); 58heros 59.stream() 60.map((h)-> h.name + " - " + h.hp + " - " + h.damage) 61.forEach(h->System.out.println(h)); 62 63} 64 }
結束操作 :當這個操作執行後,流就被使用“光”了,無法再被操作。所以這必定是流的最後一個操作。 結束操作不會返回Stream,但是會返回int、float、String、 Collection或者像forEach,什麼都不返回, 結束操作才進行真正的遍歷行為,在遍歷的時候,才會去進行中間操作的相關判斷.
常見結束操作如下:
forEach() 遍歷每個元素
toArray() 轉換為陣列
min(Comparator<T>) 取最小的元素
max(Comparator<T>) 取最大的元素
count() 總數
findFirst() 第一個元素
1 package lambda; 2 3 import java.util.ArrayList; 4 import java.util.Arrays; 5 import java.util.List; 6 import java.util.Random; 7 8 import org.omg.Messaging.SYNC_WITH_TRANSPORT; 9 10 import charactor.Hero; 11 12 public class TestAggregate { 13 14public static void main(String[] args) { 15Random r = new Random(); 16List<Hero> heros = new ArrayList<Hero>(); 17for (int i = 0; i < 5; i++) { 18heros.add(new Hero("hero " + i, r.nextInt(1000), r.nextInt(100))); 19} 20System.out.println("遍歷集合中的每個資料"); 21heros 22.stream() 23.forEach(h->System.out.print(h)); 24System.out.println("返回一個數組"); 25Object[] hs= heros 26.stream() 27.toArray(); 28System.out.println(Arrays.toString(hs)); 29System.out.println("返回傷害最低的那個英雄"); 30Hero minDamageHero = 31heros 32.stream() 33.min((h1,h2)->h1.damage-h2.damage) 34.get(); 35System.out.print(minDamageHero); 36System.out.println("返回傷害最高的那個英雄"); 37 38Hero mxnDamageHero = 39heros 40.stream() 41.max((h1,h2)->h1.damage-h2.damage) 42.get(); 43System.out.print(mxnDamageHero); 44 45System.out.println("流中資料的總數"); 46long count = heros 47.stream() 48.count(); 49System.out.println(count); 50 51System.out.println("第一個英雄"); 52Hero firstHero = 53heros 54.stream() 55.findFirst() 56.get(); 57 58System.out.println(firstHero); 59 60} 61 }
程式設計例項
首選準備100個Person物件,id都是隨機數。
分別用傳統方式和聚合操作的方式,把id第三高的名稱和id打印出來
Person.java
1 package aggregation; 2 3 public class Person { 4public String name; 5public int id; 6 7public Person(String name, int id){ 8this.name = name; 9this.id = id; 10} 11 12@Override 13public String toString() { 14return "Person [name=" + name + ", id=" + id + "]"; 15} 16 }
TestLambda.java
1 package aggregation; 2 3 import java.util.ArrayList; 4 import java.util.Collections; 5 import java.util.Comparator; 6 import java.util.List; 7 import java.util.Random; 8 9 import lambda.Check; 10 11 public class TestLambda { 12@SuppressWarnings({ "rawtypes", "unused", "unchecked" }) 13public static void main(String[] args) { 14Random r = new Random(); 15List<Person> lists = new ArrayList<Person>(); 16for(int i=0;i<100;i++) { 17lists.add(new Person("Person"+i, r.nextInt(100))); 18} 19System.out.println("使用傳統方式----"); 20Comparator<Person> c = new Comparator<Person>() { 21@Override 22public int compare(Person p1, Person p2) { 23return p1.id >= p2.id ? 1 : -1; 24} 25}; 26Collections.sort(lists, c); 27System.out.println(lists.get(2)); 28 29System.out.println("聚合操作方式----"); 30Person p = lists 31.stream() 32.sorted((person1, person2) -> person1.id>=person2.id?1:-1) 33.skip(2) 34.findFirst() 35.get(); 36System.out.println(p); 37} 38 }
結果