一、Lambda表達式引入
這裏以對人進行篩選的需求一步步變化來引入lambda
1. 數據準備
定義數據結構
package com.firewolf.java8.s001.lambda.leadin;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 作者:劉興 時間:2019-10-28
**/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Person {
/**
* 顏色
*/
private String color;
/**
* 年齡
*/
private Integer age;
}
準備初始數據集合
private List<Person> personList = null;
@Before
public void init() {
personList = Arrays.asList(
new Person("yellow", 30),
new Person("black", 50),
new Person("black", 35),
new Person("white", 45)
);
}
2. 固定條件篩選
我們有一個需求,就是需要找出所有的黑種人,方法如下:
public static List<Person> filterBlackPerson(List<Person> personList) {
List<Person> result = new ArrayList<>();
for (Person person : personList) {
if ("black".equals(person.getColor())) {
result.add(person);
}
}
return result;
}
通過這個方法就可以找出黑種人來
3.抽取變化屬性
在2中的代碼能夠找出黑種人,但是接下來,我們需要查找黃種人,這個時候,上面的代碼就不能滿足了,所以,我們可以如下寫出根據顏色查找的方法
public static List<Person> filterPersonByColor(List<Person> personList, String color) {
List<Person> result = new ArrayList<>();
for (Person person : personList) {
if (color.equals(person.getColor())) {
result.add(person);
}
}
return result;
}
4. 其他屬性的處理
接下來,我們需要找出年齡大於某個年齡的人,可以提供如下方法
public static List<Person> filterPersonsByAge(List<Person> personList, int age) {
List<Person> result = new ArrayList<>();
for (Person person : personList) {
if (person.getAge() > age) {
result.add(person);
}
}
return result;
}
5. 一個方法兼容多個屬性
我們看到,上面根據顏色和年齡分別查找的時候,需要調用不同的方法,那麼,能不能統一到一個方法呢?答案是可以的,如下:
public static List<Person> filterPersons(List<Person> personList, String color, Integer age, Integer filteType) {
List<Person> result = new ArrayList<Person>();
for (Person person : personList) {
if ((filteType==1 && person.getColor().equals(color)) || (filteType==2 && person.getAge() > age)) {
result.add(person);
}
}
return result;
}
我們看到,可以通過一個filteType來區分是根據什麼來進行查找
6. 抽象過濾接口
如果我們的人員再增加一個屬性,需要怎麼處理呢?很容易想到的是,我們在上面接口的基礎上在添加分支。然而,如果判斷條件發生變化呢?比如年齡是小於,而不是大於怎麼辦?
解決辦法如下:
- 定義一個接口,用於判斷是否滿足條件
public interface PersonPredicate {
boolean test(Person person);
}
- 接下來,我們可以根據自己的需求定義實現,如:
/**
* 根據膚色過濾器
*/
public static class PersonColorPredicate implements PersonPredicate {
private String color;
public PersonColorPredicate(String color) {
this.color = color;
}
public boolean test(Person person) {
return color.equals(person.getColor());
}
}
/**
* 根據給定的選擇器選擇人員信息
*/
public static List<Person> filterPersons(List<Person> personList,
PersonPredicate predicate) {
List<Person> result = new ArrayList<>();
for (Person person : personList) {
if (predicate.test(person)) {
result.add(person);
}
}
return result;
}
接下來,如果我們需要根據顏色來判斷,可以如下實現:
List<Person> persons1 = PersonUtil.filterPersons(personList, new PersonColorPredicate("yellow"));
System.out.println(persons1);
根據年齡,可以如下調用:
List<Person> persons2 = PersonUtil.filterPersons(personList, new PersonAgePredicate(36));
System.out.println(persons2);
7. 匿名內部類
上面的代碼結構已經比較清晰,然而,每增加一種過濾方式,就需要創建這麼一個類,如果某個過濾器只是使用一次,那麼還需要這麼定義的話,會造成很多類的複用性不高,同時增加了類的數量,此時,我們可以通過內部類來解決。
List<Person> persons = PersonUtil.filterPersons(personList, new PersonPredicate() {
@Override
public boolean test(Person person) {
return person.getAge() > 40 && "black".equals(person.getColor());
}
});
System.out.println(persons);
8. lambda表達式
上面的匿名內部類的方式,會讓我們能夠很靈活的進行處理,然而,會編寫一些無用代碼,比如方法的聲明等等,有沒有更簡單的辦法呢?答案是lambda表達式, 寫法如下:
List<Person> persons = PersonUtil.filterPersons(personList, person -> person.getAge() > 40 && "black".equals(person.getColor()));
System.out.println(persons);
這種寫法是不是非常簡單,也許你現在不太看得懂,不過不要緊,我後面會一步一步展開講解
二、什麼是Lambda表達式
Lambda 是一個匿名函數,我們可以把 Lambda 表達式理解爲是一段可以傳遞的代碼(將代碼 像數據一樣進行傳遞)。可以寫出更簡潔、更 靈活的代碼。作爲一種更緊湊的代碼風格,使 Java的語言表達能力得到了提升。
例如:
一個Runnable類型的對象創建方式的演變:
java7中使用匿名內部類:
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("hello,world");
}
};
Lambda寫法如下:
Runnable r2 = ()-> System.out.println("Hello,World");
可以很明顯的看到,使用Lambda表達式的方式會讓代碼非常的簡潔。
lambda特點
- 匿名——我們說匿名,是因爲它不像普通的方法那樣有一個明確的名稱:寫得少而想得多!
- 函數——我們說它是函數,是因爲Lambda函數不像方法那樣屬於某個特定的類。但和方 法一樣,Lambda有參數列表、函數主體、返回類型,還可能有可以拋出的異常列表。
- 傳遞——Lambda表達式可以作爲參數傳遞給方法或存儲在變量中。 簡潔——無需像匿名類那樣寫很多模板代碼
三、Lambda語法
Lambda表達式的格式爲: 函數式接口 = 參數列表 -> Lambda體
,
參數列表:就好比反覆 add(int a , int b) ,那麼int a, int b 就是參數列表;
Lambda體:可以理解爲函數體
函數式接口:這個在下一章詳細講解
(一) "->"操作符
- 在Java8中引入了一個新的操作符號“->”,該操作符被稱爲箭頭操作符或者是Lambda操作符
- 左側:Lambda表達式的參數列表
- 右側:表達式所需要執行的功能,稱爲Lambda體。
(二)幾種語法形式
- 語法格式一:無參數,無返回值
public void test1() { Runnable r = () -> System.out.println("Hello,Lambda"); r.run(); }
- 語法格式二:有一個參數,沒有返回值
public void test2() { Consumer<String> c = (x) -> System.out.println("Hello," + x); c.accept("Baby"); }
- 語法格式三:當只有一個參數的時候,可以省略掉參數的小括號
public void test3() { Consumer<String> c = x -> System.out.println("Hello," + x); c.accept("Test3"); }
- 語法格式四:有兩個及以上個數參數,並且有返回值,需要使用{}把lambda體包起來
public void test3() { Consumer<String> c = x -> System.out.println("Hello," + x); c.accept("Test3"); }
- 語法格式五:有返回值,但是lambda體只有一條語句時,可以省略{}和return
public void test5() { BinaryOperator<Long> op = (x, y) -> x + y; }
- 語法格式六:可以指明參數類型,默認情況下,可以不指定。注意的是,如果要指明,就都得指明
public void test6() { BinaryOperator<Integer> op = (Integer x, Integer y) -> x + y; }
四、Lambda類型推斷
- 在Lambda表達式中的參數類型可以由編譯器推斷得出的。Lambda 表達式中無需指定類型,程序依然可以編譯;
- 這是因爲 javac 根據程序的上下文,在後臺 推斷出了參數的類型
- Lambda 表達式的類型依賴於上 下文環境,是由編譯器推斷出來的。這就是所謂的 “類型推斷”
五、注意事項
在Java7之前,匿名內部類如果訪問了同級的變量,那麼需要把這個變量使用final修飾,而在lambda中如果引用了同級別的變量,不需要用final修飾,但是不能改變其值,其實就是Java8內部自己加上的final修飾符,不用咱們自己顯示添加