??自2014年3月18日發(fā)布以來,四年的時間里各大廠商已經(jīng)逐漸采用Java8重構(gòu)工程軟件,招聘需求里也已將其作為Java開發(fā)工程師的必備技能。掌握Java8新特性成了軟件開發(fā)人員不得不重視的能力。本人結(jié)合相關(guān)教程及書籍學習了Java8新特性,特此分享。
Lambda表達式(閉包)
??Lambda(λ,希臘數(shù)字中的第十一個數(shù)字,由于λ演算式的存在,它也代表閉包)表達式是JAVA 8中最令人激動的新特性,它使得Java編程中出現(xiàn)了函數(shù)式編程的概念,在其他高級語言中如Python(解釋型、動態(tài)數(shù)據(jù)類型、面向?qū)ο螅?,常用Lambda表達式創(chuàng)建匿名函數(shù)1。Lambda表達式允許我們將函數(shù)當成一個參數(shù)看待,可以將其傳遞給一個方法或者直接將表達式所在代碼塊作為數(shù)據(jù)處理,這一設(shè)計在JAVA 8之前只能采用匿名內(nèi)部類2的方式實現(xiàn),這損耗了大量的編程時長及讀碼效率。
Example 1 遍歷數(shù)組
Java 7
Integer[] arr = new Integer[]{2, 9, -2, 3};
for(int i : arr){
System.out.println(i);
}
Java 8
Arrays.asList(2, 9, -2, 3).foreach( (Integer e) -> {
System.out.println(e);
} );
??可以看到最簡單的Lambda表達式可以由數(shù)值列表,(變量代表名),->以及{行為代碼}組成。但實際上,()包括變量e的類型名都是可以省略的,省略的變量類型名由編譯器自行推理,深知{}也是可以省略的,所以最短代碼應(yīng)該是:
Arrays.adList(2, 9, -2, 3).foreach( e -> System.out.println(e) );
??Lambda表達式可以引用類成員變量或全局變量,但JVM會自動將其隱形轉(zhuǎn)換成final類型,理由與匿名內(nèi)部類的參數(shù)引用時必須為final一致3。
String separator = ",";
Arrays.adList(2, 9, -2, 3).foreach( e -> System.out.print(e) + separator ); // 變量separator將被隱式轉(zhuǎn)換為final類型。
??為了使Java中原有的功能能夠與Lambda表達式結(jié)合使用,官方規(guī)定函數(shù)接口(除下文即將介紹的默認函數(shù)及靜態(tài)函數(shù)外,只有一個函數(shù)的接口)能夠隱式轉(zhuǎn)換成Lambda表達式,java.lang.Runnable和java.concurrent.Callable是函數(shù)接口的最佳例子。此外,為了解決函數(shù)接口定義與Lambda表達式的沖突,官方提供了一個特殊的注解@FuntionalInterface用以表示函數(shù)接口,這意味著你以后定義上述函數(shù)接口將采用此注解標識,在Jdk中所有相關(guān)的函數(shù)也已經(jīng)加上此注解。如:
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
接口新增默認方法和靜態(tài)方法
??眾所周知,接口相關(guān)知識點是面試中老生常談的話題。在以往接口被定義為抽象方法的集合,接口中的方法會被隱式指定為public abstract,而變量會被隱式指定為public static final,其他修飾符會導致報錯。而現(xiàn)在,Java中接口的定義將被修改,接口中除public抽象函數(shù)外新增了默認方法和靜態(tài)方法。
public interface Defaulable {
default void hello(){
System.out.println("這是接口的默認方法");
}
static void create(){
System.out.println("這是接口的靜態(tài)方法");
}
}
public class DefauleImp implements Defaulable{
// @Override
// public void hello() {
// System.out.println("Hello world");
// }
public static void main(String[] args) {
new DefauleImp().hello();
Defaulable.create();
}
}
??默認方法可被繼承或重寫,而靜態(tài)方法可與類的靜態(tài)方法一樣直接通過<em>接口名.方法名</em>調(diào)用。
方法引用
??方法引用的最大用途是簡寫Lambda表達式。
| 方法引用 | Lambda表達式 |
|---|---|
| String::valueOf | x -> String.valueOf(x) |
| Object::toString | x -> x.toString() |
| x::toString | () -> x.toString() |
| ArrayList::new | () -> new ArrayList<>() |
拓寬注解的使用范圍
??猶記得在舊版的Java教程中沒有注解的出現(xiàn),當學習Struts等第三方流行框架時出現(xiàn)注解使我極其不適。而現(xiàn)在注解已經(jīng)成為JAVA世界一種獨特且無可替代的定義方式。JAVA8中,注解幾乎可以用在任何元素之上:類、接口、元素、方法,甚至是異常。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Collection;
public class Annotations {
@Retention( RetentionPolicy.RUNTIME )
@Target( { ElementType.TYPE_USE, ElementType.TYPE_PARAMETER } )
public @interface NonEmpty {
}
public static class Holder< @NonEmpty T > extends @NonEmpty Object {
public void method() throws @NonEmpty Exception {
}
}
@SuppressWarnings( "unused" )
public static void main(String[] args) {
final Holder< String > holder = new @NonEmpty Holder< String >();
@NonEmpty Collection< @NonEmpty String > strings = new ArrayList<>();
}
}
運行時獲取參數(shù)名稱
??在Java8以前,需要在運行時得到參數(shù)的名稱是一件比較困難的事情,程序員們雖然提供了諸如Paranamer liberary等方法,但用起來總歸不算順暢,而現(xiàn)在Java8從字節(jié)碼層面(使用新的javac編譯器以及-parameters參數(shù))和語言層面(Parameter.getName()和反射API)提供了這一支持。
public static void main(String[] args) {
for (Method m : ${ClassName}.class.getMethods()) {
System.out.println("----------------------------------------");
System.out.println(" method: " + m.getName());
System.out.println(" return: " + m.getReturnType().getName());
for (Parameter p : m.getParameters()) {
System.out.println("parameter: " + p.getType().getName() + ", " + p.getName());
}
}
}
??但是在JAVA8中這個功能是默認關(guān)閉的,如果需要打開需要加上參數(shù)-parameters進行編譯,如果你使用maven作為構(gòu)建工具,也可以直接在編譯插件中加入此參數(shù),如下:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<compilerArgument>-parameters</compilerArgument>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
Optional
??Java中經(jīng)常會出現(xiàn)NullException,為了檢驗空值異常,程序員經(jīng)常需要添加許多與業(yè)務(wù)邏輯無關(guān)的檢測代碼,這既破壞了代碼美感,也耗費了寶貴的開發(fā)時間,因此Java8中仿照谷歌開源庫Guava使用了Optional類,此類提供了有效的接口用于null檢查,如下:
Optional< String > fullName = Optional.ofNullable( "null" );
System.out.println( "Full Name is set? " + fullName.isPresent() );
System.out.println( "Full Name: " + fullName.orElseGet( () -> "[none]" ) );
System.out.println( fullName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );
Stream
??Stream也是Java中非常重要的一個特性,在Java文檔中這樣定義Stream:
A sequence of elements supporting sequential and parallel aggregate operations.
翻譯一下,即:
- 元素的集合,這也使得Stream類似于Iterator;
- 可以順序或是并行的對原數(shù)據(jù)進行操作。
??顯而易見,Stream的設(shè)計源于分治法,學過并行計算框架MapReduce或Fork/Join的同學更容易理解。
由于Stream所屬知識篇幅較大,有興趣的同學可以關(guān)注本人的【翻譯】Java8 Stream API 教程
附錄
- 匿名函數(shù):指程序中無需定義標識符(即函數(shù)名)的函數(shù)類型或子程序(代碼段)。
- 匿名內(nèi)部類:無類名的類,它必須且僅能繼承一個父類或?qū)崿F(xiàn)一個接口。
- 為什么匿名內(nèi)部類參數(shù)引用必須為final?