1. 程式人生 > >Java學習記錄(中級)——【4】、Lambda表示式

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() 第一個元素