java8之lambda表達式&方法引用(一)


本文將簡單的介紹一下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表達式和方法引用就簡單的介紹到這里,

限於篇幅有些地方介紹的不是很詳細,如果有疑問歡迎大家隨時提問.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM