Lambda表達式不再只是聽說

1. Lambda表達式簡介

1.1 什麼是Lambda?

Lambda 表達式,也可稱爲閉包,可以看作爲匿名內部類。它是推動 Java 8 發佈的最重要新特性。

Lambda 允許把函數作爲一個方法的參數(函數作爲參數傳遞進方法中)。

使用 Lambda 表達式可以使代碼變的更加簡潔緊湊。

1.2 爲什麼要使用Lambda表達式?

使用Lambda表達式可以對一個接口進行非常簡單的實現,接口的常規實現就是,實現類或匿名內部類,下面通過一個小的示例,對比三種實現方式

public class Test {
    public static void main(String[] args) {
        //1.使用接口實現類
        Comparator comparator = new MyComparator();
        comparator.compare(a,b);

        //2.使用匿名內部類
        Comparator comparator1 = new Comparator() {
            @Override
            public int compare(int a, int b) {
                return a - b;
            }
        };

        //3.使用Lambda表達式來實現接口
        Comparator comparator2 = (a,b) -> a - b;
    }
}

class MyComparator implements Comparator {

    @Override
    public int compare(int a, int b) {
        return a - b;
    }
}

interface Comparator{
    int compare(int a, int b);
}

通過對比,就可以看出Lambda表達式實現接口的簡潔和可讀性了。

1.3 Lambda對接口的要求

試想一下,如果一個接口中有多個方法,會是怎樣的情形。如果方法參數列表相同,Lambda表達式無法分辨或者說是識別實現哪一個方法。雖然可以使用Lambda表達式對某些接口進行簡單的實現,但是並不是所有的接口都能用Lambda表達式來實現。要求接口中定義的要實現的方法只有一個。

  • 在Java8對接口加了一個新特性:default,會默認實現,如果接口實現類不重寫該方法,就會執行執行方法的默認邏輯。接口中default修飾的方法,對Lambda表達式無影響
  • @FuncionalInterface修飾函數式接口的,被修飾的接口中只能定義一個抽象方法,default修飾的方法除外。所以用Lambda表達式實現的接口,儘量用該註解修飾。

2. Lambda表達式基礎語法

2.1接口準備

/**
 * 無返回值,無參數
 */
@FunctionalInterface
public interface LambdaNoneReturnNoneParameter {
    void test();
}
/**
 * 無返回值,單個參數
 */
@FunctionalInterface
public interface LambdaNoneReturnSingleParameter {
    void test(int n);
}
/**
 * 無返回值,多個參數
 */
@FunctionalInterface
public interface LambdaNoneReturnMultipleParameter {
    void test(int a, int b);
}
/**
 * 有返回值,無參數
 */
@FunctionalInterface
public interface LambdaWithReturnNoneParameter {
    int test();
}
/**
 * 有返回值,單個參數
 */
@FunctionalInterface
public interface LambdaWithReturnSingleParameter {
    int test(int a);
}
/**
 * 有返回值,多個參數
 */
@FunctionalInterface
public interface LambdaWithReturnMulpitleParameter {
    int test(int a, int b);
}

2.2 通過對上面接口的使用來了解Lambda語法

2.2.1 Lambda表達式基礎語法

Lambda是一個匿名函數,包含參數列表,方法體,(參數名:參數類型,參數名:參數類型) -> {方法體}

  • (): 用來描述參數列表

  • {}: 用來描述方法體

  • ->: Lambda運算符

無返回值、無參數,接口的實現

LambdaNoneReturnNoneParameter lambda1 = () -> {
    System.out.println("No Parameter, No Return.");
};
lambda1.test();//No Parameter, No Return.

無返回值、單個參數,接口的實現

LambdaNoneReturnSingleParameter lambda2 = (int a) -> {
    System.out.println(a);
};
lambda2.test(2);//2

無返回值、多個參數,接口的實現

LambdaNoneReturnMultipleParameter lambda3 = (int a, int b) -> {
    System.out.println(a + b);
};
lambda3.test(1, 99);//100

有返回值、無參數,接口的實現

LambdaWithReturnNoneParameter lambda4 = () -> {
    System.out.println("with return, no parameter.");
    return 100;
};
int result = lambda4.test();
System.out.println(result);//100

有返回值、單個參數,接口的實現

LambdaWithReturnSingleParameter lambda5 = (int a) -> {
    return a * 2;
};
int result2 = lambda5.test(168);
System.out.println(result2);//336

有返回值、多個參數,接口的實現

LambdaWithReturnMulpitleParameter lambda6 = (int a, int b) -> {
    return a * b;
};
int result3 = lambda6.test(9, 10);
System.out.println(result3);//90

3.Lambda表達式語法精簡

3.1參數類型精簡

由於在接口的抽象方法中,已經定義了參數的數量和類型,所以在Lambda表達式中,參數類型可以省略
備註:如果要省略參數類型,則每一個參數的類型都要省略,千萬不要只省略一部分參數。

LambdaNoneReturnMultipleParameter lambda1 = (a, b) -> {
    System.out.println(a + b);
};
lambda1.test(1,2);//3

3.2參數列表小括號精簡

如果參數列表中,參數的數量只有一個,此時小括號可以省略

LambdaNoneReturnSingleParameter lambda2 = a -> {
    System.out.println(a);
};
lambda2.test(2);//2

3.3 方法體大括號精簡

如果方法體中只有一條語句,此時大括號可以省略

LambdaNoneReturnSingleParameter lambda3 = a -> System.out.println(a);
lambda2.test(3);//3

3.4 return精簡

如果方法體中只有一條語句,並且這條語句是返回語句,則在省略大括號的同時,也必須省略return

LambdaWithReturnMulpitleParameter lambda4 = () -> 4;
int result = lambda4.test();
System.out.println(result);//4

LambdaWithReturnMulpitleParameter lambda5 = (a, b) -> a + b;
int result2 = lambda5.test(1, 2);
System.out.println(result);//3

4.Lambda表達式語法進階

4.1方法引用

如果有多處需要實現同一接口執行同樣的操作,每次都要重複Lambda表達式中方法體邏輯,可以採取方法引用來解決這個問題,將Lambda表達式方法體中邏輯抽取到某個類的靜態方法中,即使多處需要實現接口,只需通過Lambda表達式進行方法引用即可。引用某個類的方法類名::方法,類似通過類名調用方法,方法需要是靜態的。如果是需要引用非靜態方法,還需要先實例化對象,然後調用方法,意義不太。

public class Test3 {
    public static void main(String[] args) {
        //方法引用:
        //可以快速的將一個Lambda表達式的實現指向一個已經實現的方法
        //方法的隸屬者:: 方法名
        //注意:
        //1.參數數量和類型必須要和接口中定義的方法保持一致
        //2.返回值的類型也必須要和接口中定義的方法保持一致

        LambdaWithReturnSingleParameter lambda1 = a -> change(a);
        int result = lambda1.test(3);
        System.out.println(result);
        //方法引用:引用了change方法的實現
        LambdaWithReturnSingleParameter lambda2 = Test3::change;
        int result2 = lambda2.test(5);
        System.out.println(result2);
    }

    private static int change(int a) {
        return a * 2;
    }
    
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

4.2構造方法引用

先定義一個Person類,它的無參構造和帶參如下:

public class Person {
    private String name;
    private int age;

    public Person() {
        System.out.println("Person類的無參構造執行了.");
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
        System.out.println("Person類的有參構造執行了.");
    }
}

常規Lambda表達式實現:只有一條語句{}可省略

構造引用語法如下:

類名:: new

public class Test4 {
    public static void main(String[] args) {
        //常規Lambda表達式
        PersonCreator creator1 = () -> new Person();
        Person person1 = creator1.getPerson();//Person類的無參構造執行了.


        //無參構造引用
        PersonCreator creator2 = Person::new;
        Person person2 = creator2.getPerson();//Person類的無參構造執行了.

        //帶參構造引用
        PersonCreatorWithParameter creator3 = Person::new;
        creator3.getPerson("小明", 12);//Person類的有參構造執行了.
        
    }
}
//需求:
interface PersonCreator{
    Person getPerson();
}

interface PersonCreatorWithParameter{
    Person getPerson(String name, int age);
}

5.Lambda表達式之綜合案例

5.1 集合排序Comparator

public class Exercise1 {
    @RequiresApi(api = Build.VERSION_CODES.N)
    public static void main(String[] args) {
        //ArrayList
        //需求:已知在一個ArrayList中有若干個Person對象,將這些Person對象按照年齡進行降序排序
        ArrayList<Person> list = new ArrayList<>();
        list.add(new Person("xiaoming", 10));
        list.add(new Person("lilei", 11));
        list.add(new Person("hanmeimei", 12));
        list.add(new Person("lily", 9));
        list.add(new Person("lucy", 9));
        list.add(new Person("polly", 3));
        list.add(new Person("uncle wang", 40));
        //排序
        list.sort((a1, a2) -> a2.age - a1.age);
        System.out.println(list);
        //[Person{name='uncle wang', age=40}, Person{name='hanmeimei', age=12}, Person{name='lilei', age=11}, Person{name='xiaoming', age=10}, Person{name='lily', age=9}, Person{name='lucy', age=9}, Person{name='polly', age=3}]
    }
}

再看Lambda表達式實現TreeSet集合排序,通過TreeSet構造,我們知道TreeSet自帶排序功能

public TreeSet(Comparator<? super E> comparator) {
    this(new TreeMap<>(comparator));
}

通過打印日誌,發現age爲9的lucy沒有了,這是因爲TreeSet的去重功能。與Lambda表達式無關。若想保留age爲9的重複元素,可進行如下操作。

public class Exercise2 {
    public static void main(String[] args) {
        //需求:已知在一個ArrayList中有若干個Person對象,將這些Person對象按照年齡進行降序排序
        //會去重age爲9的一個元素
        //TreeSet<Person> list = new TreeSet<>((a1, a2) -> a2.age - a1.age);
        
        //[Person{name='uncle wang', age=40}, Person{name='hanmeimei', age=12}, Person{name='lilei', age=11}, Person{name='xiaoming', age=10}, Person{name='lily', age=9}, Person{name='polly', age=3}]
        //保留重複元素
        TreeSet<Person> list = new TreeSet<>((a1, a2) -> {
            if (a1.age >= a2.age) {
                return -1;
            } else {
                return 1;
            }
        });
        //[Person{name='uncle wang', age=40}, Person{name='hanmeimei', age=12}, Person{name='lilei', age=11}, Person{name='xiaoming', age=10}, Person{name='lucy', age=9}, Person{name='lily', age=9}, Person{name='polly', age=3}]
        
        list.add(new Person("xiaoming", 10));
        list.add(new Person("lilei", 11));
        list.add(new Person("hanmeimei", 12));
        list.add(new Person("lily", 9));
        list.add(new Person("lucy", 9));
        list.add(new Person("polly", 3));
        list.add(new Person("uncle wang", 40));
       
        System.out.println(list);
    }
}

5.2集合遍歷 forEach()

public class Exercise3 {
    @RequiresApi(api = Build.VERSION_CODES.N)
    public static void main(String[] args) {
        //集合遍歷
        //需求:已知在一個ArrayList中有若干個Person對象,將這些Person對象按照年齡進行降序排序
        ArrayList<Integer> list = new ArrayList<>();
        Collections.addAll(list, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0);

        //將集合中的每一個元素都帶入到方法accept中
        list.forEach(System.out::println);

        //輸出集合中所有的偶數
        list.forEach(element -> {
            if (element % 2 == 0) {
                System.out.println(element);
            }
        });
    }
}

5.3 刪除集合中滿足條件的元素removeIf()

public class Exercise4 {
    @RequiresApi(api = Build.VERSION_CODES.N)
    public static void main(String[] args) {
        //需求:刪除滿足條件的元素
        ArrayList<Person> list = new ArrayList<>();
        list.add(new Person("xiaoming", 10));
        list.add(new Person("lilei", 11));
        list.add(new Person("hanmeimei", 12));
        list.add(new Person("lily", 9));
        list.add(new Person("lucy", 9));
        list.add(new Person("polly", 3));
        list.add(new Person("uncle wang", 40));

        //刪除集合中年齡大於10的元素
        //常規實現
        /*ListIterator<Person> iterator = list.listIterator();
        while (iterator.hasNext()) {
            Person element = iterator.next();
            if (element.age > 10) {
                iterator.remove();
            }
        }*/

        //Lambda表達式實現
        //將集合中每一個元素帶入到test方法中,如果返回true,則刪除該元素
        list.removeIf(element -> element.age > 10);
        System.out.println(list);
    }
}

5.4 線程實例化

public class Exercise5 {
    public static void main(String[] args) {
        //需求:開啓一個線程,進行數字輸出
        //常規操作
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(i);
                }
            }
        });
        thread1.start();
		
        //Lambda實現
        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.println(i);
            }
        });
        thread2.start();
    }
}

6.閉包問題

常規認知在java中,局部變量的生命週期在局部方法調用完畢時結束。也就是說在getNumber方法調用結束後,num應該銷燬了。但是在getNumber方法中,又通過Lambda閉包提升了num的生命週期,這樣就可以在外部獲取方法中的局部變量了。

public class ClosureDemo {
    @RequiresApi(api = Build.VERSION_CODES.N)
    public static void main(String[] args) {
        int n = getNumber().get();
        System.out.println(n);//10

    }

    //Supplier<T>  無參、返回值爲T類型的接口
    private static Supplier<Integer> getNumber() {
        int num = 10;
        return () -> {
          return num;
        };
    }
}

在Lambda閉包中引用的變量,必須是常量,也就是說會默認添加final修飾符。

public class ClosureDemo2 {
    @RequiresApi(api = Build.VERSION_CODES.N)
    public static void main(String[] args) {
        //Consumer<T>  參數類型爲T 無返回值的接口
        int a = 9;
        //Lambda表達式使用局部變量時,默認添加了final修飾符
        Consumer<Integer> consumer = element -> System.out.println(a);
//        a++; //此時a 已經是final了,不可改變
        //(閉包)Lambda表達式中使用的局部變量應該是final,不可以改變
//        Consumer<Integer> consumer2 = element -> System.out.println(a++);
        
        consumer.accept(1);
    }

}
添加final修飾符。

```java
public class ClosureDemo2 {
    @RequiresApi(api = Build.VERSION_CODES.N)
    public static void main(String[] args) {
        //Consumer<T>  參數類型爲T 無返回值的接口
        int a = 9;
        //Lambda表達式使用局部變量時,默認添加了final修飾符
        Consumer<Integer> consumer = element -> System.out.println(a);
//        a++; //此時a 已經是final了,不可改變
        //(閉包)Lambda表達式中使用的局部變量應該是final,不可以改變
//        Consumer<Integer> consumer2 = element -> System.out.println(a++);
        
        consumer.accept(1);
    }

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章