Lambda表達式

Lambda表達式

Lambda表達式介紹

Lambda是一個匿名函數,咱們能夠把Lambda表達式理解爲是一段能夠傳遞的代碼(將代碼像數據同樣傳遞)。能夠寫出更簡潔、更靈活的代碼。做爲一種更緊湊的代碼風格,使Java的語言表達能力獲得了提高。java

  • 匿名:沒有一個肯定的名字
  • 函數:Lambda不屬於一個特定的類,可是卻有參數列表、函數主體、返回類型、異常列表。
  • 傳遞:能夠做爲參數傳遞給方法、或者存儲在變量中
  • 簡潔:不須要寫不少模板代碼

爲何使用Lambda表達式

知道了Lambda表達式是什麼以後,咱們也沒啥感受,下面咱們來體驗一下Lambda表達式到底有啥好的。
咱們回顧下咱們原來學過的的匿名內部類算法

//原來的匿名內部類
    @Test
    public void test01(){
        //一個Comparator接口比較兩個Integer的大小
        Comparator<Integer> com = new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return Integer.compare(o1,o2);
            }
        };
        //當用匿名內部類實現了一個接口之後咱們就能夠把這個匿名內部類的實例做爲參數進行傳遞
        TreeSet<Integer> set = new TreeSet(com);
    }

咱們來看這段代碼,最核心關鍵的一行代碼就只有一行設計模式

return Integer.compare(o1,o2);

因此下面就有請咱們的Lambda表達式閃亮登場來解決這個一堆的代碼量的問題啦。api

下面咱們用Lambda表達式來簡化上面編寫的匿名內部類。jvm

//Lambda表達式
    @Test
    public void test02(){
        //把上面的匿名內部類核心的一行代碼Integer.compare(o1,o2)提取做爲一個實現
        Comparator<Integer> com = (x,y) -> Integer.compare(x,y);
        TreeSet<Integer> set = new TreeSet(com);
    }

是否是以爲簡單了,若是你還沒找到感受,咱們再來看一個例子。ide

假設有這麼一個需求,你要獲取當前公司中員工年齡大於35的員工信息。函數

咱們通常要怎麼實現呢?首先得先有一個員工的實體類Employee測試

package com.cqq.java8;

public class Employee {
    private String name;
    private int age;
    private double salary;

    public Employee(String name, int age, double salary) {
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    public Employee() {
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", salary=" + salary +
                '}';
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }
}

而後在寫一個用於過濾當前公司員工中年齡大於35的員工信息。優化

List<Employee> employees = Arrays.asList(
            new Employee("張三",68,9000),
            new Employee("李四",38,8000),
            new Employee("王五",50,4000),
            new Employee("趙六",18,3000),
            new Employee("田七",8,1000));
    //獲取當前公司中員工年齡大於35的員工信息
    public List<Employee> filterEmployee(List<Employee> employees){
        List<Employee> emps = new ArrayList<>();
        for (Employee emp: employees) {
            if(emp.getAge() > 35){
                emps.add(emp);
            }
        }
        return emps;
    }

而後寫個測試方法ui

@Test
    public void test03(){
        List<Employee> list = filterEmployee(employees);
        for (Employee employee:list ) {
            System.out.println(employee);
        }
    }

測試結果以下

Employee{name='張三', age=68, salary=9000.0}
Employee{name='李四', age=38, salary=8000.0}
Employee{name='王五', age=50, salary=4000.0}

咱們獲取到了年齡大於35的員工信息。
這時候,若是咱們的需求再增長一個,獲取當前公司中員工工資大於5000的員工信息。因而,咱們還要在再寫一個用於獲取當前公司中員工工資大於5000的員工信息的方法。

//當前公司中員工工資大於5000的員工信息
    public List<Employee> filterEmployee2(List<Employee> employees){
        List<Employee> emps = new ArrayList<>();
        for (Employee emp: employees) {
            if(emp.getSalary() > 5000){
                emps.add(emp);
            }
        }
        return emps;
    }

而後咱們發現,上面需求的倆方法有大量的冗餘代碼,除了判斷語句以外,其餘幾乎都是相同的代碼,這時候,咱們就想要來優化了。原來咱們對這種冗餘代碼最好的優化方式就是用設計模式,想一想用什麼設計模式呢,這裏咱們可使用策略模式。下面咱們來進行優化。

優化方式一:策略模式

策略模式 :定義一系列算法,把每個算法封裝起來,而且使它們可相互替換。簡單說就是咱們能夠根據環境或者條件的不一樣選擇不一樣的算法或者策略來完成某個功能,再通俗點說就是給他什麼策略他就用什麼策略來過濾。

這裏就是根據判斷條件的不一樣選出對應的員工Employee。

因爲倆方法是條件不一致,咱們能夠來定義一個接口來進行條件判斷。

//聲明一個帶泛型的接口
public interface MyPredicate<T> {

    //聲明一個boolean的方法,傳進來一個T,而後對T操做,條件判斷,返回一個boolean
    public boolean test(T t);
}

而後,若是咱們再有一個新的判斷規則,只須要定義一個新的實現類實現MyPredicate接口便可。

下面是對員工年齡大於35的判斷的實現類

public class FilterEmployeeByAge implements MyPredicate<Employee> {
    @Override
    public boolean test(Employee employee) {
        return employee.getAge() > 35;
    }
}

這個是員工員工工資大於5000的實現類

public class FilterEmployeeBySalary implements MyPredicate<Employee> {
    @Override
    public boolean test(Employee employee) {
        return employee.getSalary() > 5000;
    }
}

過濾方法以下

//參數傳進來一個MyPredicate接口的實現類em
    public List<Employee> filterEmployee(List<Employee> list, MyPredicate em){
        List<Employee> emps = new ArrayList<>();

        for (Employee employee: list) {
            //實現類的判斷方法
            if(em.test(employee)){
                emps.add(employee);
            }
        }

        return emps;
    }

測試方法

@Test
    public void test04(){
        System.out.println("========年齡大於35=========");
        //傳進來的參數爲接口年齡判斷的實現類
        List<Employee> list = filterEmployee(employees,new FilterEmployeeByAge());
        for (Employee employee:list ) {
            System.out.println(employee);
        }
        System.out.println("========工資大於5000=========");
        //傳進來的參數爲工資年齡判斷的實現類
        List<Employee> list2 = filterEmployee(employees,new FilterEmployeeBySalary());
        for (Employee employee:list2 ) {
            System.out.println(employee);
        }
    }

測試結果

========年齡大於35=========
Employee{name='張三', age=68, salary=9000.0}
Employee{name='李四', age=38, salary=8000.0}
Employee{name='王五', age=50, salary=4000.0}
========工資大於5000=========
Employee{name='張三', age=68, salary=9000.0}
Employee{name='李四', age=38, salary=8000.0}

雖說使用了策略模式來重構了代碼,可是爲每一個判斷類都定義一個實現類,仍是不那麼好。接下來,咱們直接使用匿名內部類實現接口的方式繼續優化代碼。

優化方法二:匿名內部類

@Test
    public void test05(){
       //傳進來的參數爲一個匿名內部類,不用每加一個判斷條件再加一個接口的實現類了
       List<Employee> list =  filterEmployee(employees, new MyPredicate<Employee>() {
            @Override
            public boolean test(Employee t) {
                return t.getSalary() < 5000;
            }
        });
        System.out.println("========工資小於5000=========");
        for (Employee employee:list ) {
            System.out.println(employee);
        }
    }

測試結果

========工資小於5000=========
Employee{name='王五', age=50, salary=4000.0}
Employee{name='趙六', age=18, salary=3000.0}
Employee{name='田七', age=8, salary=1000.0}

這樣是否是好多了,傳進來的參數爲一個匿名內部類,不用每加一個判斷條件再加一個接口的實現類了。再來看看這個方式是否是最好的呢,咱們發現其實匿名內部類裏有用的代碼仍是就 return t.getSalary() < 5000;這個判斷,可讀性仍是有點差,咱們是否是還能夠再繼續優化呢?接下來咱們繼續往下看。

優化方式三:Lambda表達式

@Test
    public void test06(){
         List<Employee> list = filterEmployee(employees, (e) -> e.getSalary() > 5000);
        list.forEach(System.out::println );
    }

優化二里的匿名內部類用了Lambda表達式(e) -> e.getSalary() > 5000 來替代
測試結果

Employee{name='張三', age=68, salary=9000.0}
Employee{name='李四', age=38, salary=8000.0}

若是你以爲這種方式還不夠好,其實還有更好的,咱們往下看

優化方式四:Stream API

@Test
    public void test5() {
        //獲取當前公司中員工工資不小於5000的員工信息
        employees.stream()
                .filter((e) -> e.getSalary() >= 5000)
                .forEach(System.out::println);
    }

測試結果

Employee{name='張三', age=68, salary=9000.0}
Employee{name='李四', age=38, salary=8000.0}

Lambda表達式的基礎語法

Java8中引入了一個新的操做符"->",叫箭頭操做符或者稱爲Lamnbda操做符

該操做符分爲兩部分:

左側:Lambda表達式的參數列表(對應接口中抽象方法的參數列表)

右側:Lambda表達式中所須要執行的功能,即Lambda體(對應接口中抽象方法的實現)

語法格式一:無參無返回值

() -> System.out.println();

這種語法格式說的就是接口中的那個抽象方法無參,而且無返回值。

Lambda表達式須要函數式接口的支持,咱們熟悉的Runnable接口就是函數式接口,而且使無參且無返回值的,Runnable接口的定義以下

@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}

下面咱們來看一下,原來咱們是怎樣經過匿名內部類的方式去實現的接口的呢?

//Lambda表達式須要函數式接口的支持,咱們的Runnable接口就是函數式接口
    @Test
    public void test01(){
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello Runnable");
            }
        };
    }

下面咱們經過Lambda表達式的方法實現接口

//經過Lambda表達式實現接口
        Runnable runnable1 = () -> System.out.println("Hello Lambda");
        runnable1.run();

咱們完整的測試方法以下

//Lambda表達式須要函數式接口的支持,咱們的Runnable接口就是函數式接口
    @Test
    public void test01(){
        //經過匿名內部類的方式實現接口
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello Runnable");
            }
        };
        runnable.run();
        System.out.println("--------------------");
        //經過Lambda表達式實現接口
        Runnable runnable1 = () -> System.out.println("Hello Lambda");
        runnable1.run();
    }

運行結果

Hello Runnable
--------------------
Hello Lambda

咱們能夠看出匿名內部類和Lambda表達式實現接口的效果是同樣的,可是Lambda表達式簡介了不少。

這裏還有一個小點須要注意:若是咱們在局部內部類中,應用了一個同級別的局部變量,在jdk1.8之前該變量必須是final的,1.8開始能夠不加final了,底層默認給咱們加上了,不須要咱們手動加了,當咱們對變量進行自增(++)或自減(--)操做時仍是不行的。

@Test
    public void test01(){
        //這裏fianl能夠不加,jdk1.8開始給咱們默認記上了final,不須要咱們手動加了
       /*final*/ int num = 0;
        //經過匿名內部類的方式實現接口
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello Runnable"+num);
            }
        };
        runnable.run();
        System.out.println("--------------------");
        //經過Lambda表達式實現接口
        Runnable runnable1 = () -> System.out.println("Hello Lambda"+num);
        runnable1.run();
    }

運行結果num的數也打印出來了

Hello Runnable0
--------------------
Hello Lambda0

語法格式二:有一個參數而且無返回值

這種語法格式說的就是接口中的那個抽象方法有一個參數,而且無返回值。
咱們以Consumer接口爲例來講明,下面是Consumer接口的定義

@FunctionalInterface
public interface Consumer<T> {

    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);

    /**
     * Returns a composed {@code Consumer} that performs, in sequence, this
     * operation followed by the {@code after} operation. If performing either
     * operation throws an exception, it is relayed to the caller of the
     * composed operation.  If performing this operation throws an exception,
     * the {@code after} operation will not be performed.
     *
     * @param after the operation to perform after this operation
     * @return a composed {@code Consumer} that performs in sequence this
     * operation followed by the {@code after} operation
     * @throws NullPointerException if {@code after} is null
     */
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

測試方法

//Lambda表達式
    @Test
    public void test02(){
        Consumer consumer = (x) -> System.out.println(x);
        consumer.accept("==這是測試結果==");
    }

運行結果

==這是測試結果==

這個例子說明:Lambda表達式就是對Consumer接口的accept(T t)這個抽象方法的實現,實現的方法體爲打印一下輸入的參數x。

語法格式三:如有一個參數,小括號能夠省略不寫

這種語法格式說的就是接口中的那個抽象方法只有一個參數時,小括號能夠寫也能夠不寫。

//Lambda表達式
    @Test
    public void test02(){
        //只有一個參數,小括號寫
        Consumer<String> consumer = (x) -> System.out.println(x);
        //只有一個參數,小括號不寫
        Consumer consumer2 = x -> System.out.println(x);
        consumer.accept("==這是測試結果==");
    }

語法格式四:有兩個及以上參數,而且Lambda體中有多條語句

這種語法格式說的就是接口中的那個抽象方法有兩個及以上的參數,而且Lambda方法體中有多條語句,此時Lambda體要用大括號{}括起來。

我就以Comparator接口爲例來說述這種語法格式。咱們只看Comparator接口的compare(T o1, T o2)方法的定義

@FunctionalInterface
public interface Comparator<T> {
    //--省略
    int compare(T o1, T o2);
}

測試方法

@Test
    public void test03(){
        Comparator<Integer> comparator = (x,y) -> {
            //方法體中多條語句
            System.out.println("方法體中多條語句1");
            System.out.println("方法體中多條語句2");
            return Integer.compare(x,y);
        };
        System.out.println(comparator.compare(2,4));
        System.out.println(comparator.compare(4,2));
        System.out.println(comparator.compare(2,2));
    }

測試結果

方法體中多條語句1
方法體中多條語句2
-1
方法體中多條語句1
方法體中多條語句2
1
方法體中多條語句1
方法體中多條語句2
0

語法格式五:當Lambda體中只有一條語句時,return和大括號均可以省略不寫

這種語法格式說的就是接口中的那個抽象方法有兩個及以上的參數,而且Lambda方法體中只有一條語句,此時return和大括號{}均可以不寫。

@Test
    public void test04(){
        Comparator<Integer> comparator = (x,y) -> Integer.compare(x,y);
        System.out.println(comparator.compare(2,4));
    }

語法格式六:Lambda表達式中的參數列表的數據類型能夠省略不寫,由於JVM編譯器能夠經過上下文來判斷出數據類型,即「類型推斷」

這種語法格式說的就是Lambda表達式中的參數列表的數據類型能夠省略不寫,由於JVM的編譯器能夠經過上下文來推斷出數據類型,這個過程咱們稱之爲"類型推斷",其實,「類型推斷"也是一個語法糖。

@Test
    public void test05(){
        Comparator<Integer> comparator = (x,y) -> Integer.compare(x,y);
        //x,y的數據類型Integer能夠不寫,JVM根據上下文Comparator<Integer>這個泛型的Integer能夠推斷出來
        Comparator<Integer> comparator2 = (Integer x,Integer y) -> Integer.compare(x,y);
    }

這裏x,y的數據類型Integer能夠不寫,JVM根據上下文Comparator 這個泛型的Integer能夠推斷出來參數類型。

總結

咱們總結下語法的原則:

上聯:左右遇一括號省(左邊一個參數,右邊一個返回值時,參數的小括號和方法體的大括號均可以省略)

下聯:左側推斷類型省(箭頭操做符的左側參數類型能夠經過目標上下文推斷出來,便可以省略不寫)

橫批:能省則省

相關文章
相關標籤/搜索