Java 8 新特性,Lambda,方法引用,Stream,Optional

在C++、Python等語言裏都有Lambda表達式,Java 8也新增了這一特性。html

在java給變量賦值是這樣的:java

int num=123;
String str="hello world!";
Boolean flag=str.startsWith("h");

若是你想把「一塊代碼」賦給一個Java變量,應該怎麼作呢?api

好比,我想把右邊那塊代碼,賦給一個叫作aBlockOfCode的Java變量數組

aBlockOfCode=public void doSomeShit(String s){
               System.out.println(s);
             }

在Java 8以前,這個是作不到的。可是Java 8問世以後,利用Lambda特性,就能夠作到了,下面咱們一步步簡化oracle

 

這樣,咱們就成功的很是優雅的把「一塊代碼」賦給了一個變量。而「這塊代碼」,或者說「這個被賦給一個變量的函數」,就是一個Lambda表達式ide

 

可是這裏仍然有一個問題,就是變量aBlockOfCode的類型應該是什麼?函數

在Java 8裏面,全部的Lambda的類型都是一個接口,而Lambda表達式自己,也就是」那段代碼「,須要是這個接口的實現。這是我認爲理解Lambda的一個關鍵所在,簡而言之就是,Lambda表達式自己就是一個接口的實現。直接這樣說可能仍是有點讓人困擾,咱們繼續看看例子。咱們給上面的aBlockOfCode加上一個類型:oop

 

這種只有一個接口函數須要被實現的接口類型,咱們叫它」函數式接口「。爲了不後來的人在這個接口中增長接口函數致使其有多個接口函數須要被實現,變成"非函數接口」,咱們能夠在這個上面加上一個聲明@FunctionalInterface, 這樣別人就沒法在裏面添加新的接口函數了:優化

@FunctionalInterface
public interface MyLambdaInterface {
	void doSomeShit(String s);
}


這樣,咱們就獲得了一個完整的Lambda表達式聲明:ui

public void test(){
        MyLambdaInterface aBlockOfCode= (s) -> System.out.println(s);
}

 

在Java 7以前都是這樣接口的實現是這樣

public class MyInterfaceImpl implements MyLambdaInterface{

	@Override
	public void doSomeShit(String s) {
		System.out.println(s);
	}
	
}

 

這兩種寫法本質上是等價的。可是顯然,Java 8中的寫法更加優雅簡潔。而且,因爲Lambda能夠直接賦值給一個變量,咱們就能夠直接把Lambda做爲參數傳給函數, 而傳統的Java必須有明確的接口實現的定義,初始化才行

public static void enact(MyLambdaInterface myLambda,String s){
		myLambda.doSomeShit(s);
}
public static void main(String[] args) {
		enact(s -> System.out.println(s) ,"myLambda");
		System.out.println("執行");
}

執行結果:

myLambda
執行

 

Lambda結合FunctionalInterface Lib, forEach, stream(),method reference等新特性可使代碼變的更加簡潔!

假設Person的定義和List<Person>的值都給定

public class Person {

	private String firstName;
	
	private String lastName;
	
	private int age;

	public Person(String lastName, String firstName,int age) {
		this.lastName = lastName;
		this.firstName = firstName;
		this.age = age;
	}
    ...省略getset
}
List<Person> guiltyPersons=Arrays.asList(
				new Person("Yixing", "Zhao",25),
				new Person("Yanggui", "Li",30 ),
				new Person("Chao", "Ma", 29)
				);

如今須要你打印出guiltyPersons List裏面全部LastName以"Z"開頭的人的FirstName。

原生態Lambda寫法:定義兩個函數式接口,定義一個靜態函數,調用靜態函數並給參數賦值Lambda表達式。

@FunctionalInterface
public interface NameChecker {
	boolean check(Person p);
}
@FunctionalInterface
public interface Executor {
	void execute(Person p);
}
public static void checkAndExecute(List<Person> personList,NameChecker nameChecker,Executor executor ){
		for (Person person : personList) {
			if(nameChecker.check(person)){
				executor.execute(person);
			}
		}
}
public class Test {

	public static void main(String[] args) {
		enact(s -> System.out.println(s) ,"myLambda");
		System.out.println("執行");
		
		List<Person> guiltyPersons=Arrays.asList(
				new Person("Yixing", "Zhao",25),
				new Person("Yanggui", "Li",30 ),
				new Person("Chao", "Ma", 29)
				);
		
		checkAndExecute(guiltyPersons,p -> p.getFirstName().startsWith("Z") , p -> System.out.println(p.getFirstName()));
	}

執行結果:

myLambda
執行
Zhao

這個代碼實際上已經比較簡潔了,可是咱們還能夠更簡潔麼?

 

固然能夠。在Java 8中有一個函數式接口的包,裏面定義了大量可能用到的函數式接口(java.util.function (Java Platform SE 8 ))。因此,咱們在這裏壓根都不須要定義NameChecker和Executor這兩個函數式接口,直接用Java 8函數式接口包裏的Predicate<T>和Consumer<T>就能夠了——由於他們這一對的接口定義和NameChecker/Executor實際上是同樣的。

@FunctionalInterface
public interface Predicate<T> {
	boolean test(T t);
}
@FunctionalInterface
public interface Consumer<T> {
	void accept(T t);
}

第一步簡化 - 利用函數式接口包:

public static void checkAndExecute(List<Person> personList,Predicate<Person> predicate,Consumer<Person> consumer){
		for (Person person : personList) {
			if(predicate.test(person)){
				consumer.accept(person);
			}
		}
}
checkAndExecute(guiltyPersons,p -> p.getFirstName().startsWith("Z") , p -> System.out.println(p.getFirstName()));

靜態函數裏面的for each循環實際上是很是礙眼的。這裏能夠利用Iterable自帶的forEach()來替代。forEach()自己能夠接受一個Consumer<T> 參數。

第二步簡化 - 用Iterable.forEach()取代foreach loop

public static void checkAndExecute(List<Person> personList,Predicate<Person> predicate,Consumer<Person> consumer){

		personList.forEach(p -> {if(predicate.test(p)) consumer.accept(p);});
}
checkAndExecute(guiltyPersons,p -> p.getFirstName().startsWith("Z") , p -> System.out.println(p.getFirstName()));

因爲靜態函數其實只是對List進行了一通操做,這裏咱們能夠甩掉靜態函數,直接使用stream()特性來完成。stream()的幾個方法都是接受Predicate<T>,Consumer<T>等參數的(java.util.stream (Java Platform SE 8 ))。

這裏解釋下stream

什麼是 Stream?

Stream(流)是一個來自數據源的元素隊列並支持聚合操做

  • 元素是特定類型的對象,造成一個隊列。 Java中的Stream並不會存儲元素,而是按需計算。
  • 數據源 流的來源。 能夠是集合,數組,I/O channel, 產生器generator 等。
  • 聚合操做 相似SQL語句同樣的操做, 好比filter, map, reduce, find, match, sorted等。

和之前的Collection操做不一樣, Stream操做還有兩個基礎的特徵:

  • Pipelining: 中間操做都會返回流對象自己。 這樣多個操做能夠串聯成一個管道, 如同流式風格(fluent style)。 這樣作能夠對操做進行優化, 好比延遲執行(laziness)和短路( short-circuiting)。
  • 內部迭代: 之前對集合遍歷都是經過Iterator或者For-Each的方式, 顯式的在集合外部進行迭代, 這叫作外部迭代。 Stream提供了內部迭代的方式, 經過訪問者模式(Visitor)實現。

 

第三步簡化 - 利用stream()替代靜態函數

public static void checkAndExecute(List<Person> personList,Predicate<Person> predicate,Consumer<Person> consumer){
		personList.stream()
							.filter(p -> p.getFirstName().startsWith("Z"))
							.forEach(p -> System.out.println(p.getFirstName()));
}

對比最開始的Lambda寫法,這裏已經很是很是簡潔了。可是若是,咱們要求變一下,變成print這我的的所有信息,及p -> System.out.println(p); 那麼還能夠利用方法引用經過方法的名字來指向一個方法。

方法引用可使語言的構造更緊湊簡潔,減小冗餘代碼。

方法引用使用一對冒號 ::

Java 中 4 種不一樣方法的引用

構造器引用:它的語法是Class::new,或者更通常的Class< T >::new

靜態方法引用:它的語法是Class::static_method

特定類的任意對象的方法引用:它的語法是Class::method

特定對象的方法引用:它的語法是instance::method

例如:

public static void main(String[] args) {
		
		List names = new ArrayList();
        
	      names.add("Google");
	      names.add("Taobao");
	      names.add("Baidu");
	      names.add("Sina");
	        
	      names.forEach(System.out::println);
}

實例中咱們將 System.out::println 方法做爲靜態方法來引用

第四步簡化 - 若是是println(p),則能夠利用方法的引用代替forEach中的Lambda表達式

personList.stream().filter(p -> p.getFirstName().startsWith("Z")).forEach(System.out::println)

 

Lambda配合Optional<T>可使Java對於null的處理變的異常優雅

Optional 類是一個能夠爲null的容器對象。若是值存在則isPresent()方法會返回true,調用get()方法會返回該對象。

Optional 是個容器:它能夠保存類型T的值,或者僅僅保存null。Optional提供不少有用的方法,這樣咱們就不用顯式進行空值檢測。

Optional 類的引入很好的解決空指針異常。

 

例如:假設咱們有一個person object,對這個對象進行null判斷,Java 7都是以下:

private static final Person UNKNOWN_PERSON=null;

public static Person testOptional(){
		Person person = null;
		if(person != null){
			return person;
		}else{
			return UNKNOWN_PERSON;
		}
}

Java 8 則使用Optional 特性,具體以下:

private static final Person UNKNOWN_PERSON=null;

public static Person testOptional(){
		
		Person person = null;
		// Optional.ofNullable - 容許傳遞爲 null 參數
		Optional<Person> personOpt=Optional.ofNullable(person);
		
//		if(personOpt.isPresent()){
//			return personOpt.get();
//		}else{
//			return UNKNOWN_PERSON;
//		}
		//最後優化爲最簡化的
		personOpt.ifPresent(System.out::println);
		return personOpt.orElse(UNKNOWN_PERSON);
}

 

Java 8 的新特性先介紹到這裏,與你們共同進步

文章參考:https://www.zhihu.com/question/20125256/answer/324121308

相關文章
相關標籤/搜索