Lambda表達式

為什麼使用Lambda表達式:

Lambda是一個匿名函數,我們可以把Lambda表達式理解為是一段可以傳遞的代碼(將代碼像數據一樣進行傳遞)。它是 JDK8 的一個新特性,可以取代大部分的匿名內部類,寫出更優雅的 Java 代碼,極大地優化代碼結構。

JDK 也提供了大量的內置函數式接口供我們使用,使得 Lambda 表達式的運用更加方便、高效。

Lambda表達式對接口的要求:

Lambda 表達式需要「函數式接口」的支持

函數式接口:接口中只有一個抽象方法的接口,稱為函數式接口。一般地,函數式接口都使用註解 @FunctionalInterface 修飾,來檢查是否是函數式接口。

Lambda表達式的基礎語法:

JAVA8 中引入了一個新的操作符  「->」 ,該操作符稱為 「箭頭操作符」「Lambda操作符」

箭頭操作符將 Lambda 表達式拆分成兩部分:

左側:Lambda 表達式的參數列表

右側:Lambda 表達式中所需要執行的功能,即 Lambda 體

示例:

import org.junit.Test;
import java.util.Comparator;
import java.util.function.Consumer;
/**
 * Lambda 表達式測試
 */
public class TestLambda1 {

    /**
     * 語法格式一: 無參數,無返回值
     */
    @Test
    public void test1(){
        int num = 0;//jdk 1.7 前,必須是 final,jdk 1.8 以後,無需手動加 final ,默認就是final 的
        Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello World!" + num);
            }
        };
        r.run();
        System.out.println("-------------------------------");
        Runnable r1 = () -> System.out.println("Hello Lambda !! " + num);
        r1.run();
    }

    /**
     * 語法格式二:有一個參數,並且無返回值
     */
    @Test
    public void test2(){
        //
        /*
            //用一個Java的工具類作為案例。
            @FunctionalInterface //函數式接口的註解
            public interface Consumer<T> {
                void accept(T t);
            }
        */
        Consumer<String> con = new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println("匿名內部類方式輸入的參數:"+s);
            }
        };
        con.accept("Java 是最好的編程語言!");
        System.out.println("-------------------------------");
        Consumer<String> con1 = (s) -> System.out.println("Lambda 方式的輸入參數:"+s);
        con1.accept("Java 是最好的編程語言!");

    }
    /**
     * 語法格式三:若只有一個參數,小括號可以省略不寫
     */
    @Test
    public void test3(){
        Consumer<String> con1 = s -> System.out.println("Lambda 方式的輸入參數:"+s);
        con1.accept("Java 是最好的編程語言!");

    }
    /**
     * 語法格式四:有兩個以上的參數,有返回值,並且 Lambda 體中有多條語句,則可以加上 {}
     */
    @Test
    public void test4(){
        Comparator<Integer> com = (x,y) -> {
            System.out.println("Java 是最好的編程語言!");
            return Integer.compare(x, y);
        };
        System.out.println(com.compare(3,4));
    }
    /**
     * 語法格式五:若 Lambda 體中只有一條語句, return 和 {} 都可以省略不寫
     */
    @Test
    public void test5(){
        Comparator<Integer> com = (x,y) -> Integer.compare(x, y);
        System.out.println(com.compare(3,4));
    }
    /**
     * 語法格式六:Lambda 表達式的參數列表的數據類型可以省略不寫,因為JVM 編譯器通過上下文推斷出,數據類型,即「類型推斷」
     */
    @Test
    public void test6(){
        Comparator<Integer> com = (Integer x,Integer y) -> Integer.compare(x, y);
        System.out.println(com.compare(3,4));
    }
}

View Code

 

 類型推斷:上述 Lambda 表達式中的參數類型都是由編譯器推斷得出的。Lambda 表達式中無需指定類型,程序依然可以編譯,這是因為 javac 根據程序的上下文,在後台推斷出了參數的類型。Lambda 表達式的類型依賴於上下文環境,是由編譯器推斷出來的。這就是所謂的「類型推斷」。

Lambda表達式的應用示例:

示例1:對傳入的參數,進行運算,在實際調用方法的時候才知曉具體做什麼運算。

@FunctionalInterface
public interface MyFun {
    /**
     * 根據傳入的值,進行運算,返回運算後的值。
     * @param i
     * @return
     */
    public Integer getValue(Integer i);
}


public class TestLambda2 {
    public static void main(String[] args) {
        //先用匿名內部類的方式
        MyFun mf = new MyFun() {
            @Override
            public Integer getValue(Integer i) {
                System.out.println("我是最原始的匿名內部類實現的運算(返回:參數 * 100)...");
                return i * 100;
            }
        };
        //對 20 進行 * 100 的運算
        System.out.println(mf.getValue(20));
        System.out.println("-------------------------------");
        //轉換成Lambda表達式方式
        MyFun mf1 = (i) -> {
            System.out.println("Lambda 表達式實現運算(返回:參數 * 100)...");
            return i * 100;
        };
        System.out.println(mf1.getValue(20));

        //Lambda表達式可以用最簡便的代碼,實現各種運算
        //例:
        MyFun mf2 = i -> i + 2;
        MyFun mf3 = i -> i * 2;
        MyFun mf4 = i -> i / 2;
        MyFun mf5 = i -> i - 2;
        //等等....................
    }
}

View Code

示例2:用 Collections.sort() 方法對集合排序。

import lombok.Data;

@Data
public class Employee{
    private String name;
    private Integer age;
    private Integer gender;
}


import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class TestLambda3 {
    public static void main(String[] args) {

        List<Employee> emps = new ArrayList<>();
        emps.add(new Employee("張三",10,0));
        emps.add(new Employee("李四",13,1));
        emps.add(new Employee("王麻子",8,0));
        emps.add(new Employee("王五",18,0));
        emps.add(new Employee("趙六",13,0));

        //原始方法排序
        Collections.sort(emps, new Comparator<Employee>() {
            @Override
            public int compare(Employee o1, Employee o2) {
                //按照年齡排序
                return o1.getAge() - o2.getAge();
            }
        });
        System.out.println(emps);

        System.out.println("-------------------------------");
        //Lambda表達式方式
        // 排序1:按照年齡排
        Collections.sort(emps,(o1,o2)->o2.getAge() - o1.getAge());
        System.out.println(emps);
        System.out.println("-------------------------------");
        //排序2:如果年齡相同,則按照性別排序
        Collections.sort(emps,(o1,o2)->{
            if((o1.getAge() - o2.getAge()) == 0){
                return o2.getGender() - o1.getGender();
            }
            return o1.getAge() - o2.getAge();
        });
        System.out.println(emps);

    }
}

View Code

 

當我們需要用Lambda表達式來實現一個自己的需求,就得自己去創建一個接口,這樣還是比較麻煩的。實際上,我們完全可以不用自己創建接口,使用Java8自帶的函數式接口來滿足我們的需求。

JAVA8內置的四大函數式接口:

函數式接口  參數類型 返回類型 用途

Consumer<T>

消費型接口

T void 對類型為 T 的對象應用操作,包含方法:void accept(T t);

Supplier<T>

供給型接口

T 返回類型為T 的對象,包含方法:T get();

Function<T,R>

函數型接口

T R 對類型為 T 的對象應用操作,並返回結果。結果是R類型的對象。包含方法:R apply(T t);

Predicate<T>

斷定型接口

T boolean 確定類型為T的對象是否滿足某約束,並返回boolean值。包含方法:boolean test(T t);

 

 

 

 

 

 

 

 

 

 

 

 

 

內置函數式接口示例:

import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

public class TestLambda4 {

    /**
     * Consumer<T> 消費型接口
     */
    @Test
    public void test1(){
        happy(20000.0,m -> System.out.println("你要去做大保健,每次"+m+"元"));
    }
    public void happy(Double money, Consumer<Double> consumer){
        consumer.accept(money);
    }

    /**
     * Supplier<T> 供給型接口
     */
    @Test
    public void test2(){

        List<Integer> list = getNumList(25, () -> (int) (Math.random() * 100));
        System.out.println(list);

    }
    /**
     * 獲取指定數量的整數集合
     * @param num
     * @param supplier
     * @return
     */
    public List<Integer> getNumList(int num,Supplier<Integer> supplier){
        List<Integer> list = new ArrayList<>();
        for(int i=0;i<num ;i++){
            Integer result = supplier.get();
            list.add(result);
        }
        return list;
    }

    /**
     * Function<T, R> 函數型接口
     */
    @Test
    public void test3(){
        String newStr = strHandler("\t\t\n Java 好牛逼呀   ", (str) -> str.trim());
        System.out.println(newStr);

        String subStr = strHandler("Java is so so cool...", (str) -> str.toUpperCase());
        System.out.println(subStr);
    }
    /**
     * 需求:用於處理字符串
     * @param str
     * @param fun
     * @return
     */
    public String strHandler(String str, Function<String, String> fun){
        return fun.apply(str);
    }

    /**
     * Predicate<T> 斷定型接口
     */
    @Test
    public void test4(){
        List<String> list = Arrays.asList("Java", "Python", "Lambda", "I love u", "ok");
        List<String> strList = filterStr(list, (s) -> s.length() > 4);
        System.out.println(strList);
    }
    /**
     * 返回滿足條件的字符串集合
     * @param list
     * @param pre
     * @return
     */
    public List<String> filterStr(List<String> list, Predicate<String> pre){
        List<String> strList = new ArrayList<>();
        for (String str : list) {
            if(pre.test(str)){
                strList.add(str);
            }
        }
        return strList;
    }

}

View Code

 

其他函數式接口:

 

方法引用、構造器引用、數組引用:

方法引用

若 Lambda 體重的內容有方法已經實現了,我們可以使用「方法引用」。可以理解為方法引用就是 Lambda 表達式的另外一種表現形式。

方法引用主要的三種語法格式

對象 :: 實例方法名

類 :: 靜態方法名

類 :: 實例方法名

注意:

1. Lambda 體中調用方法的 【參數列表與【返回值類型,要與 函數式接口中 抽象方法  的 【參數列表和【返回值類型保持一致。

2. 若 Lambda 參數列表中的 第一參數 是 實例方法的調用者,而 第二個參數 是 實例方法的 參數 時,則可以使用 類 :: 實例方法名(ClassName :: method)

示例:

import org.junit.Test;
import java.util.Comparator;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.Supplier;

public class TestLambda5 {
    /**
     * 方法引用測試 對象 :: 實例方法名
     */
    @Test
    public void test1(){
        Consumer<String> con = (str) -> System.out.println("我是參數:"+str);
        con.accept("Hello Lambda");

        Consumer<String> con1 = System.out::println;
        con1.accept("Hello 方法引用...");

        // 需要實現的抽象方法的參數列表和返回值:void accept(String str); accept() 方法的參數為 String ,返回值為 void
        //Lambda體中引用的方法的  參數列表 與 返回值類型 : System.out.println("");System.out 對象的 println() 方法的參數為 String , 返回值為 void
        //在上面這種情況下,(str) -> System.out.println("我是參數:"+str);  就可以直接寫成 System.out::println
    }
    /**
     * 方法引用測試 對象 :: 實例方法名
     */
    @Test
    public void test2(){
        Employee emp = new Employee("周三",22,1);
        Supplier<String> sup1 = ()-> emp.getName();
        //Supplier<String> sup1 = emp :: getName; //與上面等價
        System.out.println(sup1.get());
        System.out.println("-------------------------------");
        //Supplier<Integer> sup2 = () -> emp.getAge(); //與下面等價
        Supplier<Integer> sup2 = emp::getAge;
        //sup2.get() 和 emp.getAge() 參數列表都為 Null ,返回值都是Integer ,所以可以用兩種表現形式。
        System.out.println(sup2.get());
    }
    /**
     * 方法引用測試 類 :: 靜態方法名
     */
    @Test
    public void test3(){
        Comparator<Integer> com1 = (x,y) -> Integer.compare(x,y);
        System.out.println(com1.compare(10,20));
        System.out.println("-------------------------------");
        Comparator<Integer> com2 = Integer::compare;
        System.out.println(com2.compare(122,22));
    }
    /**
     * 方法引用測試 類 :: 實例方法名
     */
    @Test
    public void test4(){
        BiPredicate<String,String> bp1 = (x,y) -> x.equals(y);
        System.out.println(bp1.test("abc","abc"));
        System.out.println("-------------------------------");
        BiPredicate<String,String> bp2 = String::equals;
        System.out.println(bp1.test("abc","abc"));
    }
}

View Code

 

構造器引用

與函數式接口相結合,自動與函數式接口中方法兼容。可以把構造器引用賦值給定義的方法,與構造器參數列表要與接口中抽象方法的參數列表一致!

格式:ClassName :: new

 

注意:

 

需要調用的 構造器 的 參數列表 要與 函數式接口 中 抽象方法 的 參數列表 保持一致!

 

示例:

import org.junit.Test;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;

/**
 *  構造器 引用 測試
 *  格式:ClassName :: new
 */
public class TestLambda6 {

    /**
     * 調用無參構造器
     */
    @Test
    public void test1(){
        Supplier<Employee> supplier = () -> new Employee();
        System.out.println(supplier.get());
        System.out.println("-------------------------------");
        Supplier<Employee> sup1 = Employee::new;//sup1.get() 沒有參數,調用的new Employee() 也是無參構造器。
        System.out.println(sup1.get());
    }
    /**
     * 調用一個參數的構造器
     */
    @Test
    public void test3(){
        Function<String,Employee> fun = (name) -> new Employee(name);
        System.out.println(fun.apply("張麻子"));
        System.out.println("-------------------------------");
        Function<String,Employee> fun1 = Employee::new;//fun1.apply("") 有一個參數,調用的new Employee("")一個參數的構造器。
        System.out.println(fun1.apply("王二麻子"));
    }
    /**
     * 調用兩個參數的構造器
     */
    @Test
    public void test4(){
        BiFunction<String,Integer,Employee> fun = (name,age) -> new Employee(name,age);
        System.out.println(fun.apply("張麻子",12));
        System.out.println("-------------------------------");
        BiFunction<String,Integer,Employee> fun1 = Employee::new;//fun1.apply("",int) 有兩個參數,調用的new Employee("",int)兩個參數的構造器。
        System.out.println(fun1.apply("王二麻子",20));
    }
}

View Code

 

數組引用

格式:type[] :: new

示例:

    /**
     * 數組引用
     */
    @Test
    public void test5() {
        Function<Integer, String[]> fun1 = (x) -> new String[x];
        System.out.println(fun1.apply(10).length);
        System.out.println("-------------------------------");
        Function<Integer, String[]> fun2 = String[]::new;
        System.out.println(fun1.apply(20).length);
    }

View Code