寫在前面
這是一道真實的面試題,一個讀者朋友出去面試,面試官竟然問他這樣一個問題:你說說Java8中為什么引入Lambda表達式?引入Lambda表達式后有哪些好處呢?還好這個朋友對Java8早有准備。不過,如果是看文章的你出去面試,面試官問你這樣的問題,你是否也能輕松回答呢?
什么是Lambda表達式?
Lambda表達式是一個匿名函數,我們可以這樣理解Lambda表達式:Lambda是一段可以傳遞的代碼(能夠做到將代碼像數據一樣進行傳遞)。使用Lambda表達式能夠寫出更加簡潔、靈活的代碼。並且,使用Lambda表達式能夠使Java的語言表達能力得到提升。
匿名內部類
在介紹如何使用Lambda表達式之前,我們先來看看匿名內部類,例如,我們使用匿名內部類比較兩個Integer類型數據的大小。
Comparator<Integer> com = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1, o2);
}
};
在上述代碼中,我們使用匿名內部類實現了比較兩個Integer類型數據的大小。
接下來,我們就可以將上述匿名內部類的實例作為參數,傳遞到其他方法中了,如下所示。
TreeSet<Integer> treeSet = new TreeSet<>(com);
完整的代碼如下所示。
@Test
public void test1(){
Comparator<Integer> com = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1, o2);
}
};
TreeSet<Integer> treeSet = new TreeSet<>(com);
}
我們分析下上述代碼,在整個匿名內部類中,實際上真正有用的就是下面一行代碼。
return Integer.compare(o1, o2);
其他的代碼本質上都是“冗余”的。但是為了書寫上面的一行代碼,我們不得不在匿名內部類中書寫更多的代碼。
Lambda表達式
如果使用Lambda表達式完成兩個Integer類型數據的比較,我們該如何實現呢?
Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
看到沒,使用Lambda表達式,我們只需要使用一行代碼就能夠實現兩個Integer類型數據的比較。
我們也可以將Lambda表達式傳遞到TreeSet的構造方法中,如下所示。
TreeSet<Integer> treeSet = new TreeSet<>((x, y) -> Integer.compare(x, y));
直觀的感受就是使用Lambda表達式一行代碼就能搞定匿名內部類多行代碼的功能。
看到這,不少讀者會問:我使用匿名內部類的方式實現比較兩個整數類型的數據大小並不復雜啊!我為啥還要學習一種新的語法呢?
其實,我想說的是:上面咱們只是簡單的列舉了一個示例,接下來,咱們寫一個稍微復雜一點的例子,來對比下使用匿名內部類與Lambda表達式哪種方式更加簡潔。
對比常規方法和Lambda表達式
例如,現在有這樣一個需求:獲取當前公司中員工年齡大於30歲的員工信息。
首先,我們需要創建一個Employee實體類來存儲員工的信息。
@Data
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Employee implements Serializable {
private static final long serialVersionUID = -9079722457749166858L;
private String name;
private Integer age;
private Double salary;
}
在Employee中,我們簡單存儲了員工的姓名、年齡和薪資。
接下來,我們創建一個存儲多個員工的List集合,如下所示。
protected List<Employee> employees = Arrays.asList(
new Employee("張三", 18, 9999.99),
new Employee("李四", 38, 5555.55),
new Employee("王五", 60, 6666.66),
new Employee("趙六", 16, 7777.77),
new Employee("田七", 18, 3333.33)
);
1.常規遍歷集合
我們先使用常規遍歷集合的方式來查找年齡大於等於30的員工信息。
public List<Employee> filterEmployeesByAge(List<Employee> list){
List<Employee> employees = new ArrayList<>();
for(Employee e : list){
if(e.getAge() >= 30){
employees.add(e);
}
}
return employees;
}
接下來,我們測試一下上面的方法。
@Test
public void test3(){
List<Employee> employeeList = filterEmployeesByAge(this.employees);
for (Employee e : employeeList){
System.out.println(e);
}
}
運行test3方法,輸出信息如下所示。
Employee(name=李四, age=38, salary=5555.55)
Employee(name=王五, age=60, salary=6666.66)
總體來說,查找年齡大於或者等於30的員工信息,使用常規遍歷集合的方式稍顯復雜了。
例如,需求發生了變化:獲取當前公司中員工工資大於或者等於5000的員工信息。
此時,我們不得不再次創建一個按照工資過濾的方法。
public List<Employee> filterEmployeesBySalary(List<Employee> list){
List<Employee> employees = new ArrayList<>();
for(Employee e : list){
if(e.getSalary() >= 5000){
employees.add(e);
}
}
return employees;
}
對比filterEmployeesByAge()方法和filterEmployeesBySalary方法后,我們發現,大部分的方法體是相同的,只是for循環中對於條件的判斷不同。
如果此時我們再來一個需求,查找當前公司中年齡小於或者等於20的員工信息,那我們又要創建一個過濾方法了。 看來使用常規方法是真的不方便啊!
這里,問大家一個問題:對於這種常規方法最好的優化方式是啥?相信有不少小伙伴會說:將公用的方法抽取出來。沒錯,將公用的方法抽取出來是一種優化方式,但它不是最好的方式。最好的方式是啥?那就是使用 設計模式 啊!設計模式可是無數前輩不斷實踐而總結出的設計原則和設計模式。大家可以查看《設計模式匯總——你需要掌握的23種設計模式都在這兒了!》一文來學習設計模式專題。
2.使用設計模式優化代碼
如何使用設計模式來優化上面的方法呢,大家繼續往下看,對於設計模式不熟悉的同學可以先根據《設計模式匯總——你需要掌握的23種設計模式都在這兒了!》來學習。
我們先定義一個泛型接口MyPredicate,對傳遞過來的數據進行過濾,符合規則返回true,不符合規則返回false。
public interface MyPredicate<T> {
/**
* 對傳遞過來的T類型的數據進行過濾
* 符合規則返回true,不符合規則返回false
*/
boolean filter(T t);
}
接下來,我們創建MyPredicate接口的實現類FilterEmployeeByAge來過濾年齡大於或者等於30的員工信息。
public class FilterEmployeeByAge implements MyPredicate<Employee> {
@Override
public boolean filter(Employee employee) {
return employee.getAge() >= 30;
}
}
我們定義一個過濾員工信息的方法,此時傳遞的參數不僅有員工的信息集合,同時還有一個我們定義的接口實例,在遍歷員工集合時將符合過濾條件的員工信息返回。
//優化方式一
public List<Employee> filterEmployee(List<Employee> list, MyPredicate<Employee> myPredicate){
List<Employee> employees = new ArrayList<>();
for(Employee e : list){
if(myPredicate.filter(e)){
employees.add(e);
}
}
return employees;
}
接下來,我們寫一個測試方法來測試優化后的代碼。
@Test
public void test4(){
List<Employee> employeeList = this.filterEmployee(this.employees, new FilterEmployeeByAge());
for (Employee e : employeeList){
System.out.println(e);
}
}
運行test4()方法,輸出的結果信息如下所示。
Employee(name=李四, age=38, salary=5555.55)
Employee(name=王五, age=60, salary=6666.66)
寫到這里,大家是否有一種豁然開朗的感覺呢?
沒錯,這就是設計模式的魅力,對於設計模式不熟悉的小伙伴,一定要參照《設計模式匯總——你需要掌握的23種設計模式都在這兒了!》來學習。
我們繼續獲取當前公司中工資大於或者等於5000的員工信息,此時,我們只需要創建一個FilterEmployeeBySalary類實現MyPredicate接口,如下所示。
public class FilterEmployeeBySalary implements MyPredicate<Employee>{
@Override
public boolean filter(Employee employee) {
return employee.getSalary() >= 5000;
}
}
接下來,就可以直接寫測試方法了,在測試方法中繼續調用filterEmployee(List<Employee> list, MyPredicate<Employee> myPredicate)
方法。
@Test
public void test5(){
List<Employee> employeeList = this.filterEmployee(this.employees, new FilterEmployeeBySalary());
for (Employee e : employeeList){
System.out.println(e);
}
}
運行test5方法,輸出的結果信息如下所示。
Employee(name=張三, age=18, salary=9999.99)
Employee(name=李四, age=38, salary=5555.55)
Employee(name=王五, age=60, salary=6666.66)
Employee(name=趙六, age=16, salary=7777.77)
可以看到,使用設計模式對代碼進行優化后,無論過濾員工信息的需求如何變化,我們只需要創建MyPredicate接口的實現類來實現具體的過濾邏輯,然后在測試方法中調用filterEmployee(List<Employee> list, MyPredicate<Employee> myPredicate)
方法將員工集合和過濾規則傳入即可。
這里,問大家一個問題:上面優化代碼使用的設計模式是哪種設計模式呢?如果是你,你會想到使用設計模式來優化自己的代碼嗎?小伙伴們自己先思考一下到底使用的設計模式是什么?文末我會給出答案!
使用設計模式優化代碼也有不好的地方:每次定義一個過濾策略的時候,我們都要單獨創建一個過濾類!!
3.匿名內部類
那使用匿名內部類是不是能夠優化我們書寫的代碼呢,接下來,我們就使用匿名內部類來實現對員工信息的過濾。先來看過濾年齡大於或者等於30的員工信息。
@Test
public void test6(){
List<Employee> employeeList = this.filterEmployee(this.employees, new MyPredicate<Employee>() {
@Override
public boolean filter(Employee employee) {
return employee.getAge() >= 30;
}
});
for (Employee e : employeeList){
System.out.println(e);
}
}
運行test6方法,輸出如下結果信息。
Employee(name=李四, age=38, salary=5555.55)
Employee(name=王五, age=60, salary=6666.66)
再實現過濾工資大於或者等於5000的員工信息,如下所示。
@Test
public void test7(){
List<Employee> employeeList = this.filterEmployee(this.employees, new MyPredicate<Employee>() {
@Override
public boolean filter(Employee employee) {
return employee.getSalary() >= 5000;
}
});
for (Employee e : employeeList){
System.out.println(e);
}
}
運行test7方法,輸出如下結果信息。
Employee(name=張三, age=18, salary=9999.99)
Employee(name=李四, age=38, salary=5555.55)
Employee(name=王五, age=60, salary=6666.66)
Employee(name=趙六, age=16, salary=7777.77)
匿名內部類看起來比常規遍歷集合的方法要簡單些,並且將使用設計模式優化代碼時,每次創建一個類來實現過濾規則寫到了匿名內部類中,使得代碼進一步簡化了。
但是,使用匿名內部類代碼的可讀性不高,並且冗余代碼也比較多!!
那還有沒有更加簡化的方式呢?
4.重頭戲:Lambda表達式
在使用Lambda表達式時,我們還是要調用之前寫的filterEmployee(List<Employee> list, MyPredicate<Employee> myPredicate)
方法。
注意看,獲取年齡大於或者等於30的員工信息。
@Test
public void test8(){
filterEmployee(this.employees, (e) -> e.getAge() >= 30).forEach(System.out::println);
}
看到沒,使用Lambda表達式只需要一行代碼就完成了員工信息的過濾和輸出。是不是很6呢。
運行test8方法,輸出如下的結果信息。
Employee(name=李四, age=38, salary=5555.55)
Employee(name=王五, age=60, salary=6666.66)
再來看使用Lambda表達式來獲取工資大於或者等於5000的員工信息,如下所示。
@Test
public void test9(){
filterEmployee(this.employees, (e) -> e.getSalary() >= 5000).forEach(System.out::println);
}
沒錯,使用Lambda表達式,又是一行代碼就搞定了!!
運行test9方法,輸出如下的結果信息。
Employee(name=張三, age=18, salary=9999.99)
Employee(name=李四, age=38, salary=5555.55)
Employee(name=王五, age=60, salary=6666.66)
Employee(name=趙六, age=16, salary=7777.77)
另外,使用Lambda表達式時,只需要給出需要過濾的集合,我們就能夠實現從集合中過濾指定規則的元素,並輸出結果信息。
5.重頭戲:Stream API
使用Lambda表達式結合Stream API,只要給出相應的集合,我們就可以完成對集合的各種過濾並輸出結果信息。
例如,此時只要有一個employees
集合,我們使用Lambda表達式來獲取工資大於或者等於5000的員工信息。
@Test
public void test10(){
employees.stream().filter((e) -> e.getSalary() >= 5000).forEach(System.out::println);
}
沒錯,只給出一個集合,使用Lambda表達式和Stream API,一行代碼就能夠過濾出想要的元素並進行輸出。
運行test10方法,輸出如下的結果信息。
Employee(name=張三, age=18, salary=9999.99)
Employee(name=李四, age=38, salary=5555.55)
Employee(name=王五, age=60, salary=6666.66)
Employee(name=趙六, age=16, salary=7777.77)
如果我們只想要獲取前兩個員工的信息呢?其實也很簡單,如下所示。
@Test
public void test11(){
employees.stream().filter((e) -> e.getSalary() >= 5000).limit(2).forEach(System.out::println);
}
可以看到,我們在代碼中添加了limit(2)
來限制只獲取兩個員工信息。運行test11方法,輸出如下的結果信息。
Employee(name=張三, age=18, salary=9999.99)
Employee(name=李四, age=38, salary=5555.55)
使用Lambda表達式和Stream API也可以獲取指定的字段信息,例如獲取工資大於或者等於5000的員工姓名。
@Test
public void test12(){
employees.stream().filter((e) -> e.getSalary() >= 5000).map(Employee::getName).forEach(System.out::println);
}
可以看到,使用map過濾出了工資大於或者等於5000的員工姓名。運行test12方法,輸出如下的結果信息。
張三
李四
王五
趙六
是不是很簡單呢?
最后,給出文中使用的設計模式:策略模式。
寫在最后
如果覺得文章對你有點幫助,請微信搜索並關注「 冰河技術 」微信公眾號,跟冰河學習Java8新特性。
最后,附上Java8新特性核心知識圖,祝大家在學習Java8新特性時少走彎路。