1. 程式人生 > >lambda表示式&方法引用

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表示式和方法引用就簡單的介紹到這裡,

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