1. 程式人生 > >Java 8 新特性 – 終極手冊整理

Java 8 新特性 – 終極手冊整理

1.簡介

毫無疑問,Java 8是自Java  5(2004年)釋出以來Java語言最大的一次版本升級,Java 8帶來了很多的新特性,比如編譯器、類庫、開發工具和JVM(Java虛擬機器)。在這篇教程中我們將會學習這些新特性,並通過真例項子演示說明它們適用的場景。

本教程由下面幾部分組成,它們分別涉及到Java平臺某一特定方面的內容:

  • 語言
  • 編譯器
  • 類庫
  • 開發工具
  • 執行時(Java虛擬機器)

2.Java的新特性

總體來說,Java 8是一個大的版本升級。有人可能會說,Java 8的新特性非常令人期待,但是也要花費大量的時間去學習。這一節我們會講到這些新特性。

2.1 Lambda表示式和函式式介面

Lambda表示式(也叫做閉包)是Java 8中最大的也是期待已久的變化。它允許我們將一個函式當作方法的引數(傳遞函式),或者說把程式碼當作資料,這是每個函數語言程式設計者熟悉的概念。很多基於JVM平臺的語言一開始就支援Lambda表示式,但是Java程式設計師沒有選擇,只能使用匿名內部類來替代Lambda表示式。

Lambda表示式的設計被討論了很久,而且花費了很多的功夫來交流。不過最後取得了一個折中的辦法,得到了一個新的簡明並且緊湊的Lambda表示式結構。最簡單的Lambda表示式可以用逗號分隔的引數列表、->符號和功能語句塊來表示。示例如下:

Arrays.asList( "a"
, "b", "d" ).forEach( e -> System.out.println( e ) );

請注意到編譯器會根據上下文來推測引數的型別,或者你也可以顯示地指定引數型別,只需要將型別包在括號裡。舉個例子:

Arrays.asList( "a", "b", "d" ).forEach( ( String e ) -> System.out.println( e ) );

如果Lambda的功能語句塊太複雜,我們可以用大括號包起來,跟普通的Java方法一樣,如下:

String separator = ",";
Arrays.asList( "a", "b", "d"
).forEach( ( String e ) -> System.out.print( e + separator ) );

Lambda表示式可能會引用類的成員或者區域性變數(會被隱式地轉變成final型別),下面兩種寫法的效果是一樣的:

String separator = ",";
Arrays.asList( "a", "b", "d" ).forEach(
    ( String e ) -> System.out.print( e + separator ) );

final String separator = ",";
Arrays.asList( "a", "b", "d" ).forEach(
    ( String e ) -> System.out.print( e + separator ) );

Lambda表示式可能會有返回值,編譯器會根據上下文推斷返回值的型別。如果lambda的語句塊只有一行,不需要return關鍵字。下面兩個寫法是等價的:

Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );

Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> {
    int result = e1.compareTo( e2 );
    return result;
} );

語言的設計者們思考了很多如何讓現有的功能和lambda表示式友好相容。於是就有了函式介面這個概念。函式介面是一種只有一個方法的介面,像這樣地,函式介面可以隱式地轉換成lambda表示式。

java.lang.Runnable 和java.util.concurrent.Callable是函式介面兩個最好的例子。但是在實踐中,函式介面是非常脆弱的,只要有人在接口裡新增多一個方法,那麼這個介面就不是函式介面了,就會導致編譯失敗。Java 8提供了一個特殊的註解@FunctionalInterface來克服上面提到的脆弱性並且顯示地表明函式介面的目的(java裡所有現存的介面都已經加上了@FunctionalInterface)。讓我們看看一個簡單的函式介面定義:

@FunctionalInterface
public interface Functional {
    void method();
}

我們要記住預設的方法和靜態方法(下一節會具體解釋)不會違反函式介面的約定,例子如下:

@FunctionalInterface
public interface FunctionalDefaultMethods {
    void method();

    default void defaultMethod() {
    }
}

支援Lambda是Java 8最大的賣點,他有巨大的潛力吸引越來越多的開發人員轉到這個開發平臺來,並且在純Java裡提供最新的函數語言程式設計的概念。對於更多的細節,請參考官方文件

2.2 介面的預設方法和靜態方法

Java 8增加了兩個新的概念在介面宣告的時候:預設和靜態方法。預設方法和Trait有些類似,但是目標不一樣。預設方法允許我們在接口裡新增新的方法,而不會破壞實現這個介面的已有類的相容性,也就是說不會強迫實現介面的類實現預設方法。

預設方法和抽象方法的區別是抽象方法必須要被實現,預設方法不是。作為替代方式,介面可以提供一個預設的方法實現,所有這個介面的實現類都會通過繼承得倒這個方法(如果有需要也可以重寫這個方法),讓我們來看看下面的例子:

private interface Defaulable {
    // Interfaces now allow default methods, the implementer may or
    // may not implement (override) them.
    default String notRequired() {
        return "Default implementation";
    }
}

private static class DefaultableImpl implements Defaulable {
}

private static class OverridableImpl implements Defaulable {
    @Override
    public String notRequired() {
        return "Overridden implementation";
    }
}

介面Defaulable使用default關鍵字聲明瞭一個預設方法notRequired(),類DefaultableImpl實現了Defaulable介面,沒有對預設方法做任何修改。另外一個類OverridableImpl重寫類預設實現,提供了自己的實現方法。

Java 8 的另外一個有意思的新特性是接口裡可以宣告靜態方法,並且可以實現。例子如下:

private interface DefaulableFactory {
    // Interfaces now allow static methods
    static Defaulable create( Supplier< Defaulable > supplier ) {
        return supplier.get();
    }
}

下面是把介面的靜態方法和預設方法放在一起的示例(::new 是構造方法引用,後面會有詳細描述):

public static void main( String[] args ) {
    Defaulable defaulable = DefaulableFactory.create( DefaultableImpl::new );
    System.out.println( defaulable.notRequired() );

    defaulable = DefaulableFactory.create( OverridableImpl::new );
    System.out.println( defaulable.notRequired() );
}

控制檯的輸出如下:

Default implementation
Overridden implementation

JVM平臺的介面的預設方法實現是很高效的,並且方法呼叫的位元組碼指令支援預設方法。預設方法使已經存在的介面可以修改而不會影響編譯的過程。java.util.Collection中新增的額外方法就是最好的例子:stream()parallelStream()forEach()removeIf()

雖然預設方法很強大,但是使用之前一定要仔細考慮是不是真的需要使用預設方法,因為在層級很複雜的情況下很容易引起模糊不清甚至變異錯誤。更多的詳細資訊請參考官方文件

2.3   方法引用

方法引用提供了一個很有用的語義來直接訪問類或者例項的已經存在的方法或者構造方法。結合Lambda表示式,方法引用使語法結構緊湊簡明。不需要複雜的引用。

下面我們用Car 這個類來做示例,Car這個類有不同的方法定義。讓我們來看看java 8支援的4種方法引用。

public static class Car {
    public static Car create( final Supplier< Car > supplier ) {
        return supplier.get();
    }              

    public static void collide( final Car car ) {
        System.out.println( "Collided " + car.toString() );
    }

    public void follow( final Car another ) {
        System.out.println( "Following the " + another.toString() );
    }

    public void repair() {
        System.out.println( "Repaired " + this.toString() );
    }
}

第一種方法引用是構造方法引用,語法是:Class::new ,對於泛型來說語法是:Class<T >::new,請注意構造方法沒有引數:

final Car car = Car.create( Car::new );
final List< Car > cars = Arrays.asList( car );

第二種方法引用是靜態方法引用,語法是:Class::static_method請注意這個靜態方法只支援一個型別為Car的引數。

cars.forEach( Car::collide );

第三種方法引用是類例項的方法引用,語法是:Class::method請注意方法沒有引數。

cars.forEach( Car::repair );

最後一種方法引用是引用特殊類的方法,語法是:instance::method請注意只接受Car型別的一個引數。

final Car police = Car.create( Car::new );
cars.forEach( police::follow );

執行這些例子我們將會在控制檯得到如下資訊(Car的例項可能會不一樣):

Collided [email protected]a81197d
Repaired [email protected]a81197d
Following the [email protected]a81197d

關於方法引用更多的示例和詳細資訊,請參考官方文件

2.4   重複註釋

自從Java 5支援註釋以來,註釋變得特別受歡迎因而被廣泛使用。但是有一個限制,同一個地方的不能使用同一個註釋超過一次。 Java 8打破了這個規則,引入了重複註釋,允許相同註釋在宣告使用的時候重複使用超過一次。

重複註釋本身需要被@Repeatable註釋。實際上,他不是一個語言上的改變,只是編譯器層面的改動,技術層面仍然是一樣的。讓我們來看看例子:

package com.javacodegeeks.java8.repeatable.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

public class RepeatingAnnotations {
    @Target( ElementType.TYPE )
    @Retention( RetentionPolicy.RUNTIME )
    public @interface Filters {
        Filter[] value();
    }

    @Target( ElementType.TYPE )
    @Retention( RetentionPolicy.RUNTIME )
    @Repeatable( Filters.class )
    public @interface Filter {
        String value();
    };

    @Filter( "filter1" )
    @Filter( "filter2" )
    public interface Filterable {
    }

    public static void main(String[] args) {
        for( Filter filter: Filterable.class.getAnnotationsByType( Filter.class ) ) {
            System.out.println( filter.value() );
        }
    }
}

我們可以看到,註釋Filter被@Repeatable( Filters.class )註釋。Filters 只是一個容器,它持有Filter, 編譯器盡力向程式設計師隱藏它的存在。通過這樣的方式,Filterable介面可以被Filter註釋兩次。

另外,反射的API提供一個新方法getAnnotationsByType() 來返回重複註釋的型別(請注意Filterable.class.getAnnotation( Filters.class )將會返回編譯器注入的Filters例項)。

程式的輸出將會是這樣:

filter1
filter2

更多詳細資訊請參考官方文件

2.5   更好的型別推斷

Java 8在型別推斷方面改進了很多,在很多情況下,編譯器可以推斷引數的型別,從而保持程式碼的整潔。讓我們看看例子:

package com.javacodegeeks.java8.type.inference;

package com.javacodegeeks.java8.type.inference;

public class Value<T> {
    public static<T> T defaultValue() {
        return null;
    }

    public T getOrDefault( T value, T defaultValue ) {
        return ( value != null ) ? value : defaultValue;
    }
}

這裡是Value< String >的用法

package com.javacodegeeks.java8.type.inference;

public class TypeInference {
    public static void main(String[] args) {
        final Value<String> value = new Value<>();
        value.getOrDefault( "22", Value.defaultValue() );
    }
}

引數Value.defaultValue()的型別被編譯器推斷出來,不需要顯式地提供型別。在java 7, 相同的程式碼不會被編譯,需要寫成:Value.< String >defaultValue()

2.6   註解的擴充套件

Java 8擴充套件了註解可以使用的範圍,現在我們幾乎可以在所有的地方:區域性變數、泛型、超類和介面實現、甚至是方法的Exception宣告。一些例子如下:

package com.javacodegeeks.java8.annotations;

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<>();
    }
}

Java 8 新增加了兩個註解的程式元素型別ElementType.TYPE_USE ElementType.TYPE_PARAMETER ,這兩個新型別描述了可以使用註解的新場合。註解處理API(Annotation Processing API)也做了一些細微的改動,來識別這些新新增的註解型別。

3.Java編譯器的新特性

3.1 引數名字

很長時間以來,Java程式設計師想盡辦法把引數名字儲存在java位元組碼裡,並且讓這些引數名字在執行時可用。Java 8 終於把這個需求加入到了Java語言(使用反射API和Parameter.getName() 方法)和位元組碼裡(使用java編譯命令javac的–parameters引數)。

package com.javacodegeeks.java8.parameter.names;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

public class ParameterNames {
public static void main(String[] args) throws Exception {
Method method = ParameterNames.class.getMethod( "main", String[].class );
for( final Parameter parameter: method.getParameters() ) {
System.out.println( "Parameter: " + parameter.getName() );
}
}
}

如果你編譯這個class的時候沒有新增引數–parameters執行的時候你會得到這個結果:

Parameter: arg0

編譯的時候添加了–parameters引數的話,執行結果會不一樣:

Parameter: args

對於有經驗的Maven使用者,–parameters引數可以新增到maven-compiler-plugin的配置部分:

<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>

最新版的Eclipse Kepler SR2 提供了編譯設定項,如下圖所示:

Java 8 特性 – 終極手冊

Picture 1. Configuring Eclipse projects to support new Java 8 compiler –parameters argument.

額外的,有一個方便的方法Parameter.isNamePresent() 來驗證引數名是不是可用。

4.Java  庫的新特性

Java 8 新添加了很多類,並且擴充套件了很多現有的類來更好地支援現代併發、函數語言程式設計、日期\時間等等。

4.1 Optional

著名的NullPointerException 是引起系統失敗最常見的原因。很久以前Google Guava專案引入了Optional作為解決空指標異常的一種方式,不贊成程式碼被null檢查的程式碼汙染,期望程式設計師寫整潔的程式碼。受Google Guava的鼓勵,Optional 現在是Java 8庫的一部分。

Optional 只是一個容器,它可以儲存一些型別的值或者null。它提供很多有用的方法,所以沒有理由不顯式地檢查null。請參照java 8的

相關推薦

Java 8 特性終極手冊整理

1.簡介 毫無疑問,Java 8是自Java  5(2004年)釋出以來Java語言最大的一次版本升級,Java 8帶來了很多的新特性,比如編譯器、類庫、開發工具和JVM(Java虛擬機器)。在這篇教程中我們將會學習這些新特性,並通過真例項子演示說明它們適用的場景。

Java 5/Java 6/Java7/Java 8特性收集

lan 鏈接 develop new strong tar chrom eve ref 前言: Java 8對應的JDK版本為JDK8,而官網下載回來安裝的時候,文件夾上寫的是JDK1.8,同一個意思。(而這個版本命名也是有規律的,以此類推) 一、Java 5 1、h

Java 8 特性1-函數式接口

實例 his sys subject 生成 license object類 acc class類 Java 8 新特性1-函數式接口 (原) Lambda表達式基本結構: (param1,param2,param3) -> {代碼塊} 例1: package

Java 8 特性:5-Supplier、IntSupplier、BinaryOperator接口

point except java 8 htm import void int() uci cti (原) 這個接口很簡單,裏面只有一個抽象方法,沒有default和靜態方法。 /* * Copyright (c) 2012, 2013, Oracle and/or

JAVA 8 特性 (值得學習)

java 8 新特性JAVA 8 已經出現好長時間了,大的互聯網公司很多都已經使用了,甚至很多知名互聯網公司踩過很多坑,也有一些大牛分享出了他們的實戰經驗。去很多知名的互聯網公司經常會被面試官問,你了解java 8嗎?你知道它的一些新特性嗎?好像似乎成了一面面試官必問的一道題目。這篇博文,只是簡答的介紹了一下

Java 8 特性:4-Optional類

get方法 syn 序列 new ret 有一個 例子 使用 n) (原) 先看看上面的說明: /** * A container object which may or may not contain a non-null value. * If a value

Java--8--特性--Lambda

value 需要 員工信息 span final oid function get test java9 都出來了,我才開始接觸到java8的新特性,有點脫節啊。。 Lambda是一個匿名函數,可以理解為一段可以傳遞的代碼,將代碼像數據一樣傳遞,下面是一個小例子。 pub

Java 8特性之接口改善(八惡人-1)

1.8 我想 when 直接 有一個 圖片 class java類 聖誕節 Daisy Donergue 多莫歌·黛西 “By woman, you mean her?” 她也能叫女人?   Java 8在13年9月發布,寫這篇博文的時間已經是17年12月份了。

Java 8特性之 並行和並行數組(八惡人-8

都是 class chm 請求 external syntax 匹配 main jvm Jody Domingre 多莫歌·喬迪 “How you doing, dummy?” 你還好嗎,傻瓜 一、基本介紹   Java8不僅增加了Stream,而且還增加了para

Java 8 特性 - Lambda表達式(一)

ava 鏈接 article post lambda targe dash lambda表達式 java8 鏈接 Java8新特性——Lambda表達式(一)Java 8 新特性 - Lambda表達式(一)

Java 8 特性

語法 空指針異常 有用 編程 using javac www. strong network Java 8 (又稱為 jdk 1.8) 是 Java 語言開發的一個主要版本。 Oracle 公司於 2014 年 3 月 18 日發布 Java 8 ,它支持函數式編程,新的

Java之Date Time API (Java 8 特性)

今天 utc eating mes interval etime api int isa Java 8 – Date Time APIJava 8 comes with a much improved and much required change in the way

Java 8 特性-菜鳥教程 (3) -Java 8 函數式接口

但是 style vax arr 結果 友好 face todo 兩個 Java 8 函數式接口 函數式接口(Functional Interface)就是一個有且僅有一個抽象方法,但是可以有多個非抽象方法的接口。 函數式接口可以被隱式轉換為lambda表達式。 函數式接口

Java 8 特性——Stream API

目錄 1. 什麼是stream  2. Stream操作三個步驟 3. 建立Stream 4. Stream 的中間操作 5. Stream 的終止操作 Stream API(java.util.stream.*)。    &n

java 8特性之收集器,Optional類

一.收集器介面 Collectors類的靜態工廠方法能夠建立的所有收集器總結: 所有這些收集器都是對Collector介面的實現,以下時Collector介面宣告的五個方法: 二.Optional類 是一個容器類,代表一個值存在或不存在,這樣就避免了和null檢查相關的bug

Java 8特性之Optional取代null

NullPointerException,大家應該都見過。這是Tony Hoare在設計ALGOL W語言時提出的null引用的想法,他的設計初衷是想通過編譯器的自動檢測機制,確保所有使用引用的地方都是絕對安全的。很多年後,他對自己曾經做過的這個決定而後悔不已,把它稱為“我價值百萬的重大失誤”。它帶來的後果就

Java 8特性的日期和時間API

在Java 1.0中,對日期和時間的支援只能依賴java.util.Date類。這個類只能以毫秒的精度表示時間。這個類還有很多糟糕的問題,比如年份的起始選擇是1900年,月份的起始從0開始。這意味著你要想表示2018年8月22日,就必須建立下面這樣的Date例項: Date date = new Date

Java 8特性之CompletableFuture:組合式非同步程式設計

隨著多核處理器的出現,提升應用程式的處理速度最有效的方式就是可以編寫出發揮多核能力的軟體,我們已經可以通過切分大型的任務,讓每個子任務並行執行,使用執行緒的方式,分支/合併框架(Java 7) 和並行流(Java 8)來實現。 現在很多大型的網際網路公司都對外提供了API服務,比如百度的地圖,微博的新聞,天

Java 8特性—02.Lambda 表示式基礎語法

Lambda 表示式的基礎語法:Java8中引入了一個新的操作符“->” 該操作符稱為箭頭操作符或Lambda操作符, 該操作符將Lambda表示式拆分為兩部分: 左側:Lambda 表示式的引數部分 右側:Lamdba 表示式中所需執行的功能,即Lambda 體。

Java 8特性—01.簡介

主要內容 Lambda表示式 函式式介面 方法引用與構造器引用 Stream API 介面中 預設方法與靜態方法 新日期API 其他特性 Java 8 新特性簡介 速度更快 程式碼更少(增加了新的語法Lambda表示式) 強大的Stream API 便於並行 最大化減少空指標異常 Opt