背景
java9的一再推遲發布,似乎讓我們恍然想起離發布java8已經過去了三年之久,java8應該算的上java語言在歷代版本中變化最大的一個版本了,最大的新特性應該算得上是增加了lambda表達式,借助lambda表達式,我們可以編寫出性能更好,可讀性更強的代碼,更重要的,它給我們帶來了一種編程思想的改革,作為一個活躍了20多年的編程語言,java能夠做到與時俱進,擁抱新變化,實屬不易,雖然現在很多公司包括我所在的公司尚未把jdk升級到最新的版本,但關注並學習每一個版本帶來的新變化,是每個java程序員都該做的事,因為正是這些變化代表了這門語言未來的發展方向.可以預想,幾年以后lambda表達式必將在整個java開發領域完成普及和應用,因此現在,對我們來說,是時候把lambda表達式學起來了,廢話就說這么多,下面就讓我們了解一下lambda的強大.
入門
從外部迭代到內部迭代
拿一個簡單的例子來說,如果我們需要遍歷一個List集合,需要怎么做,一般是下邊這樣:
List<String> lists=Arrays.asList("a","b","c","d"); for (String s:lists){ System.out.println(s); }
java8給我提供了Collection.forEach()方法,於是我們可以這樣編程:
List<String> lists=Arrays.asList("a","b","c","d");
lists.forEach(i-> System.out.println(i));
這是一個最簡單的例子,但是我們能看出來一點,之前遍歷是寫在外部的,即客戶端代碼,而使用了lambda表達式將迭代的操作放到了forEach()方法中,即封裝到了java類庫中,這樣對外界是沒有任何侵入性的,現在不用糾結於這么一行代碼到底是怎么實現的,是什么意思,下面會慢慢剖析.
lambda表達式基礎
定義
在數學計算的角度,lambda表達式指的是一種函數,但在java中現在還不能編寫一個獨立的函數,畢竟java不是函數式編程語言,在java中,lambda可以看做一種匿名方法,一種格式非常簡潔的匿名方法,可以省略修飾符,返回類型等.
語法
lambda表達式可以分解成三部分來看: (1)參數列表 (2)-> (3)lambda體,如上面例子中的
i -> System.out.println(i))
左邊是參數列表,lambda可以接收任意多個參數,當參數數量大於1時,需要將參數括起來,如
(i,j)->System.out.println(i+j))
如果不需要指定參數,需要使用()來表示無參,如
()->System.out.println("hello lambda")
可以看到我們並沒有聲明參數的數據類型,這是因為很多情況下,編譯器能從上下文中推導出數據的參數類型,當然我們可以顯示的指定類型
(int i,int j)->System.out.println(i+j))
函數箭頭的右側是lambda體,上面的例子中只有一行代碼,當有多行代碼時,需要使用{}括起來,如
(i,j) ->{System.out.println(i);System.out.println("----"); }
如果lambda體中的表達式有返回值,需要使用return來返回,也可以后面跟一個參數來中止lambda體,
(i,j)->return i+j 或 (i,j)->i+j;
函數式接口
理解函數式接口,是學習lambda表達式的關鍵,函數式接口的定義其實比較簡單,對於任意一個接口(interface),如果他只包含一個(抽象)方法,那么這個接口就可以稱之為函數式接口,這種接口被@FunctionalInterface注解標示,現在我們來回憶一下在java8以前,我們經常碰到的函數式接口.
public interface Runnable { void run(); }
public interface Callable<V> { V call() throws Exception; }
public interface Comparator<T> { int compare(T o1, T o2); boolean equals(Object obj); }
現在我們以第三個方法Comparator來舉例,以往我們使用這個類來實現自定義功能一般是這樣的,這里假設我們需要以Person的age來對Person對象進行排列,一般這樣實現:
1 Person[] persons=new Person[]{new Person("a",7),new Person("b",9),new Person("c",5)}; 3 Arrays.sort(persons, new Comparator<Person>() { 4 public int compare(Person o1, Person o2) { 5 return Integer.compare(o1.getScore(),o2.getScore()); 6 } 7 }); 9 System.out.println(Arrays.toString(persons));
Person類這里不再給出定義,Person有兩個屬性name和age.運行程序,在打印台我們可以看到輸出順序是 c,a,b
我們已經知道Comparator是一個函數式接口,我們可以使用lambda表達式來得到一個實例,現在我們來觀察sort方法的第二個參數,它接受一個Comparator對象用於定義比較的規則,new Comparator<Person>表示new一個Comparatotr對象,這不是廢話嗎,sort方法已經定義了第二個參數必須為Comparator對象,因此這段代碼是可以省略的,因為聰明的編譯器可以從上下文中推測出來,那讓我們來看一下,還有什么東西是編譯器能夠推測出來的,我們稍加思索便會發現,public int compare,返回類型,o1,o2的數據類型,都是可以推測出來的,因為只有這一個方法嘛,那么使用lambda改造一下的模樣是這樣的.
Person[] persons=new Person[]{new Person("a",7),new Person("b",9),new Person("c",5)};
Arrays.sort(persons, ( o1, o2) -> Integer.compare(o1.getScore(),o2.getScore()) );
System.out.println(Arrays.toString(persons));
一個匿名內部類對象就這樣被我們用lambda表達式改造成上面這樣子,我們可以這么理解,上面紅色加粗的代碼,就相當於創建了一個Comparator對象並重寫了compare方法內容.這時候,你該問了,這不就是一個匿名內部類的語法糖嗎?事實上,從語法上看,的確很像一個語法糖,但二者之間存在很多顯著的差異.這里暫時不做深入討論.
在java8中引入了一個新的包java.util.function,這個包中定義了很多函數式接口用於支持lambda表達式,下面簡單介紹幾個常見接口.
Function接口,有一個參數,並且返回一個結果,觀察該接口,我們發現Function能接受一個T類型的參數並返回R類型,這里使用泛型有更好的擴展性.
@FunctionalInterface public interface Function<T, R> { R apply(T t); }
下面我們使用Function接口來寫一個小例子,如下:
public static int changeTheValue(int value ,Function<Integer,Integer> function){ return function.apply(value); } @Test public void testChangeTheValue(){ int value=10; int result1=changeTheValue(value,i->i+5); System.out.println("result1:"+result1); int result2=changeTheValue(value,(i)->i*30); System.out.println("result2"+result2); }
上述代碼中,我們定義了一個方法changeTheValue,接收兩個參數,第一個是需要修改的value,第二個是一個function對象,在下面的測試方法中,我們根據自己的想法靈活的改變第二個參數,可以得到對象的結果.運行測試,可以得到result1:15 result2:300.
除了Function接口之外,類庫還提供了一個Consumer接口,他和Function接口唯一的區別就是,方法沒有返回值,還記得最上面我們遍歷集合的時候使用的list.forEach方法嗎,它接受的就是一個Consumer實例.
類庫給我們預定義的函數式接口,當然不止上面提的這兩個,還有一個Predicate,他返回一個boolean類型的值,用來判斷某項條件是否滿足,經常用來進行篩濾操作,其他接口這里也不再討論.
總結
我們初步認識了lambda的簡單語法結構: 參數列表->lambda體
lambda的函數式接口(目標類型),java8引進了用於支持lambda表達式的java.util.Function接口
lambda表達式的應用場景之一是替換之前廣泛使用的匿名內部類的常規語法.
參考資料
<精通lambda表達式:java多核編程>