歡迎交流java8新特性系列文章:http://www.reibang.com/nb/27231419 . [1][2][3][4][5]
一拳魁、簡介
???? java8于2014年發(fā)布疾瓮,相比于java7,java8新增了非常多的特性饲做,如lambda表達(dá)式线婚、函數(shù)式接口、方法引用盆均、默認(rèn)方法塞弊、新工具(編譯工具)、Stream API泪姨、Date Time API游沿、Optional等 。 當(dāng)前很多公司的老產(chǎn)品依然使用的java7肮砾,甚至開發(fā)人員開發(fā)新產(chǎn)品時依然沒有選擇升級诀黍, 寫關(guān)于java8系列文章的目的在于梳理和分享java8新增的主要特性,開發(fā)時也可以用作參考唇敞。
???? lambda表達(dá)式是java8新增的主要特性之一蔗草,lambda表達(dá)式又稱閉包或匿名函數(shù)咒彤,主要優(yōu)點在于簡化代碼疆柔、增強(qiáng)代碼可讀性咒精、并行操作集合等。至于是否使用旷档,有的同學(xué)覺得不適應(yīng)模叙,有的同學(xué)欲罷不能,見仁見智~
????技多不壓身鞋屈,本文將采用由淺入深的方式范咨,講解java8 lambda表達(dá)式的語法及使用,并附帶代碼進(jìn)行演示厂庇。
二渠啊、lambda語法
lambda的基本語法:
(parameters) -> expression
or
(parameters) ->{ statements; }
lambda表達(dá)式的特性:
- 可選類型聲明: 無需聲明參數(shù)類型,編譯器即可自動識別
- 可選的參數(shù)圓括號: 僅有一個參數(shù)時圓括號可以省略
- 可選的大括號:主體只包含一個語句時可省略大括號
- 可選的返回關(guān)鍵字:主體只包含一個表達(dá)式返回值并省略大括號時权旷,編譯器會自動return返回值替蛉;有大括號時,需要顯式指定表達(dá)式return了一個數(shù)值
特性示例:
//1拄氯、無參數(shù)躲查,返回值1
() -> 1
//2、無參數(shù)译柏,無返回值
() -> System.out.print("Java8 lambda.");
//3镣煮、1個參數(shù),參數(shù)類型為數(shù)字鄙麦,返回值為其值的5倍
x -> 5 * x
//4典唇、2個參數(shù),參數(shù)類型均為數(shù)字胯府,返回值為其差值
(x, y) -> x - y
//5介衔、2個參數(shù),指定參數(shù)類型均為int型盟劫,返回值為其差值
(int x, int y) -> x - y
//6夜牡、1個參數(shù),指定參數(shù)類型為String 侣签,無返回值
(String str) -> System.out.print(str)
個人建議塘装,從程序的嚴(yán)謹(jǐn)性角度出發(fā),盡量指明函數(shù)的參數(shù)類型影所,避免出錯1碾取!猴娩!
三阴幌、java8 lambda使用示例
前面我們講到lambda表達(dá)式的語法和特性勺阐,那么在java8中如何使用lambda表達(dá)式呢?我們先以用幾個示例來展現(xiàn)lambda表達(dá)式在java8中的使用矛双。
3.1?? java Runnable接口的lambda實現(xiàn)
????用lambdah代替匿名類是java8中l(wèi)ambda的常用形式渊抽,本文以開發(fā)同學(xué)經(jīng)常使用的Runnable接口匿名類為示例,演示如何用lambda表達(dá)式來代替匿名類:
- 在java8之前:
new Thread(new Runnable()
{
@Override
public void run()
{
System.out.println("No use lambda.");
}
}).start();
- 在java8之后:
new Thread(() -> System.out.println("Use lambda")).start();
????可以看到议忽,java8中利用lambda表達(dá)式大大簡化了代碼編寫懒闷。
????此處簡要提下,用lambda表達(dá)式代替匿名類的關(guān)鍵在于栈幸,匿名類實現(xiàn)的接口使用了java.lang.FunctionalInterface注解愤估,且只有一個待實現(xiàn)的抽象接口方法,如Runnable接口:
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
本文后面會詳細(xì)講解FunctionalInterface注解速址。
3.2?? java List迭代的lambda實現(xiàn)
????開發(fā)同學(xué)經(jīng)常會使用到集合類玩焰,并對集合類對象進(jìn)行迭代,以實現(xiàn)業(yè)務(wù)邏輯芍锚。
????java8中昔园,集合類的頂層接口java.lang.Iterable定義了一個forEach方法:
/* @param action The action to be performed for each element
* @throws NullPointerException if the specified action is null
* @since 1.8
*/
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
forEach方法可以迭代集合的所有對象,其參數(shù)為Consumer對象闹炉,Consumer類位于java.util.function包下蒿赢,我們看下其定義:
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
到此已經(jīng)很容易聯(lián)想到,我們可以采用lambda表達(dá)式來實現(xiàn)java8集合的迭代邏輯渣触,下面我們進(jìn)行示例:
- 在java8之前:
List<Integer> features = Arrays.asList(1,2);
for (Integer feature : features) {
System.out.println(feature);
}
- 在java8之后:
List<Integer> features = Arrays.asList(1,2);
features.forEach(n -> System.out.println(n));
上述邏輯還可以用java8的方法引用來表示:
List<Integer> features = Arrays.asList(1,2);
features.forEach(System.out::println);
方法引用也是java8的新特性羡棵,由::操作符標(biāo)示,詳細(xì)可參考方法引用的文章嗅钻,本文不贅述皂冰。
四、函數(shù)式接口
????在上一節(jié)中我們提到:"用lambda表達(dá)式代替匿名類的關(guān)鍵在于养篓,匿名類實現(xiàn)的接口使用了java.lang.FunctionalInterface注解秃流,且只有一個待實現(xiàn)的抽象接口方法", 這里的接口便是函數(shù)式接口。
????函數(shù)式接口(Functional Interface)是java8新增的特性柳弄,它是一個有且僅有一個抽象方法舶胀,但是可以有多個非抽象方法的接口。函數(shù)式接口可以被隱式轉(zhuǎn)換為lambda表達(dá)式碧注。
????Runnable接口是在JDK1.8之前已經(jīng)存在的接口嚣伐,在JDK1.8中加入了@FunctionalInterface注解,表示將其定義為一個函數(shù)式接口萍丐。在JDK1.8中定義的函數(shù)式接口還有:
- java.util.concurrent.Callable
- java.security.PrivilegedAction
- java.util.Comparator
- java.io.FileFilter
- java.nio.file.PathMatcher
- java.lang.reflect.InvocationHandler
- java.beans.PropertyChangeListener
- java.awt.event.ActionListener
- javax.swing.event.ChangeListener
JDK1.8新增加的函數(shù)式接口有java.util.function包下的接口轩端,典型的如上一節(jié)中提到的Consumer接口,感興趣的讀者可以閱讀JDK1.8的源碼逝变,在此不逐個列出基茵,在下一節(jié)本文還會列舉java.util.function包中典型的函數(shù)式接口的使用奋构。
到這里,可以總結(jié)出拱层,java8中用lambda表達(dá)式代替匿名內(nèi)部類弥臼,本質(zhì)上是將接口定義為函數(shù)式接口,并將函數(shù)式接口隱式轉(zhuǎn)換為lambda表達(dá)式舱呻、
五醋火、典型函數(shù)式接口的使用
????上一節(jié)我們理解了java8函數(shù)式接口的概念和定義方法,本節(jié)再列舉java.util.function幾個典型的函數(shù)式接口的使用,加深下函數(shù)式接口與lambda表達(dá)式結(jié)合的理解色解。
5.1??Predicate接口
5.1.1??Predicate接口的基本用法
Predicate接口適合用于過濾信卡,測試對象是否符合某個條件,Predicate接口源碼如下:
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate<T> negate() {
return (t) -> !test(t);
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
可以看到瘩欺,Predicate接口待實現(xiàn)的唯一抽象方法是 boolean test(T t) 方法。我們用Predicate接口實現(xiàn)從整數(shù)型數(shù)組中過濾正數(shù):
public static void main(String[] args)
{
List<Integer> numbers = Arrays.asList(-1, -2, 0, 4, 5);
filter(numbers, n -> n > 0);
}
public static void filter(List<Integer> numbers, Predicate<Integer> condition)
{
for (Integer number : numbers)
{
if (condition.test(number))
{
System.out.println("Eligible number: " + number);
}
}
}
運(yùn)行結(jié)果如下:
Eligible number: 4
Eligible number: 5
對數(shù)組的迭代,還可以使用Stream API的方式:
public static void main(String[] args)
{
List<Integer> numbers = Arrays.asList(-1, -2, 0, 4, 5);
numbers.stream().filter(n -> n > 0).forEach(n -> System.out.println("Eligible number: " + n));
}
上面的代碼采用Stream API + Predicate接口 + Consumer接口的方式實現(xiàn)了同樣的功能怎栽,代碼量大大減少。Stream API(java.util.stream)同樣是java8的新特性宿饱,將真正的函數(shù)式編程風(fēng)格引入到j(luò)ava語言中熏瞄,進(jìn)一步簡化了代碼。
5.1.2??Predicate接口的進(jìn)階用法
????我們再看上一節(jié)提到的Predicate接口的源碼谬以,發(fā)現(xiàn)它有三個default關(guān)鍵字定義的方法强饮,分別為and()、negate()为黎、or()三個方法邮丰,顧名思義,它們類似于邏輯操作&&铭乾、!剪廉、||,用于生成新的Predicate對象炕檩。
????以上一節(jié)的數(shù)組為例斗蒋,我們用and操作過濾出數(shù)組中大于-1且小于5的數(shù)字:
public static void main(String[] args)
{
List<Integer> numbers = Arrays.asList(-1, -2, 0, 4, 5);
filter(numbers, n -> n > -1 , n -> n < 5);
}
public static void filter(List<Integer> numbers, Predicate<Integer> first, Predicate<Integer> second)
{
for (Integer number : numbers)
{
if (first.and(second).test(number))
{
System.out.println("Eligible number: " + number);
}
}
}
結(jié)果為:
Eligible number: 0
Eligible number: 4
????上例用and()方法將兩個Predicate對象進(jìn)行and運(yùn)算,同理negate()笛质、or()方法的使用也很簡單泉沾,在此不再贅述。
5.2 ??Stream API
????Stream API將處理的數(shù)據(jù)源看做一種Stream(流)经瓷,Stream(流)在Pipeline(管道)中傳輸和運(yùn)算爆哑,Stream API結(jié)合lambda表達(dá)式可以很方便的對集合進(jìn)行篩選、排序等運(yùn)算舆吮,由于篇幅較大揭朝,請閱讀【java8新特性】Stream API詳解队贱,本文中不詳細(xì)講解。
5.3 ??Optional
????Optional類是Java8為了解決null值判斷問題潭袱,借鑒google guava類庫的Optional類而引入的一個同名Optional類柱嫌,使用Optional類配合lambda表達(dá)式可以避免顯式的null值判斷,并實現(xiàn)很多類似Stream API的功能屯换,由于篇幅較大编丘,請閱讀【java8新特性】Optional詳解,本文中不詳細(xì)講解彤悔。
五嘉抓、注意事項
- lambda表達(dá)式可以使用方法引用,當(dāng)且僅當(dāng)主體中不修改lambda表達(dá)式提供的參數(shù)晕窑,如第三章提到的兩種寫法
features.forEach(n -> System.out.println(n));
等價于
features.forEach(System.out::println);
而如果對參數(shù)有任何修改時不能使用方法引用抑片,如:
features.forEach(n -> System.out.println(n+1));
- lambda與匿名類的聯(lián)系和區(qū)別
聯(lián)系:
1) 都可以訪問final或effectively final局部變量。
2) 生成的對象都可以調(diào)用實現(xiàn)的接口方法杨赤。
區(qū)別:
1) this指針的指向不同敞斋。我們知道匿名類的this指針指向匿名類,而lambda表達(dá)式的this指針指向的是包圍lambda表達(dá)式的類疾牲。
2) 編譯方式不同植捎。lambda在編譯器內(nèi)部被翻譯為私有方法,并使用了Java 7的 invokedynamic 字節(jié)碼指令來動態(tài)綁定這個方法
3) 實現(xiàn)的接口限制有區(qū)別阳柔。匿名類可以為任意接口創(chuàng)建實例焰枢,只要實現(xiàn)接口所有的抽象方法即可;而lambda表達(dá)式只能實現(xiàn)函數(shù)式接口(只有一個必須實現(xiàn)的抽象方法)盔沫。
4) 接口默認(rèn)方法的調(diào)用權(quán)限不同医咨。匿名類實現(xiàn)的抽象方法允許調(diào)用接口中的默認(rèn)方法,而lambda表達(dá)式不能調(diào)用接口中的默認(rèn)方法架诞。