Java筆記——Java8特性之Lambda、方法引用和Streams

轉自: https://www.cnblogs.com/Fndroid/p/6087380.htmlhtml

Java8已經推出了好一段時間了,而掌握Java8的新特性也是必要的,若是要進行Spring開發,那麼能夠發現Spring的官網已經所有使用Java8來編寫示例代碼了,因此,不學就看不懂。java

這裏涉及三個重要特性:數組

  • Lambda
  • 方法引用
  • Streams

① Lambdaide

最先了解Lambda是在C#中,而從Java8開始,Lambda也成爲了新的特性,而這個新的特性的目的,就是爲了消除單方法接口實現的匿名內部類函數

在Java8之前的版本中,定義一個Thread是這樣的:this

複製代碼
1 final int i = 0;
2 new Thread(new Runnable() {
3     @Override
4     public void run() {
5         System.out.println("i = " + i);
6     }
7 }).start();
複製代碼

而Lambda是這樣的:spa

1 int i = 0;
2 new Thread(() -> System.out.println("i = " + i));

首先不看Lambda自己的寫法,能夠發現,對於i值的訪問,在Lambda中已經不須要聲明i爲final了。3d

其次,要明白一個重要的道理:Lambda要求實現的接口中只有一個方法,像上面的Runnable接口就只有一個run方法,若是一個接口中有多於一個方法,則不能寫成Lambda的形式。指針

最後來看標準的Lambda表達式的結構:code

結構很簡單,小括號表示參數列表,大括號表示方法體,中間使用一個 "->" 隔開便可。

這裏的參數體和方法體分別指的是接口中方法的參數體和方法體

接着咱們看一個比較比較複雜的:

複製代碼
1 ArrayList<Integer> integers = new ArrayList<>();
2 integers.add(6);
3 integers.add(2);
4 integers.add(5);
5 integers.sort((o1, o2) -> o1 - o2);
6 System.out.println(integers);
複製代碼

能夠看到,建立了一個ArrayList,裏面的元素是Integer類型,接着調用sort()方法對其進行排序,sort方法接受一個Comparator接口的實現類,這個接口中有且僅有一個方法compare,因此若是使用匿名內部類的寫法,以下所示:

複製代碼
1 integers.sort(new Comparator<Integer>() {
2     @Override
3     public int compare(Integer o1, Integer o2) {
4         return o1 - o2;
5     }
6 });
複製代碼

能夠發現,這裏參數列表和方法體都很明白了,要注意的是,這裏的方法體中不帶{},緣由就是當方法體只有一個語句的時候,{}能夠省略

另外,return關鍵字也被省略了,緣由是編譯器會認爲,既然只有一個語句,那麼這個語句執行的結果就應該是返回值,因此return也就不須要了。同理,當參數只有一個的時候,小括號也是能夠省略的

明白這種對應的關係,Lambda就算是掌握了。

② 方法引用

方法引用包括幾種狀況:

  • 靜態方法引用
  • 構造方法引用
  • 類成員方法引用
  • 對象方法引用

要注意這裏的方法引用其實是某些Lambda表達式的更簡潔寫法,緣由就是在這些狀況下,編譯器可以智能的推斷出參數體中的值到底是方法的傳入參數仍是調用者。

先定義一個Car類:

複製代碼
 1 import java.util.function.Supplier;
 2 
 3 public class Car {
 4     // 經過Supplier獲取Car實例
 5     public static Car create(Supplier<Car> supplier) {
 6         return supplier.get();
 7     }
 8 
 9     // 靜態方法,一個入參Car對象
10     public static void collide(final Car car) {
11         System.out.println("Collide " + car.toString());
12     }
13 
14     // 一個入參Car
15     public void follow(final Car car) {
16         System.out.println("Following car " + car.toString());
17     }
18 
19     // 不帶入參
20     public void repair() {
21         System.out.println("Repaired car " + this.toString());
22     }
23 }
複製代碼

--構造方法引用

Supplier接口的定義:

複製代碼
@FunctionalInterface
public interface Supplier<T> {

    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}
複製代碼

這個接口被FunctionalInterface註解聲明瞭,這個註解是一個新的註解,代表這個接口是一個函數式接口,只有一個抽象方法。

因此咱們調用靜態方法create建立Car對象的時候,代碼應該是以下所示的:

複製代碼
1 Car.create(new Supplier<Car>() {
2     @Override
3     public Car get() {
4         return new Car();
5     }
6 });
複製代碼

可是咱們才說了Lambda表達式,因此更簡單的寫法就是:

1 Car.create(()->new Car());

能夠看到,Car類存在一個不帶參數的構造方法,因此編譯器不須要根據參數列表猜想構造方法的參數(由於都是空的),因此就有一個更加簡單的寫法:

1 Car.create(Car::new);

實際上,若是Lambda的參數個數和類的構造方法個數一致,也能夠改寫爲上面的形式,只要是沒有歧義便可。

--靜態方法引用

這裏開始會涉及一些Streams內容,可是能夠先忽略,後面會詳細說。

咱們建立一個Car對象,接着將其添加進一個List中:

1 final Car car = Car.create(Car::new);
2 final List<Car> cars = Arrays.asList(car); 

Java8中給Iterable接口添加了forEach方法方便咱們遍歷集合類型。

如今假設咱們要給List中的每一個Car對象調用一次Car.collide(Car car)靜態方法,那麼可使用forEach方法,而forEach方法須要傳入一個Consumer,剛好,這個Consumer接口也帶有FunctionalInterface註解,因此咱們一步一步的來看:

複製代碼
1 cars.forEach(new Consumer<Car>() {
2     @Override
3     public void accept(Car car) {
4         Car.collide(car);
5     }
6 });
複製代碼

寫成Lambda:

1 cars.forEach(c -> Car.collide(c));

就是對傳進來的Car對象執行靜態方法,很簡單。可是實際上,對於靜態方法,編譯器也不須要推斷調用者(類名),當傳入參數和靜態方法所需參數個數一致時,就不存在歧義:

因此這裏能夠直接使用方法引用:

1 cars.forEach(Car::collide);

--類成員方法引用

類的成員方法不能是靜態的,而這個狀況其實和靜態方法相似,區別是,Lambda表達式的參數個數須要等於所調用方法的入參個數加一。

爲何要加一?

由於類的成員方法不能經過類名直接調用,只能經過對象來調用,也就是Lambda表達式的第一個參數,是方法的調用者,從第二個開始的參數個數要和須要調用方法的入參個數一致便可。以下圖所示:

對於上面的例子,若是要對List中的每一個對象執行一次它的repair方法:

1 cars.forEach(c -> c.repair());

根據上圖,這裏參數只有一個,而repair方法沒有入參,因此不存在歧義,便可以改寫爲對應的方法引用:

1 cars.forEach(Car::repair);

--對象方法引用

與類方法引用不一樣的是,對象方法引用方法的調用者是一個外部的對象。以下圖:

對於上面例子,能夠再建立一個Car的對象police,並讓police調用follow方法跟蹤List中的每一個Car:

1 final Car police = Car.create(Car::new);
2 cars.forEach((car1) -> police.follow(car1));

改爲對象方法引用:

1 cars.forEach(police::follow);

至此,方法引用也完成了。

③ Streams

Streams的思想很簡單,就是遍歷。

一個流的生命週期分爲三個階段:

  1. 生成
  2. 操做、變換(能夠屢次)
  3. 消耗(只有一次)

--生成

生成Stream對象

複製代碼
1 // 1. 對象
2 Stream stream = Stream.of("a", "b", "c");
3 // 2. 數組
4 String [] strArray = new String[] {"a", "b", "c"};
5 stream = Stream.of(strArray);
6 stream = Arrays.stream(strArray);
7 // 3. 集合
8 List<String> list = Arrays.asList(strArray);
9 stream = list.stream();
複製代碼

生成DoubleSteam、IntSteram或LongStream對象(這是目前支持的三個數值類型Stream對象)

1 IntStream.of(new int[]{1, 2, 3}); // 根據數組生成
2 IntStream.range(1, 3); // 按照範圍生成,不包括3
3 IntStream.rangeClosed(1, 3); // 按照範圍生成,包括3

等等。。。

--變換

一個流能夠通過屢次的變換,變換的結果仍然是一個流。

常見的變換:map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered

--消耗

一個流對應一個消耗操做。

常見的消耗操做:forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator

例子:

定義一個學生類:

複製代碼
 1 public class Student {
 2     public enum Sax{
 3         FEMALE, MALE
 4     }
 5 
 6     private String name;
 7     private int age;
 8     private Sax sax;
 9     private int height;
10 
11     public Student(String name, int age, Sax sax, int height) {
12         this.name = name;
13         this.age = age;
14         this.height = height;
15         this.sax = sax;
16     }
17 
18     public String getName() {
19         return name;
20     }
21 
22     public int getAge() {
23         return age;
24     }
25 
26     public Sax getSax() {
27         return sax;
28     }
29 
30     public int getHeight() {
31         return height;
32     }
33 
34     @Override
35     public String toString() {
36         return "Student{" +
37                 "name='" + name + '\'' +
38                 ", age=" + age +
39                 ", sax=" + sax +
40                 ", height=" + height +
41                 '}';
42     }
43 }
複製代碼

在main方法中建立示例數據:

1 List<Student> students = Arrays.asList(
2         new Student("Fndroid", 22, Student.Sax.MALE, 180),
3         new Student("Jack", 20, Student.Sax.MALE, 170),
4         new Student("Liliy", 18, Student.Sax.FEMALE, 160)
5 );

下面實現幾個查詢:

1.輸出全部性別爲MALE的學生:

循環:

1 for (Student student : students) {
2     if (student.getSax() == Student.Sax.MALE) {
3         System.out.println(student);
4     }
5 }

使用Stream:

1 students.stream() // 打開流
2         .filter(student -> student.getSax() == Student.Sax.MALE) // 進行過濾
3         .forEach(System.out::println); // 輸出

2.求出全部學生的平均年齡:

1 OptionalDouble averageAge = students.stream()
2         .mapToInt(Student::getAge) // 將對象映射爲整型
3         .average(); // 根據整形數據求平均值
4 System.out.println("全部學生的平均年齡爲:" + averageAge.orElse(0));

能夠看到這裏的average方法獲得一個OptionalDouble類型的值,這也是Java8的新增特性,OptionalXXX類用於減小空指針異常帶來的崩潰,能夠經過orElse方法得到其值,若是值爲null,則取默認值0。

3.輸出每一個學生姓名的大寫形式:

1 List<String> names = students.stream()
2         .map(Student::getName) // 將Student對象映射爲String(姓名)
3         .map(String::toUpperCase) // 將姓名轉爲小寫
4         .collect(Collectors.toList()); // 生成列表
5 System.out.println("全部學生姓名的大寫爲:" + names);

4.按照年齡從小到大排序:

1 List<Student> sortedStudents = students.stream()
2         .sorted((o1, o2) -> o1.getAge() - o2.getAge()) // 按照年齡排序
3         .collect(Collectors.toList()); // 生成列表
4 System.out.println("按年齡排序後列表爲:" + sortedStudents);

5.判斷是否存在名爲Fndroid的學生:

boolean isContain = students.stream()
        .anyMatch(student -> student.getName().equals("Fndroid")); // 查詢任意匹配項是否存在
System.out.println("是否包含姓名爲Fndroid的學生:" + isContain);

6.將全部學生按照性別分組:

1 Map<Student.Sax, List<Student>> groupBySax = students.stream()
2         .collect(Collectors.groupingBy(Student::getSax)); // 根據性別進行分組
3 System.out.println(groupBySax.get(Student.Sax.FEMALE));

7.求出每一個學生身高比例:

複製代碼
1 double sumHeight = students.stream().mapToInt(Student::getHeight).sum(); // 求出身高總和
2 DecimalFormat formator = new DecimalFormat("##.00"); // 保留兩位小數
3 List<String> percentages = students.stream()
4         .mapToInt(Student::getHeight) // 將Student對象映射爲身高整型值
5         .mapToDouble(value -> value / sumHeight * 100) // 求出比例
6         .mapToObj(per -> formator.format(per) + "%") // 組裝爲字符串
7         .collect(Collectors.toList()); 
8 System.out.println("全部學生身高比例:" + percentages);
複製代碼
相關文章
相關標籤/搜索