Lambda表达式应用浅析

一、什么是Lambda表达式

Lambda表达式是Java8的一个新特性,它提供了一种更加清晰和简明的方式使用单一方法接口(SAM,即Simple  Abstract Method.)。使用Lambda表达式能够更加方便和简单的使用匿名内部类,比如对于集合的遍历、比较、过滤等等。

二、知识背景

Annoymous Inner class

在Java里面,可以在使用的时候声明一个类的对象,并在使用时给出类的某些方法的实现,这种类叫做匿名内部类,比如在Android中的OnClickListener,代码如下:
如上代码所示,new View.OnClickListener()就是一个匿名内部类。

Functional interface

功能接口,只含有一个抽象方法的接口,即刚才所说的SAM,单一抽象方法。这样的接口比如:

  • 文件过滤接口-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。
**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+”元”);
    }
}

怎么样代码是不是变的优雅许多了呢?