JDK1.8新特性:Lambda表示式語法和內建函式式介面

通往架構師的路上發表於2019-01-04

前言:Lambda 是一個匿名函式,我們可以把 Lambda表示式理解為是一段可以傳遞的程式碼(將程式碼像資料一樣進行傳遞)。可以寫出更簡潔、更靈活的程式碼。作為一種更緊湊的程式碼風格,使Java的語言表達能力得到了提升。
首先我們來看下一個簡單從匿名類到Lambda的例子,體會下Lambda的特點
在java8以前我們通過實行Runable介面建立執行緒

new Thread(new Runnable() {
	@Override
	public void run() {
		System.out.println("Hello Runnable Run");
	}
}).start();

利用java8的Lambad語法我們可以更方便的實現相同的功能

new Thread(() -> System.out.println("Hello Runnable Run")).start();

例2:在這裡我們定義一個需求:通過年齡或者工資過濾員工資訊
基於此業務場景我們先定義一個員工物件

package cq.java8.lambda;
public class Employ {
	private Integer age; //年齡
	private Double salary; //工資
	private String name; //姓名
	public Employ() {
		super();
	}
	public Employ(Integer age, Double salary, String name) {
		super();
		this.age = age;
		this.salary = salary;
		this.name = name;
	}
	public Integer getAge() {
		return age;
	}
	public void setAge(Integer age) {
		this.age = age;
	}
	public Double getSalary() {
		return salary;
	}
	public void setSalary(Double salary) {
		this.salary = salary;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	@Override
	public String toString() {
		return "Employ [age=" + age + ", salary=" + salary + ", name=" + name + "]";
	}
}

我們簡單運用策略模式實現此類業務需求,首先定義一個公共的介面

//定義一個介面
public interface EmployStrategy {
	//定義一個篩選方法
	public boolean filterEmploy(Employ employ);
}

實現上面的公共介面過濾年齡大於25歲的員工

//定義一個根據年齡篩選的實現類
public class EmployFilterByAge implements EmployStrategy{
	@Override
	public boolean filterEmploy(Employ employ) {
		return  employ.getAge() > 25 ? true : false;
	}
}

實現上面的公共介面實現年齡工資大於6000的員工

//定義一個根據工資篩選的實現類
public class EmployFilterBySalary implements EmployStrategy{
	@Override
	public boolean filterEmploy(Employ employ) {
		return  employ.getSalary() > 6000 ? true : false;
	}
}

功能實現

public class TestLambda {
	
	List<Employ> employs = Arrays.asList(
		new Employ(18, 5555.55, "張三"),
		new Employ(22, 6666.66, "李四"),
		new Employ(33, 3333.33, "王五"),
		new Employ(44, 9999.99, "趙六"),
		new Employ(55, 8888.88, "田七"));
	
	//定義過濾員工返回新員工集合方法
	public List<Employ> getEmployByFilter(List<Employ> employs, EmployStrategy employStrategy){
		List<Employ> newEmploys = new ArrayList<Employ>();
		for(Employ employ : employs) {
			if(employStrategy.filterEmploy(employ)) {
				newEmploys.add(employ);
			}
		}
		return newEmploys;
	}
	
	//通過策略模式實現
	@Test
	public void test() {
		//根據年齡過濾
		List<Employ> ageFilterEmploys = getEmployByFilter(employs, new EmployFilterByAge());
		System.out.println("年齡大於25的員工:");
		for (Employ e : ageFilterEmploys) {
			System.out.println(e);
		}
		
		//根據工資過濾
		List<Employ> salaryFilterEmploys = getEmployByFilter(employs, new EmployFilterBySalary());
		System.out.println("工資大於6000的員工:");
		for (Employ e : salaryFilterEmploys) {
			System.out.println(e);
		}
	}
}

執行結果如下:

年齡大於25的員工:
Employ [age=33, salary=3333.33, name=王五]
Employ [age=44, salary=9999.99, name=趙六]
Employ [age=55, salary=8888.88, name=田七]
工資大於6000的員工:
Employ [age=22, salary=6666.66, name=李四]
Employ [age=44, salary=9999.99, name=趙六]
Employ [age=55, salary=8888.88, name=田七]

此實現方式進行了解耦,弊端就是每次新增需求需要加新的介面實現。當然我們也可以用匿名內部類的方式實現,當然我們也可以通過匿名內部類的方式實現,如下:

//通過匿名內部類實現
@Test
public void test2() {
	//根據年齡過濾
	List<Employ> ageFilterEmploys = getEmployByFilter(employs, new EmployStrategy() {
		@Override
		public boolean filterEmploy(Employ employ) {
			return employ.getAge() > 25 ? true : false;
		}
	});
	System.out.println("年齡大於25的員工:");
	for (Employ e : ageFilterEmploys) {
		System.out.println(e);
	}
	//根據年齡過濾
	List<Employ> salaryFilterEmploys = getEmployByFilter(employs, new EmployStrategy() {
		@Override
		public boolean filterEmploy(Employ employ) {
			return  employ.getSalary() > 6000 ? true : false;
		}
	});
	System.out.println("工資大於6000的員工:");
	for (Employ e : salaryFilterEmploys) {
		System.out.println(e);
	}
}

從第一個建立執行緒的例子中,延用相同的思路我們利用java8的lambda語法實現上述功能如下(介面還是使用EmployStrategy)

//Lambda表示式實現
@Test
public void test3() {
	List<Employ> ageFilterEmploys = getEmployByFilter(employs, e -> e.getAge() > 25);
	System.out.println("年齡大於25的員工:");
	for (Employ e : ageFilterEmploys) {
		System.out.println(e);
	}
	
	List<Employ> salaryFilterEmploys = getEmployByFilter(employs, e -> e.getSalary() > 6000);
	System.out.println("年齡大於25的員工:");
	for (Employ e : salaryFilterEmploys) {
		System.out.println(e);
	}
}

從上文實現員工過濾的例項中我們可以看到Lambda表單式可以大大簡化程式碼。在這裡我們也可以對Lambda表示式有個簡單的瞭解和定義:可以將lambda表示式定義為一種 簡潔、可傳遞的匿名函式,首先我們需要明確lambda表示式本質上是一個函式,雖然它不屬於某個特定的類,但具備引數列表、函式主體、返回型別,以及能夠丟擲異常;其次它是匿名的,lambda表示式沒有具體的函式名稱;lambda表示式可以像引數一樣進行傳遞,從而極大的簡化程式碼的編寫。上文所闡述的其實就是在告訴大家,我們為什麼要使用Lambda表示式,接下來我們詳細瞭解下Lambda語法。

1.表示式介紹
Lambda表示式在Java語言中引入了新的語法元素和操作符。這個操作符為 “ ->” , 該操作符被稱為 Lambda 操作符或剪頭操作符。它將 Lambda 分為兩個部分:
左側: 指定了 Lambda 表示式需要的所有引數
右側: 指定了 Lambda 體,即 Lambda 表示式要執行的功能。

2.語法介紹
(2.1)語法格式一:無參,無返回值,Lambda體只需要一條語句
語法格式及案例:() -> System.out.println("無參無返回值Lambda表示式")

@Test
public void test4() {
	new Thread(() -> System.out.println("無參無返回值Lambda表示式")).start();
}

(2.2)語法格式二:一個引數,無返回值,此時引數的小括號可省略
語法格式及案例:args -> System.out.println(args)

@Test
public void test5() {
	Consumer<String> con = x -> System.out.println(x);
	con.accept("新年好");
}

(2.3)語法格式三:一個引數,有返回值,當Lambda體只有一條語句是return與大括號可省略
語法格式及案例:(args) -> return args引數處理後的結果

@Test
public void test6() {
	//接收一個引數返回其2的倍數
	Function<Integer, Integer> fun = (x) -> 2*x;
	fun.apply(5);
}

(2.4)語法格式四:兩個引數,無返回值,當引數的資料型別可由編譯器通過上下文推薦出來時,可省
語法格式及案例:(args1,args2) -> {兩個引數處理,無返回值}

@Test
public void test7() {
	//求兩個數的和並列印
	BiConsumer<Integer, Integer> biConsumer = (x, y) -> System.out.println(x+y);
	biConsumer.accept(10, 20);
}

(2.5)語法格式五:兩個引數,有返回值,Lambda體只有1條語句
語法格式及案例:(args1, args2) -> return args1和args2處理後返回

@Test
public void test8() {
	Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
	int r = com.compare(5, 10);
	System.out.println(r);
}

(2.6)語法格式五:兩個引數,有返回值,Lambda體有多條語句
語法格式及案例:
(args1, args2) -> { System.out.println("輸入兩個引數"); return args1和args2處理後返回 }

@Test
public void test9() {
	Comparator<Integer> com = (x, y) ->{
		System.out.println("輸入兩個引數");
		return Integer.compare(x, y);
	} 
	int r = com.compare(5, 10);
	System.out.println(r);
}

3.JDK1.8提供的內建函式式介面
上文中我們定義了EmployStrategy 介面,並且只包含一個抽象方法的介面,可以稱之為函式式介面,我們可以在任意的函式式介面上使用@FunctionalInterface註解,這樣做可以檢查它是否是一個函式式介面,我們可以通過Lambda表示式建立該介面物件。
Java 內建四大核心函式式介面

介面型別 函式式介面 引數型別 返回值 用途
消費型 Consumer<T> T 對型別為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)

其他函式式介面

介面型別 函式式介面 引數型別 返回值 用途
函式型 BiFunction<T,U,R> T,U R 對型別為T,U的物件應用操作,返回R型別的結果方法:R apply(T t,U u)
函式型 UnaryOperator<T> T T 對型別為T的物件應用操作(一元運算),返回T型別的結果,方法:T apply(T t),是Function的子類
函式型 BinaryOperator<T> T,T T 對型別為T的應用操作(二元運算),並返回T型別結果。結果是R型別的物件,方法R apply(T t1, T t2),是BiFunction的子類
消費型 BiConsumer<T,U> T,U 對型別為T,U的物件應用操作,方法void accept(T t, U u)

在這裡我們著重介紹四大核心函式式介面的使用
(3.1) Consumer<T> 方法 void accept(T t)
在這裡我們定義一個需求:執行一段業務邏輯無引數無返回值,用此內建函式式介面實現如下:

public class TestConsumer {
	//定義一個花錢的消費方法,引數:錢、消費函式介面
	public void costMoney(double money, Consumer<Double> con) {
		//花錢
		con.accept(money);
	}
	@Test
	public void test() {
		//引數m,無返回值
		costMoney(1000, m -> System.out.println("花了"+m+"元錢,很開心!"));
	}
}

(3.2) Supplier<T> 方法 T get()
在這裡我們定義一個需求作為例項:獲取指定數量的隨機數,並放入集合,用此內建函式式介面實現如下:

public class TestSupplier {
	//獲取指定數量的隨機數,並放入集合返回
	public List<Integer> getRandList(int num, Supplier<Integer> sup){
		List<Integer> randList = new ArrayList<>();
		for(int i = 0; i < num; i++) {
			//獲取隨機數並放入集合
			randList.add(sup.get());
		}
		return randList;
	}
	
	@Test
	public void test() {
		List<Integer> randList = getRandList(10, () -> (int)(Math.random()*100));
		System.out.println(randList);
		//列印結果:[76, 43, 23, 12, 42, 96, 84, 82, 4, 93]
	}
}

(3.3) Function<T, R> 方法 R apply(T t)
在這裡我們定義一個需求作為例項:去除字串空格,用此內建函式式介面實現如下:

public class TestFunction {
	//定義獲取字串方法
	public String getStr(String string, Function<String, String> fun) {
		return fun.apply(string);
	}
	@Test
	public void test() {
		String string = "   中國人民萬歲,中華人名共和國萬歲";
		//去掉空格
		string = getStr(string, (t) -> t.trim());
		System.out.println(string);
		//列印結果:中國人民萬歲,中華人名共和國萬歲
	}
}

(3.4) Predicate<T> 方法 boolean test(T t)
在這裡我們定義一個需求:從集合中獲取部分滿足條件的元素放入新集合並返回,用此內建函式式介面實現如下:

public class TestPredicate {
	//從集合中獲取部分滿足條件的元素放入新集合並返回
	public List<Integer> getList(List<Integer> list, Predicate<Integer> pre){
		List<Integer> listNew = new ArrayList<>();
		for(Integer v : list) {
			if(pre.test(v)) {
				listNew.add(v);
			}
		}
		return listNew;
	}
	@Test
	public void test() {
		List<Integer> list = Arrays.asList(39,8,12,99,22);
		//獲取值小於30,並返回新集合
		List<Integer> listNew = getList(list, (value) -> value < 30);
		System.out.println("小於30:"+listNew);
		//獲取值大於40,並返回新集合
		listNew = getList(list, value -> value > 40);
		System.out.println("大於40:"+listNew);
		//列印結果
		//小於30:[8, 12, 22]
		//大於40:[99]
	}
}

相關文章