1. 程式人生 > >Lambda表示式應用淺析

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+”元”);
    }
}
怎麼樣程式碼是不是變的優雅許多了呢?