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

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

在 Random-TW 群組發布
翻譯文章Java 8 Features – The ULTIMATE Guide的第二部分。第一部分在這裡(連結可能會更改)。 Java 8 功能 – 終極指南(第 2 部分)- 1

5. Java 8函式庫的新特性

Java 8 增加了許多新類別並擴展了現有類,以更好地支援現代並發、函數式程式設計、日期/時間等。

5.1. 類別 可選

著名的NullPointerException是迄今為止 Java 應用程式失敗的最常見原因。很早以前,Google 的優秀專案Guava就提出了Optional一種解決方案NullPointerException,從而防止程式碼被 null 檢查污染,從而鼓勵編寫更乾淨的程式碼。受 Google 啟發的 Guava 類別Optional現在是 Java 8 的一部分。 Optional它只是一個容器:它可以包含一個值或某種類型Т,或只是 null。它提供了許多有用的方法,因此顯式空檢查不再合理。請參閱官方文件以獲取更詳細的資訊。讓我們來看看兩個使用的小例子Optional:帶 null 和不帶 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!" ) );
如果實例包含非空值,則 該方法isPresent()傳回true ,否則傳回false。如果結果包含 null,則該方法包含一個回退機制,接受函數來產生預設值。map ()方法轉換目前值並傳回一個新實例。該方法與 類似,但它採用預設值而不是函數。這是該程式的輸出: OptionalorElseGet()OptionalOptionalOptionalorElse()orElseGet()
Full Name is set? false
Full Name: [none]
Hey Stranger!
讓我們快速看一下另一個例子:
Optional<String> firstName = Optional.of( "Tom" );
System.out.println( "First Name is set? " + firstName.isPresent() );
System.out.println( "First Name: " + firstName.orElseGet( () -> "[none]" ) );
System.out.println( firstName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );
System.out.println();
結果會是這樣的:
First Name is set? true
First Name: Tom
Hey Tom!
更詳細的資訊請參考官方文件

5.2. 流

加入的 Stream API ( java.util.stream) 引入了 Java 中真正的函數式程式設計。它是迄今為止 Java 庫中最全面的補充,使 Java 開發人員的效率顯著提高,並且使他們能夠創建高效、乾淨和簡潔的程式碼。Stream API 讓處理集合變得更加容易(但不限於它們,我們稍後會看到)。我們以一個簡單的類別為例Task
public class Streams  {
    private enum Status {
        OPEN, CLOSED
    };

    private static final class Task {
        private final Status status;
        private final Integer points;

        Task( final Status status, final Integer points ) {
            this.status = status;
            this.points = points;
        }

        public Integer getPoints() {
            return points;
        }

        public Status getStatus() {
            return status;
        }

        @Override
        public String toString() {
            return String.format( "[%s, %d]", status, points );
        }
    }
}
任務有一定的意義(或偽難度),可以是OPENCLOSE。讓我們介紹一小部分可以解決的問題。
final Collection<Task> tasks = Arrays.asList(
    new Task( Status.OPEN, 5 ),
    new Task( Status.OPEN, 13 ),
    new Task( Status.CLOSED, 8 )
);
我們要了解的第一個問題是OPEN任務目前包含 多少分?在 Java 8 之前,通常的解決方案是使用迭代器foreach。但在 Java 8 中,答案是流:支援順序和平行聚合操作的元素序列。
// Подсчет общего количества очков всех активных задач с использованием sum()
final long totalPointsOfOpenTasks = tasks
    .stream()
    .filter( task -> task.getStatus() == Status.OPEN )
    .mapToInt( Task::getPoints )
    .sum();

System.out.println( "Total points: " + totalPointsOfOpenTasks );
控制台輸出將如下所示:
Total points: 18
讓我們看看這裡發生了什麼。首先,任務集合被轉換為流表示。然後,該操作filter會過濾掉所有處於CLOSED狀態的任務。在下一步中,該操作使用每個實例的方法mapToInt將流轉換Task為流。最後,使用 方法 對所有點進行求和,從而提供最終結果。在繼續下一個範例之前,需要記住一些有關線程的注意事項(更多詳細資訊請參見此處)。操作分為中間操作和最終操作。 中間操作返回一個新流。它們總是很懶惰;當執行諸如 之類的中間操作時,它們實際上並不執行過濾,而是創建一個新流,該新流在完成後包含與給定謂詞匹配的原始流的元素。 有限運算(例如和)可以透過流傳遞以產生結果或副作用。一旦最終操作完成,該流就被視為已使用,並且不能再次使用。幾乎在所有情況下,最終操作都傾向於透過底層資料來源來完成其遍歷。執行緒的另一個有價值的特性是對並行進程的開箱即用的支援。讓我們來看看這個例子,它找到所有問題的分數總和。 IntegerTask::getPointsTasksumstreamfilterforEachsum
// Calculate total points of all tasks
final double totalPoints = tasks
   .stream()
   .parallel()
   .map( task -> task.getPoints() ) // or map( Task::getPoints )
   .reduce( 0, Integer::sum );

System.out.println( "Total points (all tasks): " + totalPoints );
這與第一個範例非常相似,只不過我們嘗試並行處理所有任務並使用方法計算最終結果reduce。這是控制台輸出:
Total points (all tasks): 26.0
通常需要根據特定標準對元素進行分組。該範例演示了線程如何幫助解決此問題。
// Группировка задач по их статусу
final Map<Status, List<Task>> map = tasks
    .stream()
    .collect( Collectors.groupingBy( Task::getStatus ) );
System.out.println( map );
控制台輸出如下:
{CLOSED=[[CLOSED, 8]], OPEN=[[OPEN, 5], [OPEN, 13]]}
為了完成問題範例,讓我們根據總分來計算集合中每個問題的總體百分比(或權重):
// Подсчет веса каждой задачи (How процент от общего количества очков)
final Collection<String> result = tasks
    .stream()                                        // Stream<String>
    .mapToInt( Task::getPoints )                     // IntStream
    .asLongStream()                                  // LongStream
    .mapToDouble( points -> points / totalPoints )   // DoubleStream
    .boxed()                                         // Stream<Double>
    .mapToLong( weigth -> ( long )( weigth * 100 ) ) // LongStream
    .mapToObj( percentage -> percentage + "%" )      // Stream<String>
    .collect( Collectors.toList() );                 // List<String>

System.out.println( result );
控制台輸出將如下所示:
[19%, 50%, 30%]
最後,正如我們之前提到的,Stream API 不僅僅適用於 Java 集合。典型的 I/O 操作(例如逐行讀取文字檔案)非常適合使用流處理。這是一個小例子來證明這一點。
final Path path = new File( filename ).toPath();
try( Stream<String> lines = Files.lines( path, StandardCharsets.UTF_8 ) ) {
    lines.onClose( () -> System.out.println("Done!") ).forEach( System.out::println );
}
onConsole在執行緒上呼叫的 方法傳回一個帶有附加私有處理程序的等效執行緒。close()當在執行緒上呼叫方法時,將呼叫私有處理程序。Stream API 以及lambda引用方法以及 Java 8 中的預設方法和靜態方法是現代軟體開發範例的答案。更詳細的資訊請參考官方文件

5.3. 日期/時間 API (JSR 310)

Java 8 透過提供新的​​日期和時間 API (JSR 310),為日期和時間管理帶來了新的面貌。日期和時間操作是 Java 開發人員最大的痛點之一。java.util.Date遵循標準並java.util.Calendar沒有整體上改善這種情況(甚至可能使其更加混亂)。這就是Joda-Time的誕生:Java 的一個出色的日期/時間 API 替代品。Java 8 (JSR 310) 中的新日期/時間 API 深受Joda-Time的影響,並從中汲取了精華。新包java.time包含日期、時間、日期/時間、時區、持續時間和時間操作的所有類別。API 設計非常重視不變性:不允許更改(從 中學到的慘痛教訓java.util.Calendar)。如果需要修改,將會傳回對應類別的新實例。讓我們看看主要的類別和它們的使用範例。第一類Clock,它使用時區提供對當下時刻、日期和時間的存取。Clock可以用來代替System.currentTimeMillis()TimeZone.getDefault()
// Получить системное время How смещение UTC
final Clock clock = Clock.systemUTC();
System.out.println( clock.instant() );
System.out.println( clock.millis() );
控制台輸出範例:
2014-04-12T15:19:29.282Z
1397315969360
我們將關注的其他新類別是LocaleDateLocalTimeLocaleDate僅包含 ISO-8601 日曆系統中的日期部分,不包含時區。因此,LocalTime它僅包含時間碼的一部分>。
// получить местную date и время время
final LocalDate date = LocalDate.now();
final LocalDate dateFromClock = LocalDate.now( clock );

System.out.println( date );
System.out.println( dateFromClock );

// получить местную date и время время
final LocalTime time = LocalTime.now();
final LocalTime timeFromClock = LocalTime.now( clock );

System.out.println( time );
System.out.println( timeFromClock );
控制台輸出範例:
2014-04-12
2014-04-12
11:25:54.568
15:25:54.568
LocalDateTime連接LocaleDateandLocalTime包含 ISO-8601 日曆系統中的日期和時間,但不包含時區。下面給出一個簡單的例子。
// Get the local date/time
final LocalDateTime datetime = LocalDateTime.now();
final LocalDateTime datetimeFromClock = LocalDateTime.now( clock );

System.out.println( datetime );
System.out.println( datetimeFromClock );
控制台輸出範例:
2014-04-12T11:37:52.309
2014-04-12T15:37:52.309
如果您需要特定時區的日期/時間,ZonedDateTime. 它包含 ISO-8601 日曆系統中的日期和時間。以下是不同時區的一些範例。
// Получение даты/времени для временной зоны
final ZonedDateTime zonedDatetime = ZonedDateTime.now();
final ZonedDateTime zonedDatetimeFromClock = ZonedDateTime.now( clock );
final ZonedDateTime zonedDatetimeFromZone = ZonedDateTime.now( ZoneId.of( "America/Los_Angeles" ) );

System.out.println( zonedDatetime );
System.out.println( zonedDatetimeFromClock );
System.out.println( zonedDatetimeFromZone );
控制台輸出範例:
2014-04-12T11:47:01.017-04:00[America/New_York]
2014-04-12T15:47:01.017Z
2014-04-12T08:47:01.017-07:00[America/Los_Angeles]
最後,讓我們來看看該類別Duration:以秒和奈秒為單位的時間跨度。這使得兩個日期之間的計算變得非常簡單。讓我們看看如何做到這一點:
// Получаем разницу между двумя датами
final LocalDateTime from = LocalDateTime.of( 2014, Month.APRIL, 16, 0, 0, 0 );
final LocalDateTime to = LocalDateTime.of( 2015, Month.APRIL, 16, 23, 59, 59 );

final Duration duration = Duration.between( from, to );
System.out.println( "Duration in days: " + duration.toDays() );
System.out.println( "Duration in hours: " + duration.toHours() );
上面的範例計算兩個日期April 16, 2014April 16, 2015之間的持續時間(以天和小時為單位) 。以下是控制台輸出的範例:
Duration in days: 365
Duration in hours: 8783
Java 8 中新日期/時間的整體印象非常非常正面。部分是因為這些變化是基於久經考驗的基礎(Joda-Time),部分是因為這次問題得到了認真的重新考慮,並且開發人員的聲音得到了傾聽。詳細資訊請參考官方文件

5.4. Nashorn JavaScript 引擎

Java 8 附帶了新的 Nashorn JavaScript 引擎,它允許您在 JVM 上開發和運行某些類型的 JavaScript 應用程式。Nashorn JavaScript 引擎只是 javax.script.ScriptEngine 的另一個實現,它遵循相同的規則集以允許 Java 和 JavaScript 互動。這是一個小例子。
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName( "JavaScript" );

System.out.println( engine.getClass().getName() );
System.out.println( "Result:" + engine.eval( "function f() { return 1; }; f() + 1;" ) );
控制台輸出範例:
jdk.nashorn.api.scripting.NashornScriptEngine
Result: 2

5.5. Base64

最後,隨著 Java 8 的發布,對 Base64 編碼的支援進入了 Java 標準函式庫。它非常易於使用,範例示範了這一點。
package com.javacodegeeks.java8.base64;

import java.nio.charset.StandardCharsets;
import java.util.Base64;

public class Base64s {
    public static void main(String[] args) {
        final String text = "Base64 finally in Java 8!";

        final String encoded = Base64
            .getEncoder()
            .encodeToString( text.getBytes( StandardCharsets.UTF_8 ) );
        System.out.println( encoded );

        final String decoded = new String(
            Base64.getDecoder().decode( encoded ),
            StandardCharsets.UTF_8 );
        System.out.println( decoded );
    }
}
程式的控制台輸出顯示編碼和解碼的文字:
QmFzZTY0IGZpbmFsbHkgaW4gSmF2YSA4IQ==
Base64 finally in Java 8!
還有 URL 友善的編碼器/解碼器以及 MIME 友善的編碼器/解碼器(Base64.getUrlEncoder()/ Base64.getUrlDecoder()Base64.getMimeEncoder()/ Base64.getMimeDecoder())的類別。

5.6. 平行數組

Java 8 版本新增了許多用於平行數組處理的新方法。也許其中最重要的是parallelSort(),它可以大大加快多核心機器上的排序速度。下面的小範例示範了新方法系列 ( parallelXxx) 的實際應用。
package com.javacodegeeks.java8.parallel.arrays;

import java.util.Arrays;
import java.util.concurrent.ThreadLocalRandom;

public class ParallelArrays {
    public static void main( String[] args ) {
        long[] arrayOfLong = new long [ 20000 ];

        Arrays.parallelSetAll( arrayOfLong,
            index -> ThreadLocalRandom.current().nextInt( 1000000 ) );
        Arrays.stream( arrayOfLong ).limit( 10 ).forEach(
            i -> System.out.print( i + " " ) );
        System.out.println();

        Arrays.parallelSort( arrayOfLong );
        Arrays.stream( arrayOfLong ).limit( 10 ).forEach(
            i -> System.out.print( i + " " ) );
        System.out.println();
    }
}
這段小程式碼使用一種方法parallelSetAll()用 20,000 個隨機值填滿數組。此後即可應用parallelSort()。程式列印排序前後的前 10 個元素,以表示陣列確實已排序。範例程式輸出可能如下所示(請注意,陣列的元素是隨機的)。
Unsorted: 591217 891976 443951 424479 766825 351964 242997 642839 119108 552378
Sorted: 39 220 263 268 325 607 655 678 723 793

5.7. 平行性

在該類別中新增了新方法,java.util.concurrent.ConcurrentHashMap以支援基於新新增的流物件和 lambda 表達式的聚合操作。此外,該類別中還新增了新方法java.util.concurrent.ForkJoinPool來支援共享池(也請參閱我們關於 Java 並發性的免費課程)。新增了一個新類別,java.util.concurrent.locks.StampedLock以提供基於功能的鎖定,具有三種存取模式用於讀取/寫入控制(可以將其視為不太好的鎖定的更好替代方案java.util.concurrent.locks.ReadWriteLock)。已新增至包中的新類別java.util.concurrent.atomic
  • 雙累加器
  • 雙加法器
  • 長累加器
  • 長加法器

6. Java運行環境(JVM)的新特性

該區域PermGen已退役並由Metaspace (JEP 122) 取代。JVM 選項-XX:PermSize-XX:MaxPermSize已分別替換為-XX:MetaSpaceSize-XX:MaxMetaspaceSize

七、結論

未來已來:Java 8 透過提供讓開發人員提高工作效率的功能,推動了其平台的發展。現在將生產系統遷移到 Java 8 還為時過早,但在接下來的幾個月裡,採用率應該會慢慢開始成長。然而,現在是時候開始準備程式碼庫以實現 Java 8 相容性,並準備在 Java 8 足夠安全且穩定時合併 Java 8 變更。作為社群對 Java 8 接受度的證明,Pivotal 最近發布了 Spring 框架,為 Java 8 提供了生產支援您可以在評論中提供有關 Java 8 中令人興奮的新功能的意見。

8. 來源

一些深入討論 Java 8 功能各個方面的其他資源:
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION