Java學習記錄(中級)——【4】、Lambda表示式
import java.util.ArrayList; import java.util.List; import java.util.Random; public class LambdaTest { public static void main(String[] args) { Random r = new Random(); List<Student> students = new ArrayList<Student>(); for (int i = 0; i < 5; i++) { int age = r.nextInt(6) + 6; students.add(new Student("student" + i, age, age % 6 + 1)); } System.out.println("初始化後的集合:"); System.out.println(students); System.out.println("滿足條件的學生有:"); // 這裡傳遞的第二個引數就是Lambda表示式 // 但實際上,它相當於一個簡寫的匿名類(實現了StudentChecker介面) // s是引數,【->】後是方法體,相當於以下的匿名類 filter(students, s -> s.age >= 10 && s.grade <= 6); /*StudentChecker checker = new StudentChecker() { @Override public boolean check(Student student) { return student.age >= 10 && student.grade <= 6; } }; // 這和上面的Lambda表示式是一樣的效果 filter(students, checker);*/ } public static void filter(List<Student> list, StudentChecker checker) { for (Student student : list) { if (checker.check(student)) { System.out.println(student); } } } } class Student { public String name; public int age; public int grade; public Student(String name, int age, int grade) { this.name = name; this.age = age; this.grade = grade; } @Override public String toString() { return "\n{Name : " + name + ",Age : " + age + ",Grade : " + grade + "}\n"; } } interface StudentChecker { boolean check(Student student); }
(1)、從匿名類演變成Lambda表示式
Lambda表示式可以看成是匿名類一點點演變過來:↓ ↓ ↓ [1]、匿名類的正常寫法
StudentChecker checker = new StudentChecker() {
@Override
public boolean check(Student student) {
return student.age >= 10 && student.grade <= 6;
}
};
[2]、把外面的殼子去掉,只保留方法引數和方法體,引數和方法體之間加上符號【 ->】
StudentChecker checker = (Student student)->{ return student.age >= 10 && student.grade <= 6; };
[3]、把return和{}去掉
StudentChecker checker = (Student student)->student.age >= 10 && student.grade <= 6;
[4]、把 引數型別和圓括號去掉(只有一個引數的時候,才可以去掉圓括號)
StudentChecker checker = student->student.age >= 10 && student.grade <= 6;
[5]、把checker作為引數傳遞進去
filter(students, checker);
[6]、直接把表示式傳遞進去
filter(students, s -> s.age >= 10 && s.grade <= 6);
(2)、匿名方法
與匿名類概念相比較,Lambda 其實就是匿名方法,這是一種把方法作為引數進行傳遞的程式設計思想。 雖然程式碼是這麼寫
filter(students, s -> s.age >= 10 && s.grade <= 6);
但是,Java會在背後,悄悄的,把這些都還原成匿名類方式。引入Lambda表示式,會使得程式碼更加緊湊,而不是各種介面和匿名類到處飛。
(3)、Lambda的弊端
Lambda表示式雖然帶來了程式碼的簡潔,但是也有其侷限性。 [1]、可讀性差,與囉嗦的但是清晰的匿名類程式碼結構比較起來,Lambda表示式一旦變得比較長,就難以理解 [2]、不便於除錯,很難在Lambda表示式中增加除錯資訊,比如日誌 [3]、版本支援,Lambda表示式在JDK8版本中才開始支援,如果系統使用的是以前的版本,考慮系統的穩定性等原因,而不願意升級,那麼就無法使用。Lambda比較適合用在簡短的業務程式碼中,並不適合用在複雜的系統中,會加大維護成本。
(4)、方法引用
[1]、引用靜態方法
[2]、引用物件方法
[3]、引用容器中的物件的方法
[4]、引用構造器
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class LambdaTest {
public static void main(String[] args) {
Random r = new Random();
List<Student> students = new ArrayList<Student>();
for (int i = 0; i < 5; i++) {
int age = r.nextInt(6) + 6;
students.add(new Student("student" + i, age, age % 6 + 1));
}
System.out.println("初始化後的集合:");
System.out.println(students);
// 引用靜態方法
System.out.println("引用靜態方法的結果:");
// filter(students, s->LambdaTest.checkStudent(s));
filter(students, LambdaTest::checkStudent);
// 引用物件方法
LambdaTest lambda = new LambdaTest();
System.out.println("引用物件方法的結果:");
// filter(students, s->lambda.checkObjectStudent(s));
filter(students, lambda::checkObjectStudent);
// 引用容器中的物件的方法
System.out.println("引用容器中的物件的方法的結果:");
// filter(students, s->s.matched());
filter(students, Student::matched);
System.out.println("無參的Lambda表示式使用:");
List list1 = getList(()->new ArrayList<>());
System.out.println(list1);
System.out.println("引用構造器的結果:");
List list2 = getList(ArrayList::new);
System.out.println(list2);
}
public static void filter(List<Student> list, StudentChecker checker) {
for (Student student : list) {
if (checker.check(student)) {
System.out.println(student);
}
}
}
public boolean checkObjectStudent(Student student) {
return student.age >= 10 && student.grade <= 6;
}
public static boolean checkStudent(Student student) {
return student.age >= 10 && student.grade <= 6;
}
public static List getList(Worker<List> w) {
return w.get();
}
}
class Student {
public String name;
public int age;
public int grade;
public Student(String name, int age, int grade) {
this.name = name;
this.age = age;
this.grade = grade;
}
@Override
public String toString() {
return "\n{Name : " + name + ",Age : " + age + ",Grade : " + grade + "}\n";
}
public boolean matched() {
return age >= 10 && grade <= 6;
}
}
interface StudentChecker {
boolean check(Student student);
}
interface Worker<T>{
T get();
}
執行結果:
(5)、聚合操作
[1]、傳統方式與聚合操作方式遍歷資料
遍歷資料的傳統方式就是使用for迴圈,然後條件判斷,最後打印出滿足條件的資料
for (Hero h : heros) {
if (h.hp > 100 && h.damage < 50)
System.out.println(h.name);
}
使用聚合操作方式,畫風就發生了變化:
heros
.stream()
.filter(h -> h.hp > 100 && h.damage < 50)
.forEach(h -> System.out.println(h.name));
[2]、Stream和管道的概念
要了解聚合操作,首先要建立Stream和管道的概念 Stream 和Collection結構化的資料不一樣,Stream是一系列的元素,就像是生產線上的罐頭一樣,一串串的出來。管道指的是一系列的聚合操作。
【管道】又分3個部分:
管道源: 在這個例子裡,源是一個List
中間操作: 每個中間操作,又會返回一個Stream,比如.filter()又返回一個Stream, 中間操作是“懶”操作,並不會真正進行遍歷。
結束操作: 當這個操作執行後,流就被使用“光”了,無法再被操作。所以這必定是流的最後一個操作。 結束操作不會返回Stream,但是會返回int、float、String、 Collection或者像forEach,什麼都不返回, 結束操作才進行真正的遍歷行為,在遍歷的時候,才會去進行中間操作的相關判斷
【注: 這個Stream和I/O章節的InputStream,OutputStream是不一樣的概念。】
[3]、管道源
把Collection切換成管道源很簡單,呼叫stream()就行了。
heros.stream()
但是陣列卻沒有stream()方法,需要使用
Arrays.stream(hs)
或
Stream.of(hs)
[4]、中間操作每個中間操作,又會返回一個Stream,比如.filter()又返回一個Stream, 中間操作是“懶”操作,並不會真正進行遍歷。
中間操作比較多,主要分兩類:
1、對元素進行篩選
2、轉換為其他形式的流
常見中間操作:↓ ↓ ↓
對元素進行篩選 | 轉換為其他形式的流 | ||
---|---|---|---|
操作名 | 功能 | 操作名 | 功能 |
filter | 匹配 | mapToDouble | 轉換為double的流 |
distinct | 去除重複(根據equals判斷) | map | 轉換為任意型別的流 |
sorted | 自然排序 | ||
sorted(Comparator<T>) | 指定排序 | ||
limit | 保留 | ||
skip | 忽略 |
[5]、結束操作
當進行結束操作後,流就被使用“光”了,無法再被操作。所以這必定是流的最後一個操作。 結束操作不會返回Stream,但是會返回int、float、String、 Collection或者像forEach,什麼都不返回,。 結束操作才真正進行遍歷行為,前面的中間操作也在這個時候,才真正的執行。
常見結束操作:↓ ↓ ↓
操作名 | 功能 |
---|---|
forEach() | 遍歷每個元素 |
min(Comparator<T>) | 取最小的元素 |
max(Comparator<T>) | 取最大的元素 |
toArray() | 轉換為陣列 |
count() | 總數 |
findFirst() | 第一個元素 |