天天看點

Lambda表達式應用淺析

一、什麼是Lambda表達式

最近研究了一下java8的一個新特性lambda表達式,發現使用lambda表達式确實能夠簡化我們的代碼是的代碼的邏輯更加清晰和簡潔。以下是我整理的關于lambda表達式的知識點,在這裡分享給大家,如有寫的不對的地方請指出。

Lambda表達式是Java8的一個新特性,它提供了一種更加清晰和簡明的方式使用Functional Interface。在代碼中經常會出現各種各樣的匿名内部類,使得代碼變的臃腫、混亂,而lambda就是一種優雅的方式,它簡化對于Functionla Interface的使用,而不需要定義匿名内部類。

二、知識背景

Annoymous Inner class

在Java裡面,可以在使用的時候聲明一個類的對象,并在使用時給出類的某些方法的實作,這種類叫做匿名内部類,比如在Android中的OnClickListener,代碼如下:

 如上代碼所示,new View.OnClickListener()就是一個匿名内部類。

Functional interface

功能接口,隻含有一個抽象方法的接口,即SAM(Simple Abstract Method),單一抽象方法。這樣的接口比如:

  • 檔案過濾接口-java.io.FileFilter    public abstract boolean accept(File pathname);
  • 比較接口-java.lang.Comarable    int compareTo(T another);
  • Android的OnClickListener接口-android.view.View.OnClickListener    onClick(View v);

三、Lambda表達式應用

了解了匿名内部類和功能接口下面來介紹一下lambda表達式。lambda是一種能夠極大的簡化聲明和使用功能性接口的表達式文法,通過使用lambda表達式,可以使得代碼更加的清晰和簡潔。

Lambda Syntax

每個lambda表達式都包括以下三個部分:

Argument List-(int x,int y)

Arrow Token -  ->

Body - x+y

Argument部分是調用SAM方法時的入口參數;Arrow Token固定為->;Body内部可以是簡單的表達式,也可是複雜的代碼塊。以上的例子僅僅是一個簡單的表達式,用來傳回輸入的x和y的和,如果參數不比對或者不是單一抽象接口都會出現編譯錯誤。

Lambda Examples

1. Runnable Lambda

使用匿名内部類代碼如下:

public static void testRunnable(){
    Thread thread = new Thread(new Runnable(){
        System.out.println(“In ”);
    });
}
           

使用lambda表達式代碼如下

public static void testRunnable(){
    Thread thread = new Thread(() -> {
        System.out.println(“In ”);
    });
}
           

2. FileFilter

使用匿名内部類代碼如下:

public static void testFileFilter(String path){
    File file = new File(path);
    FileFilter filter = new FileFilter(){
        public boolean accept(File pathName){
            return pathName.isDirectory();
        }
    };
    File[] files = file.listFiles(filter);
}
public static void testFileFilter(String path){
    File file = new File(path);
    file.listFiles((file)->file.isDirectory);
}
           

3.Comparator

private static void testComparator(){ 
    Person person1 = new Person(); 
    person1.setAge(10);
    Person person2 = new Person(); 
    person2.setAge(90); 
    Person person3 = new Person(); 
    person3.setAge(26); 
    List<Person> persons = new ArrayList<>(); 
    persons.add(person1); 
    persons.add(person2); 
    persons.add(person3); 
    Collections.sort(persons,(p1,p2) -> p1.getAge() - p2.getAge());
    for(Person p : persons){ 
         System.out.println(p.getAge()); 
    }
 }
           

    在這個例子當中,lambda作為Comparator參數傳入sort方法,有人可能會有疑問,Comparator接口中并非隻有一個simple abstract method ,即該接口不是剛才說過的Functional Interface。我們打開java.lang.Comparator接口,發現接口名字上有個标注@Functational Interface,進一步看這個标注的詳細介紹會發現,該接口是從java8開始加入的,目的是聲明某個接口為Functional Interface。如果該接口不是Functional Interface,即擁有超過一個抽象方法,則會抛出編譯錯誤。而這裡Comparator中還有一個抽象方法名為boolean equals(Object obj),從文檔中可以看出,如果某個接口擁有一個抽象方法,同時又覆寫了Object的public方法,那麼這個抽象方法并不會計算在該接口的抽象方法數量中。也就是說該接口還是一個 Functional Interface。

四、Lambda Advantages

    通過以上的幾個例子,大家對lambda已經有了一定的認識,lambda除了能夠簡化代碼行數之外,還能夠使得我們的代碼遵從Don’ Repeat Yourself(DRY)準則,提高代碼的可讀性和簡潔程度。

(1)情景:現在某公司年底需要給公司的員工發一些獎勵,獎勵程度和在公司工作的年數有關,規則如下:a.工作1年以内的-發放2000元;b.工作1-3年的-發放5000元;c.工作3-5年的-發放10000元;d.工作5年以上的-發放20000元。

(2)代碼初步實作,如下:

//定義實體類:Staff.java
public class Staff{
    private int staffId;
    private int workYears;
    private int name;
    public int getWorkYears(){
         return this.workYears;
    }
    public String getName(){
    
    }
    public void setWorkYears(int workYears){
        this.workYears = workYears;
    }
    public void setName(String name){
        this.name = name;
    }
}
//定義生産資料的靜态方法
public class StaffUtil{
    public static List<Staff> createStaffList(int count){
        List<Staff> staffList = new ArrayList<Staff>();
        for(int i=0;i<count;i++){
            Staff staff = new Staff();
            staff.setName(“Kevin”+i);
            staff.setWorkYears(i);
            staffList.add(staff);
        }
    }
}
//定義測試類
public class TestLambda{
    public static void  main(String[] args){
         List staffList = StaffUtil.createStaffList(20);
         filterA(staffList);
    }
    public static void filterA(List<Staff> staffList){
        for(Staff staff:staffList){
            if(staff.getWorkYears() <= 1){
                sendMoney(staff,2000);
            }
        }
    }
    public static void filterB(List<Staff> staffList){
        for(Staff staff:staffList){
            if(staff.getWorkYears() <= 3 && staff.getWorkYears() > 1){
                sendMoney(staff,5000);
            }
        }
    }
    public static void filterC(List<Staff> staffList){
        for(Staff staff:staffList){
            if(staff.getWorkYears() <= 5 && staff.getWorkYears() > 3){
                sendMoney(staff,10000);
            }
        }
    }
    public static void filterD(List<Staff> staffList){
        for(Staff staff:staffList){
            if(staff.getWorkYears() <= 5 && staff.getWorkYears() > 3){
                sendMoney(staff,10000);
            }
        }
    }
    public static void sendMoney(Staff staff,int money){
        System.out.println(“發放”+staff.getName()+”獎金”+money+”元”);
    }
}
           

以上代碼邏輯清晰,但是有些地方還需要繼續優化:

1.代碼違背了DRY準則,每個方法内部都會做一次循環

2.對于每種規則都會有一個方法與之對應,每種情況都需要實作一個方法與之對應

3.代碼不夠靈活,可擴充性不高“

(3)第一次修改

以下代碼我們嘗試将判斷條件抽出來,放到另一個單獨的方法中

public class TestLambda1{
    public static void  main(String[] args){
         List staffList = StaffUtil.createStaffList(20);
         filterA(staffList);
    }
    public static void filterA(List<Staff> staffList){
        for(Staff staff:staffList){
            if(isA(staff)){
                sendMoney(staff,2000);
            }
        }
    }
    public static void filterB(List<Staff> staffList){
        for(Staff staff:staffList){
            if(isB(staff)){
                sendMoney(staff,5000);
            }
        }
    }
    public static void filterC(List<Staff> staffList){
        for(Staff staff:staffList){
            if(isC(staff)){
                sendMoney(staff,10000);
            }
        }
    }
    public static void filterD(List<Staff> staffList){
        for(Staff staff:staffList){
            if(isD(staff)){
                sendMoney(staff,10000);
            }
        }
    }
    public static boolean isA(Staff staff){
       return staff.getWorkYears() <= 1;
    }
    public static boolean isB(Staff staff){
       return staff.getWorkYears() <= 3 && staff.getWorkYears() > 1;
    }
    public static boolean isC(Staff staff){
       return staff.getWorkYears() <= 5 && staff.getWorkYears() > 3;
    }
    public static boolean isD(Staff staff){
       return staff.getWorkYears() <= 5 && staff.getWorkYears() > 3;
    }
    public static void sendMoney(Staff staff,int money){
        System.out.println(“發放”+staff.getName()+”獎金”+money+”元”);
    }
}
           

代碼将判斷條件抽出來放到方法裡面是的每個判斷條件得以重用,這是相對之前代碼一個提高。那麼對于判斷條件的處理有沒有更好的方法呢?

(4)匿名内部類

定義一個判斷員工工作年限的接口,如下:

public interface MyTest<T>{
    boolean test(T t);
}
public class TestLambda1{
    public static void  main(String[] args){
         List staffList = StaffUtil.createStaffList(20);
         filterA(staffList,new MyTest<Staff>(){
            @Override
            boolean test(Staff staff){
                return staff.getWorkYears() <= 1;
             }
         });
         filterA(staffList,new MyTest<Staff>(){
            @Override
            boolean test(Staff staff){
                return staff.getWorkYears() <= 3 && staff.getWorkYears() > 1;
             }
         });
         //…
    }
    public static void filterA(List<Staff> staffList,MyTest<Staff> test){
        for(Staff staff:staffList){
            if(test(staff)){
                sendMoney(staff,2000);
            }
        }
    }
    public static void filterB(List<Staff> staffList,MyTest<Staff> test){
        for(Staff staff:staffList){
            if(test(staff)){
                sendMoney(staff,5000);
            }
        }
    }
    public static void filterC(List<Staff> staffList,MyTest<Staff> test){
        for(Staff staff:staffList){
            if(test(staff)){
                sendMoney(staff,10000);
            }
        }
    }
    public static void filterD(List<Staff> staffList,MyTest<Staff> test){
        for(Staff staff:staffList){
            if(test(staff)){
                sendMoney(staff,10000);
            }
        }
    }
    public static void sendMoney(Staff staff,int money){
        System.out.println(“發放”+staff.getName()+”獎金”+money+”元”);
    }
}
           

上面的代碼使用匿名内部類來代替剛才的方法,好處時,減少了三個方法,而且對于判斷規則,沒必要寫死,隻要在調用時給出規則即可。但是缺點是使用匿名内部類,使得代碼變得臃腫無比。

(5)使用優雅的Lambda表達式

前面(4)定義了一個MyTest接口,該接口隻有一個抽象方法test,結合我們前面講到的Functional Interface,是以該接口是支援lambda表達式的。其實我們定義這個方法是沒有必要的,因為java中已經定義了類似的接口供我們使用:Java.util.function.Predicate。

public class TestLambda1{
    public static void  main(String[] args){
         List staffList = StaffUtil.createStaffList(20);
         Predicate<Staff> preA = staff -> staff.getWorkYears() <= 1;
         Predicate<Staff> preB = staff -> staff.getWorkYears() <= 3 && staff.getWorkYears() > 1;
         filterA(staffList,preA);
         filterA(staffList,preB);
         //…
    }
    public static void filterA(List<Staff> staffList,Predicate<Staff> test){
        for(Staff staff:staffList){
            if(test(staff)){
                sendMoney(staff,2000);
            }
        }
    }
    public static void filterB(List<Staff> staffList,Predicate<Staff> test){
        for(Staff staff:staffList){
            if(test(staff)){
                sendMoney(staff,5000);
            }
        }
    }
    public static void filterC(List<Staff> staffList,Predicate<Staff> test){
        for(Staff staff:staffList){
            if(test(staff)){
                sendMoney(staff,10000);
            }
        }
    }
    public static void filterD(List<Staff> staffList,Predicate<Staff> test){
        for(Staff staff:staffList){
            if(test(staff)){
                sendMoney(staff,10000);
            }
        }
    }
    public static void sendMoney(Staff staff,int money){
        System.out.println(“發放”+staff.getName()+”獎金”+money+”元”);
    }
}
           

怎麼樣代碼是不是變的優雅許多了呢?