JavaRush /Java Blog /Random-TW /Java 8 功能 – 終極指南(第 1 部分)
0xFF
等級 9
Донецк

Java 8 功能 – 終極指南(第 1 部分)

在 Random-TW 群組發布
文章《Java 8 Features – The ULTIMATE Guide》 翻譯的第一部分。第二部分在這裡(連結可能會更改)。 Java 8 功能 – 終極指南(第 1 部分)- 1 編者註:本文是在 Java 8 向公眾開放時發表的,所有跡像都表明這確實是一個主要版本。在這裡,我們提供了豐富的 Java Code Geeks 指南,例如《Playing with Java 8 – Lambdas and Concurrency》《Java 8 Date and Time API Guide: LocalDateTime》《Java 8 Era 中的 Abstract Class vs. Interface》。我們也連結到其他來源的 15 篇必讀 Java 8 教學。當然,我們也會看到一些缺點,例如Java 8 的黑暗面。因此,是時候將 Java 8 的所有主要功能集中在一處,以方便您使用。享受!

一、簡介

毫無疑問,Java 8 的發布是自 Java 5(很久以前發布,2004 年發布)以來最偉大的事件。它在語言、編譯器、函式庫、工具和 JVM(Java 虛擬機器)方面為 Java 帶來了許多新功能。在本教程中,我們將了解這些更改,並透過現實生活中的範例演示​​不同的用例。該指南由幾個部分組成,每個部分都涉及平台的一個特定方面:
  • 語言
  • 編譯器
  • 圖書館
  • 工具
  • 運行時環境(JVM)

2.Java 8的新特性

無論如何,Java 8 是一個主要版本。可以說,之所以花了這麼長時間,是因為實現了每個 Java 開發人員都在尋找的功能。在本節中,我們將介紹其中的大部分內容。

2.1. Lambda 和函數式介面

Lambda(也稱為私有或匿名方法)是整個 Java 8 版本中最大且最受期待的語言變更。它們允許我們將功能指定為方法參數(透過在其周圍宣告一個函數),或將程式碼指定為數據:每個功能開發人員都熟悉的概念。程式設計_ JVM 平台上的許多語言(Groovy、Scala等)從第一天起就擁有 lambda,但 Java 開發人員別無選擇,只能透過匿名類別來表示 lambda。lambda 設計爭論花費了公眾大量的時間和精力。但最終找到了妥協方案,導致了新的簡潔設計的出現。在最簡單的形式中,lambda 可以表示為以逗號分隔的參數清單、–>符號和主體。例如:
Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) )
請注意,參數e 的類型由編譯器決定。此外,您可以將參數括在括號中來明確指定參數的類型。例如:
Arrays.asList( "a", "b", "d" ).forEach( ( String e ) -> System.out.println( e ) );
如果 lambda 主體更複雜,可以將其括在大括號中,類似於 Java 中的常規函數定義。例如:
Arrays.asList( "a", "b", "d" ).forEach( e -< {
    System.out.print( e );
    System.out.print( e );
} );
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 可以傳回一個值。返回類型將由編譯器決定。return如果 lambda 主體由一行組成,則不需要聲明。下面的兩個程式碼片段是等效的:
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 中新增了一個特殊的註解(@FunctionalInterfaceJava 庫中的所有現有介面都收到了 @FunctionalInterface 註解)。讓我們來看看函數式介面的簡單定義:
@FunctionalInterface
public interface Functional {
    void method();
}
有一點要記住:預設方法和靜態方法不違反函數式介面的原則,可以宣告:
@FunctionalInterface
public interface FunctionalDefaultMethods {
    void method();

    default void defaultMethod() {
    }
}
Lambda 是 Java 8 中最受歡迎的功能。它們有潛力吸引更多開發人員使用這個出色的平台,並為純 Java 中的功能提供智慧支援。更詳細的資訊請參考官方文件

2.2. 預設介面和靜態方法

Java 8 透過兩個新概念擴展了介面的定義:預設方法和靜態方法。預設方法使介面與特徵有些相似,但用途略有不同。它們允許您向現有介面添加新方法,而不會破壞這些介面先前編寫的版本的向後相容性。預設方法和抽象方法之間的區別在於,抽象方法必須實現,而預設方法則不需要。相反,每個介面必須提供一個所謂的預設實現,並且所有後代都將預設接收它(如果需要,可以覆蓋此預設實現)。讓我們看下面的例子。
private interface Defaulable {
    // Интерфейсы теперь разрешают методы по умолчанию,
    // клиент может реализовывать  (переопределять)
    // or не реализовывать его
    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聲明預設方法。其中一個類別實作了此接口,保留預設方法不變。另一個類別覆蓋預設實作並提供自己的實作。Java 8 中引入的另一個有趣的功能是介面可以聲明靜態方法(並提供靜態方法的實作)。這是一個例子: notRequired()defaultDefaultableImplOverridableImpl
private interface DefaulableFactory {
    // Interfaces now allow static methods
    static Defaulable create( Supplier<Defaulable> supplier ) {
        return supplier.get();
    }
}
一小段程式碼結合了上例中的預設方法和靜態方法:
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 介面在不破壞編譯過程的情況下發展。很好的例子是添加到介面中的許多方法java.util.Collectionstream(), parallelStream(), forEach(), removeIf(), ... 儘管預設方法功能強大,但應謹慎使用:在聲明預設方法之前,您應該三思而行是否真的有必要,因為這可能會導致複雜層次結構中的編譯歧義和錯誤。有關更詳細的信息,請參閱文件

2.3. 參考方法

引用方法實作有用的語法來引用 Java 類別或物件(實例)的現有方法或建構子。與 lambda 表達式一起,引用方法使語言構造緊湊、簡潔,使其基於模板。以下是一個類別Car作為不同方法定義的範例,讓我們重點介紹一下支援的四種引用方法類型:
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 );
作為 Java 程式運行所有這些範例會產生以下控制台輸出(類別引用Car可能會有所不同):
Collided com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d
Repaired com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d
Following the com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d
更詳細的資訊和參考方法詳情請參考官方文件

2.4. 重複註釋

自從Java 5引入了對註解的支援以來,這個特性已經變得非常流行並且使用非常廣泛。然而,使用註釋的限制之一是同一註釋不能在同一位置聲明多次。Java 8 打破了這個規則並引入了重複的註解。這允許相同的註釋在聲明它們的位置重複多次。重複的註解應該使用 annotation 來註解自己@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,但 Java 編譯器試圖向開發人員隱藏它們的存在。因此,此介麵Filterable包含聲明Filter兩次的註釋(不提及Filters)。Reflection API 還提供了一種getAnnotationsByType()用於傳回某種類型的重複註解的新方法(請記住 Filterable..getAnnotation class( Filters. class) 將傳回編譯器注入的實例Filters)。程式的輸出將如下所示:
filter1
filter2
更詳細的資訊請參考官方文件

2.5. 改進的類型推斷

Java 8 編譯器已獲得許多型別推斷改進。在許多情況下,編譯器可以定義顯式類型參數,從而使程式碼更簡潔。讓我們來看一個例子:
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;
    }
}
這是 type 的用法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 中,同一範例將無法編譯,必須重寫為 <NOBR>Value.<String>defaultValue()</NOBR>。

2.6。擴展註釋支持

Java 8 擴充了可以使用註解的上下文。如今,幾乎任何東西都可以有註解:局部變數、泛型類型、超類別和實現的接口,甚至方法異常。下面提供了一些範例:
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<>();
    }
}
ElementType.TYPE_USE以及ElementType.TYPE_PARAMETER兩個新的元素類型來描述相關的註釋上下文。Annotation Processing API也進行了一些細微的變更以識別 Java 中的新註解類型。

3. Java編譯器的新特性

3.1. 參數名稱

長期以來,Java 開發人員發明了多種在 Java 字節碼中儲存方法參數名稱的方法,以使它們在運行時可用(例如Paranamer 庫)。最後,Java 8 在語言(使用反射 API 和方法Parameter.getName())和字節碼(使用新的編譯器參數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() );
        }
    }
}
如果您在不使用參數的情況下編譯此類–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>
要檢查參數名稱的可用性,該類別isNamePresent()提供了一種方便的方法Parameter

4.新的Java工具

Java 8 附帶了一組新的命令列工具。在本節中,我們將看看其中最有趣的部分。

4.1. Nashorn 引擎:jjs

jjs是一個基於命令列的獨立 Nashorn 引擎。它獲取 JavaScript 原始碼檔案列表並運行它們。例如,讓我們建立一個包含以下內容的 func.js檔案:
function f() {
     return 1;
};

print( f() + 1 );
要運行此文件,我們將其作為參數傳遞給jjs
jjs func.js
控制台輸出將如下所示:
2
有關更多詳細信息,請參閱文件

4.2. 類別依賴分析器:jdeps

jdeps是一個非常棒的命令列工具。它顯示了 Java 類別的套件或類別層級的依賴關係。它接受.class檔案、資料夾或JAR 檔案作為輸入。預設情況下,jdeps將依賴項輸出到標準輸出(控制台)。作為範例,讓我們來看看流行的Spring 框架庫的依賴關係報告。為了讓範例簡短,我們只看一下 JAR 檔案的依賴關係org.springframework.core-3.0.5.RELEASE.jar
jdeps org.springframework.core-3.0.5.RELEASE.jar
該命令輸出相當多,因此我們只分析部分輸出。依賴項按包分組。如果沒有依賴,會顯示not found
org.springframework.core-3.0.5.RELEASE.jar -> C:\Program Files\Java\jdk1.8.0\jre\lib\rt.jar
   org.springframework.core (org.springframework.core-3.0.5.RELEASE.jar)
      -> java.io
      -> java.lang
      -> java.lang.annotation
      -> java.lang.ref
      -> java.lang.reflect
      -> java.util
      -> java.util.concurrent
      -> org.apache.commons.logging                         not found
      -> org.springframework.asm                            not found
      -> org.springframework.asm.commons                    not found
   org.springframework.core.annotation (org.springframework.core-3.0.5.RELEASE.jar)
      -> java.lang
      -> java.lang.annotation
      -> java.lang.reflect
      -> java.util
更詳細的資訊請參考官方文件
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION