lambda與stream是java8中比較重要兩個新特性,lambda表達式采用一種簡潔的語法定義代碼塊,允許我們將行為傳遞到函數中。之前我們想將行為傳遞到函數中,僅有的選擇是使用匿名內部類,現在我們可以使用lambda表達式替代匿名內部類。在學習lambda表達式之前,建議各位看官先去學習一下匿名內部類(JAVA基礎知識|內部類)。
stream提供了很多有用的api,方便了我們對集合的操作
一、lambda表達式
基本語法:
(parameters) -> expression
或
(parameters) ->{ statements; }
lambda表達式的重要特征:
- 可選類型聲明:不需要聲明參數類型,編譯器可以統一識別參數值。
- 可選的參數圓括號:一個參數無需定義圓括號,但多個參數需要定義圓括號。
- 可選的大括號:如果主體包含了一個語句,就不需要使用大括號。
- 可選的返回關鍵字:如果主體只有一個表達式返回值則編譯器會自動返回值,大括號需要指定明表達式返回了一個數值。
以下是一些簡單的例子,便於我們理解這些特性:
// 1. 不需要參數,返回值為5 () -> 5 // 2. 接收一個參數,()可以省略,返回其2倍的值 x -> 2 * x // 3. 接受2個參數(數字),並返回他們的差值 (x, y) ->x –y // 4. 接收2個int型整數,返回他們的和 ( int x, int y) ->x + y // 5. 接受一個 string 對象,並在控制台打印,不返回任何值(看起來像是返回void) (String s) ->System.out.print(s)
二、lambda表達式使用
2.1、在for循環中使用
package com.my.controller; import junit.framework.TestCase; import org.junit.Test; import java.util.ArrayList; import java.util.List; /** * description:{description} * author:jyy * date:2018-01-09 16:43 * modify:{modify} */ public class AppTest extends TestCase { @Test public void test() { List<String> list = new ArrayList<>(); list.add("北京"); list.add("上海"); list.add("廣州"); list.add("深圳"); for (String str : list) { System.out.println(str); } System.out.println("================="); list.forEach(str -> System.out.println(str)); } }
執行結果:
北京 上海 廣州 深圳 ================= 北京 上海 廣州 深圳
2.2、替代匿名內部類使用
我們使用JAVA基礎知識|內部類中的try catch示例
package com.my.controller; import junit.framework.TestCase; import org.junit.Test; import java.util.ArrayList; import java.util.List; /** * description:{description} * author:jyy * date:2018-01-09 16:43 * modify:{modify} */ public class AppTest extends TestCase { @Test public void test() { //匿名內部類 new ExceptionTemplate().execute(new CatchExceptionable() { @Override public void catchException() { System.out.println("代碼"); } }); //lambda表達式 new ExceptionTemplate().execute(() -> System.out.println("代碼")); } }
2.3、lambda表達式與Comparator類結合使用
package com.my.controller; import junit.framework.TestCase; import org.junit.Test; import java.util.ArrayList; import java.util.Comparator; import java.util.List; /** * description:{description} * author:jyy * date:2018-01-09 16:43 * modify:{modify} */ public class AppTest extends TestCase { @Test public void test() { List<String> list = new ArrayList<>(); list.add("BeiJing"); list.add("ShangHai"); list.add("GuangZhou"); list.add("ShenZhen"); list.sort(new Comparator<String>() { @Override public int compare(String o1, String o2) { return o1.compareTo(o2); } }); list.forEach(str -> System.out.println(str)); System.out.println("=============="); List<String> list2 = new ArrayList<>(); list2.add("BeiJing"); list2.add("ShangHai"); list2.add("GuangZhou"); list2.add("ShenZhen"); //list2.sort((String o1,String o2) -> o1.compareTo(o2)); Comparator<String> comparator =(String o1,String o2) -> o1.compareTo(o2); list2.sort(comparator); list2.forEach(str -> System.out.println(str)); } }
執行結果:
BeiJing GuangZhou ShangHai ShenZhen ============== BeiJing GuangZhou ShangHai ShenZhen
2.4、 lambda不能訪問非final的局部變量
之前我們說過匿名內部類不能訪問非final的局部變量,其實lambda也有這個限制,lambda與匿名內部類在訪問外部變量時,都不允許有修改變量的傾向。
因為實例變量存在堆中,而局部變量是在棧上分配,lambda 表達(匿名類) 會在另一個線程中執行。如果在線程中要直接訪問一個局部變量,可能線程執行時該局部變量已經被銷毀了,這會導致一些問題。
Java 8 的 lambda 可以捕獲什么變量呢?
(1). 捕獲實例或靜態變量是沒有限制的 (可認為是通過 final 類型的局部變量 this 來引用前兩者);
(2). 捕獲的局部變量必須顯式的聲明為 final或實際效果的final 類型。
也就是在 Java 8 下,即使局部變量未聲明為 final 類型,一旦在匿名類中訪問了一下就被強型加上了 final 屬性,所以后面就無法再次賦值了。換句話說,如果在匿名類或 lambda 表達式中訪問的局部變量,如果不是 final 類型的話,編譯器自動加上 final 修飾符。
public DocApplicationTests(){ String str="123"; Consumer<String> consumer = a->System.out.print(str); //str="345";//編譯報錯 }
三、流stream
Java 8 中的stream 是對集合(Collection)對象功能的增強,它專注於對集合對象進行各種非常便利、高效的聚合操作(aggregate operation),或者大批量數據操作 (bulk data operation)。stream API 借助於同樣新出現的 lambda 表達式,極大的提高編程效率和程序可讀性。同時它提供串行和並行兩種模式進行匯聚操作,並發模式能夠充分利用多核處理器的優勢
生成流的兩種方式:
- stream() − 為集合創建串行流。
- parallelStream() − 為集合創建並行流。
下面我們就來使用stream提供的各種API:
3.1、篩選和切片
| 方法 | 描述 |
| filter | 從流中過濾元素 |
| distinct | 通過流所生成的元素的hashCode()和equals()方法去重 |
| limit | 截斷流,選取前n個元素 |
| skip | 跳過元素,返回一個扔掉了前 n 個元素的流。若流中元素不足n個,則返回一個空流 |
package com.my.po; /** * description:{description} * author:jyy * date:2018-02-11 11:06 * modify:{modify} */ public class Employee { private String id; private String name; private double salary; private String sex; public Employee(String id, String name, double salary,String sex) { this.id = id; this.name = name; this.salary = salary; this.sex=sex; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getSalary() { return salary; } public void setSalary(double salary) { this.salary = salary; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } @Override public String toString() { return "Employee{" + "id='" + id + '\'' + ", name='" + name + '\'' + ", salary=" + salary + ", sex='" + sex + '\'' + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Employee employee = (Employee) o; return id.equals(employee.id); } @Override public int hashCode() { return id.hashCode(); } }
package com.my.controller; import com.my.po.Employee; import junit.framework.TestCase; import org.junit.Test; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; /** * description:{description} * author:jyy * date:2018-01-09 16:43 * modify:{modify} */ public class AppTest extends TestCase { @Test public void test() { Employee[] employees = { new Employee("1001", "李明", 5000, "male"), new Employee("1002", "王明", 7000, "male"), new Employee("1003", "張麗", 10000, "female"), new Employee("1004", "謝楠", 11000, "female"), new Employee("1004", "謝楠", 11000, "female") }; List<Employee> list = Arrays.asList(employees); //forEach,查詢所有數據 list.stream().forEach(e -> System.out.println(e.toString())); //filter,查詢集合中所有的女性 list.stream().filter(e -> Objects.equals(e.getSex(), "female")).forEach(e -> System.out.println(e.toString())); //查詢集合中薪資大於6000的雇員 list.stream().filter(e -> e.getSalary() > 7000).forEach(e -> System.out.println(e)); //limit,查詢前兩條數據 list.stream().limit(2).forEach(e -> System.out.println(e.toString())); //distinct,去重,利用Employee對象中的hashCode()和equals()方法 list.stream().distinct().forEach(e -> System.out.println(e)); //skip,跳過前兩個 list.stream().skip(2).forEach(e -> System.out.println(e)); } }
3.2、映射
| 方法 | 描述 |
| map(Function f) | 接受一個函數作為參數,並將函數應用到每一個元素上,返回新的元素 |
| mapToDouble(ToDoubleFunction f) | 返回的新元素為double類型 |
| mapToInt(ToIntFunction f) | 返回的新元素為int類型 |
| mapToLong(ToLongFunction f) | 返回的新元素為long類型 |
| flatMap(Function f) | 操作多層嵌套的流,使其扁平化 |
3.2.1、map
Employee[] employees = { new Employee("1001", "李明", 5000, "male"), new Employee("1002", "王明", 7000, "male"), new Employee("1003", "張麗", 10000, "female"), new Employee("1004", "謝楠", 11000, "female"), new Employee("1004", "謝楠", 11000, "female") }; List<Employee> list = Arrays.asList(employees); //map list.stream().map(e -> e.getSalary()).forEach(e -> System.out.println(e));
執行結果:
5000.0 7000.0 10000.0 11000.0 11000.0
可以看出,集合list經過map操作之后,類型已經改變。具體什么類型,由返回值決定
3.2.2、mapToDouble、mapToInt、mapToLong
Employee[] employees = { new Employee("1001", "李明", 5000, "male"), new Employee("1002", "王明", 7000, "male"), new Employee("1003", "張麗", 10000, "female"), new Employee("1004", "謝楠", 11000, "female"), new Employee("1004", "謝楠", 11000, "female") }; List<Employee> list = Arrays.asList(employees); list.stream().mapToDouble(e -> e.getSalary()).forEach(e -> System.out.println(e));
執行結果:
5000.0 7000.0 10000.0 11000.0 11000.0
3.2.3、flatMap
Employee[] employees1 = { new Employee("1001", "李明", 5000, "male"), new Employee("1002", "王明", 7000, "male"), new Employee("1003", "張麗", 10000, "female"), new Employee("1004", "謝楠", 11000, "female") }; Employee[] employees2 = { new Employee("1005", "Marry", 5000, "male"), new Employee("1006", "Linda", 7000, "male"), new Employee("1007", "Cris", 10000, "female") }; List<Employee[]> list = new ArrayList<>(); list.add(employees1); list.add(employees2); list.stream().flatMap(e -> Arrays.stream(e)).forEach(e -> System.out.println(e.toString()));
執行結果:
Employee{id='1001', name='李明', salary=5000.0, sex='male'}
Employee{id='1002', name='王明', salary=7000.0, sex='male'}
Employee{id='1003', name='張麗', salary=10000.0, sex='female'}
Employee{id='1004', name='謝楠', salary=11000.0, sex='female'}
Employee{id='1005', name='Marry', salary=5000.0, sex='male'}
Employee{id='1006', name='Linda', salary=7000.0, sex='male'}
Employee{id='1007', name='Cris', salary=10000.0, sex='female'}
3.3、排序
| 方法 | 描述 |
| sorted() | 產生一個新流,其中按自然順序排序 |
| sorted(Comparator comp) | 產生一個新流,其中按比較器順序排序 |
Employee[] employees = { new Employee("1001", "李明", 5000, "male"), new Employee("1002", "王明", 7000, "male"), new Employee("1003", "張麗", 10000, "female"), new Employee("1005", "Marry", 6000, "male"), new Employee("1006", "Linda", 9000, "male"), new Employee("1007", "Cris", 10000, "female"), new Employee("1004", "謝楠", 11000, "female") }; List<Employee> list = Arrays.asList(employees); list.stream().map(e -> e.getSalary()).sorted().forEach(e -> System.out.println(e.toString())); System.out.println("======="); Comparator<String> comparator = (String o1, String o2) -> o1.compareTo(o2); list.stream().map(e -> e.getId()).sorted(comparator).forEach(e -> System.out.println(e.toString()));
執行結果:
5000.0 6000.0 7000.0 9000.0 10000.0 10000.0 11000.0 ======= 1001 1002 1003 1004 1005 1006 1007
3.4、查找與匹配
| 方法 | 描述 |
| allMatch(Predicate p) | 檢查是否匹配所有元素 |
| anyMatch(Predicate p) | 檢查是否至少匹配一個元素 |
| noneMatch(Predicate p) | 檢查是否沒有匹配所有元素 |
| findFirst() | 返回第一個元素 |
| findAny() | 返回當前流中的任意元素 |
| count() | 返回流中元素總數 |
| max(Comparator c) | 返回流中最大值 |
| min(Comparator c) | 返回流中最小值 |
| forEach(Consumer c) | 內部迭代(使用 Collection 接口需要用戶去做迭代,稱為外部迭代。相反,Stream API 使用內部迭代——它幫你把迭代做了) |
Employee[] employees = { new Employee("1001", "李明", 5000, "male"), new Employee("1002", "王明", 7000, "male"), new Employee("1003", "張麗", 10000, "female"), new Employee("1005", "Marry", 6000, "male"), new Employee("1006", "Linda", 9000, "male"), new Employee("1007", "Cris", 10000, "female"), new Employee("1004", "謝楠", 11000, "female") }; List<Employee> list = Arrays.asList(employees); System.out.println("===allMatch==="); Boolean b1 = list.stream().allMatch(e -> e.getSalary() > 4000); Boolean b2 = list.stream().allMatch(e -> e.getSalary() > 5000); System.out.println(b1); System.out.println(b2); System.out.println("===anyMatch==="); Boolean b3 = list.stream().anyMatch(e -> e.getSalary() > 10000); Boolean b4 = list.stream().anyMatch(e -> e.getSalary() > 11000); System.out.println(b3); System.out.println(b4); System.out.println("===noneMatch==="); Boolean b5 = list.stream().noneMatch(e -> e.getSalary() > 10000); Boolean b6 = list.stream().noneMatch(e -> e.getSalary() > 11000); System.out.println(b5); System.out.println(b6); System.out.println("===findFirst==="); System.out.println(list.stream().findFirst().toString()); System.out.println("===findAny==="); System.out.println(list.stream().findAny().toString()); System.out.println("===count==="); System.out.println(list.stream().count()); System.out.println("===max==="); System.out.println(list.stream().max((Employee o1, Employee o2) -> { if (o1.getSalary() > o2.getSalary()) return 1; else return -1; }).toString()); System.out.println("===min==="); System.out.println(list.stream().min((Employee o1, Employee o2) -> { if (o1.getSalary() > o2.getSalary()) return 1; else return -1; }).toString());
執行結果:
===allMatch=== true false ===anyMatch=== true false ===noneMatch=== false true ===findFirst=== Optional[Employee{id='1001', name='李明', salary=5000.0, sex='male'}] ===findAny=== Optional[Employee{id='1001', name='李明', salary=5000.0, sex='male'}] ===count=== 7 ===max=== Optional[Employee{id='1004', name='謝楠', salary=11000.0, sex='female'}] ===min=== Optional[Employee{id='1001', name='李明', salary=5000.0, sex='male'}]
3.5、歸約
map-reduce模式,在mongoDB、spark、hadoop等都有它的身影,因google使用它進行網絡搜索而出名
| 方法 | 描述 |
| reduce(T iden, BinaryOperator b) | 可以將流中元素反復結合起來,得到一個值。返回T |
| reduce(BinaryOperator b) | 可以將流中元素反復結合起來,得到一個值。返回Optional<T> |
Employee[] employees = { new Employee("1001", "李明", 5000, "male"), new Employee("1002", "王明", 7000, "male"), new Employee("1003", "張麗", 10000, "female"), new Employee("1005", "Marry", 6000, "male"), new Employee("1006", "Linda", 9000, "male"), new Employee("1007", "Cris", 10000, "female"), new Employee("1004", "謝楠", 11000, "female") }; List<Employee> list = Arrays.asList(employees); String result1 = list.stream().map(e -> e.getName()).reduce("", (x, y) -> x + "," + y); System.out.println(result1.substring(1)); Optional<String> result2 = list.stream().map(e -> e.getName()).reduce((x, y) -> x + "," + y); System.out.println(result2.get());
執行結果:
李明,王明,張麗,Marry,Linda,Cris,謝楠
李明,王明,張麗,Marry,Linda,Cris,謝楠
3.6、收集
| 方法 | 描述 |
| collect(Collector c) | 將流轉換為其他形式。接收一個Collector接口的實現,用於給Stream中元素做匯總的方法 |
Employee[] employees = { new Employee("1001", "李明", 5000, "male"), new Employee("1002", "王明", 7000, "male"), new Employee("1003", "張麗", 10000, "female"), new Employee("1005", "Marry", 6000, "male"), new Employee("1006", "Linda", 9000, "male"), new Employee("1007", "Cris", 10000, "female"), new Employee("1004", "謝楠", 11000, "female") }; List<Employee> list = Arrays.asList(employees); List<String> collect = list.stream().map(e -> e.getName()).collect(Collectors.toList()); collect.forEach(System.out::println); System.out.println("-------------------"); Set<String> set = list.stream() .map(e -> e.getName()) .collect(Collectors.toSet()); set.forEach(System.out::println); System.out.println("-------------------"); HashSet<String> hashSet = list.stream() .map(e -> e.getName()) .collect(Collectors.toCollection(HashSet::new)); hashSet.forEach(System.out::println); System.out.println("-------------------"); //分組 Map<Double, List<Employee>> group = list.stream() .collect(Collectors.groupingBy(e -> e.getSalary())); System.out.println(group);
執行結果:
李明 王明 張麗 Marry Linda Cris 謝楠 ------------------- 張麗 Cris 李明 王明 Linda Marry 謝楠 ------------------- 張麗 Cris 李明 王明 Linda Marry 謝楠 {9000.0=[Employee{id='1006', name='Linda', salary=9000.0, sex='male'}], 10000.0=[Employee{id='1003', name='張麗', salary=10000.0, sex='female'}, Employee{id='1007', name='Cris', salary=10000.0, sex='female'}], 5000.0=[Employee{id='1001', name='李明', salary=5000.0, sex='male'}], 11000.0=[Employee{id='1004', name='謝楠', salary=11000.0, sex='female'}], 6000.0=[Employee{id='1005', name='Marry', salary=6000.0, sex='male'}], 7000.0=[Employee{id='1002', name='王明', salary=7000.0, sex='male'}]}
以上內容只是對lambda表達式和stream的簡單介紹,在實際使用中要復雜的多
