本文將簡單的介紹一下Lambda表達式和方法引用,這也是Java8的重要更新,Lambda表達式和方法引用最主要的功能是為流(專門負責迭代數據的集合)服務.
什么是lambda表達式
可以把lambda表達式理解為簡潔的匿名函數.
我們先聲明一個函數式接口(函數式接口:就是只有一個抽象方法的接口. lambda表達式和方法引用,只能用在函數式接口上),比較一下lambda表達式和匿名函數
public interface Animal { void cry(); public static void main(String [] args){ Animal dog = new Animal() { @Override public void cry() { System.out.println("狗: 汪汪叫"); } }; dog.cry();
Animal cat = () -> System.out.println("貓: 喵喵叫"); cat.cry(); } }
一個Animal的接口,里面只有一個cry()的抽象方法, 分別用匿名函數和lambda表達式去實現這個接口. 使用lambda表達式的方法非常的簡潔,只需要一行.
lambda表達式語法: 參數 -> 具體的實現.
函數式接口的方法叫做函數描述符,lambda表達式的參數和實現必須和函數描述符的參數和返回值一一對應.cry()方法的參數和返回值都沒有所以lambda表達式就是 () -> System.out.println("貓: 喵喵叫");
如果實現有多條語句的話,要寫在{}中,並且以;結尾.
() -> {
xxx;
yyy;
return "ccc";
}
列舉一個高端一點的使用lambda表達式的方法.以Oracle的Emp(員工表)為例.
表結構
public class Emp { private BigDecimal empno; private String ename; private String job; private BigDecimal mgr; private Date hiredate; private Double sal; private BigDecimal comm; private BigDecimal deptno; public BigDecimal getEmpno() { return empno; } public void setEmpno(BigDecimal empno) { this.empno = empno; } public String getEname() { return ename; } public void setEname(String ename) { this.ename = ename == null ? null : ename.trim(); } public String getJob() { return job; } public void setJob(String job) { this.job = job == null ? null : job.trim(); } public BigDecimal getMgr() { return mgr; } public void setMgr(BigDecimal mgr) { this.mgr = mgr; } public Date getHiredate() { return hiredate; } public void setHiredate(Date hiredate) { this.hiredate = hiredate; } public Double getSal() { return sal; } public void setSal(Double sal) { this.sal = sal; } public BigDecimal getComm() { return comm; } public void setComm(BigDecimal comm) { this.comm = comm; } public BigDecimal getDeptno() { return deptno; } public void setDeptno(BigDecimal deptno) { this.deptno = deptno; } }
現在我們要寫一個方法,過濾所有工資在3000以上的員工(可能有的人可能會想,我直接寫sql不得了,費這么多勁干什么,所以我們以下的測試都假設數據是從redis查詢出來的.需要手動寫過濾條件)
public List<Emp> filter(List<Emp> listEmp){ List<Emp> filterList = new ArrayList<>(); for (Emp emp :listEmp) { if (emp.getSal()>3000){ filterList.add(emp); } } return filterList; }
這么寫的壞處是條件硬編碼,如果光是改工資,我們可以把3000抽取為一個參數,但是如果要將條件改為小於呢,如果過濾的是員工的工作呢.可能新手就會進行復制粘貼改一改條件,但是當重復的代碼達到一定的數量時,維護起來就是個災難.
我們看看Java8提供的函數式編程,可以怎么解決這個方法.(當然使用匿名函數也可以,但是不夠簡潔).
把變化的的條件抽取出去,變為一個參數Predicate.具體的實現就是實現這個接口的test方法.
public List<Emp> filter1(List<Emp> listEmp, Predicate<Emp> predicate){ List<Emp> filterList = new ArrayList<>(); for (Emp emp :listEmp) { if (predicate.test(emp)){ filterList.add(emp); } } return filterList; }
我們利用了java.util.function這個包提供的Predicate接口.這就是一個標准的函數式接口
測試一下我們寫的過濾方法,分別按照工資和工作名稱進行過濾
1 List<Emp> filterSalEmp = empService.filter1(listEmp, Emp emp -> emp.getSal() > 3000); 2 List<Emp> filterJobEmp = empService.filter1(listEmp, Emp emp -> "SALMAN".equals(emp.getJob()));
Predicate接口的方法 boolean test(T t); 返回值是Boolean類型的,參數是任意類型 我們的實現 Emp emp -> emp.getSal() > 3000 參數Emp ,返回Boolean類型的值 emp.getSal() > 3000 完全滿足. 可以看到使用函數式接口編程提高了代碼的靈活性和可重用性.
其實lambda表達式的類型是可以從上下文中自己推斷出來的,也就是說 上面的 lambda的參數 Emp emp 可以不帶參數類型.寫成下面這樣
1 List<Emp> filterSalEmp = empService.filter1(listEmp, emp -> emp.getSal() > 3000); 2 List<Emp> filterJobEmp = empService.filter1(listEmp, emp -> "SALMAN".equals(emp.getJob()));
lambda表達式使用局部變量
回到之前的例子:
String catCry = "貓: 喵喵叫";
Animal cat = () -> System.out.println(catCry);
cat.cry();
打印輸出:貓: 喵喵叫
lambda表達式可以使用局部變量,但是必須是final類型的或事實上final類型的(不可改變).
<<java8實戰>>中的解釋:
第一,實例變量和局部變量背后的實現有一
個關鍵不同。實例變量都存儲在堆中,而局部變量則保存在棧上。如果Lambda可以直接訪問局
部變量,而且Lambda是在一個線程中使用的,則使用Lambda的線程,可能會在分配該變量的線
程將這個變量收回之后,去訪問該變量。因此,Java在訪問自由局部變量時,實際上是在訪問它
的副本,而不是訪問原始變量。如果局部變量僅僅賦值一次那就沒有什么區別了——因此就有了
這個限制。
第二,這一限制不鼓勵你使用改變外部變量的典型命令式編程模式(我們會在以后的各章中
解釋,這種模式會阻礙很容易做到的並行處理)。
方法引用
方法引用可以理解為lambda表達式的快捷寫法,它比lambda表達式更加的簡潔,可讀性更高.有更好的重用性.如果實現比較簡單,一句話就可以實現,復用的地方又不多推薦使用lambda表達式,否則應該使用方法引用.
方法引用的格式 類名::方法名
我們使用方法引用的方式,重新實現上面剛剛過濾員工表的例子.
定義兩個條件類,方法的參數和返回值定義的和predicate的函數名描述符一致
public class EmpConditionA { public static boolean test(Emp emp) { return emp.getSal() > 3000; } }
public class EmpConditionB{
public static boolean test(Emp emp) {
return "engineer".equals(emp.getJob());
}
}
實現方式: 使用類名::方法的方式
List<Emp> listEmp = empService.listEmp(); List<Emp> filterSalEmp = empService.filter1(listEmp, EmpConditionA::test); List<Emp> filterJobEmp = empService.filter1(listEmp, EmpConditionB::test);
因為這個方法調用的是第三方類的方法所以是static的
還有兩種調用方式: 一種是直接調用流中的實例的方式,還有一種是調用局部變量的方式.
直接調用流中的實例的方式: 注意下面的Emp::getJob 就相當於集合中每一個emp對象都調用自己的getJob方法.
這個例子是講將集合轉換為流,map()方法可以理解為對集合的每一個元素進行相應的操作,這里就是對每一個emp實例調用getJob方法.最后.collect(Collectors.toList())將流轉換為新的list集合(關於流,筆者后面會繼續更新相關的博客).
listEmp.stream().map(Emp::getJob).collect(Collectors.toList());
調用局部變量的方式: 創建條件EmpconditionA的實例
EmpConditionA empConditionA = new EmpConditionA(); List<Emp> filterSalEmp = empService.filter1(listEmp, empConditionA::test);
好了關於lambda表達式和方法引用就簡單的介紹到這里,
限於篇幅有些地方介紹的不是很詳細,如果有疑問歡迎大家隨時提問.