程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> Java SE 8:標准庫增強

Java SE 8:標准庫增強

編輯:關於JAVA

Lambda表達式是Java SE 8的核心功能,大部分的改進都圍繞lambda表達式展開。(Jigsaw項目已經被推遲到Java SE 9。)關於lambda表達式的內容,已經在上一篇文章中進行了說明。這篇文章主要介紹Java SE 8中包含的其他Java標准庫的增強。

並行排序

隨著多核CPU的流行,Java平台的標准庫實現也盡可能利用底層硬件平台的能力來提高性能。Java SE 7中引入了Fork/Join框架作為一個輕量級的並行任務執行引擎。Java SE 8把Fork/Join框架用到了標准庫的一些方法的實現中。比較典型的是java.utils.Arrays類中新增的parallelSort方法。與已有的sort方法不同的是,parallelSort方法使用Fork/Join框架來實現。在多核CPU平台上的性能更好。下面的代碼對包含1億個整數的數組分別使用parallelSort和sort進行排序。

Random random = new Random();
int count = 100000000;
int[] array = new int[count];
Arrays.parallelSetAll(array, (index) -> random.nextInt());
int[] copy = new int[count];
System.arraycopy(array, 0, copy, 0, array.length);
Arrays.parallelSort(array);
Arrays.sort(copy);

在本人的4核CPU的平台上,parallelSort和sort方法的耗時分別是7112毫秒和16777毫秒。所以parallelSort方法的性能要好不少。不過parallelSort方法只在數據量較大時有比較明顯的性能提升。當數據量較小時,Fork/Join框架本身所帶來的額外開銷足以抵消它帶來的性能提升。

集合批量數據操作

在Java應用的開發中,對集合的操作是比較常見的。不過在Java SE 8之前的Java標准庫中,對集合所能進行的操作比較有限,基本上都圍繞集合遍歷來展開。相對於其他編程語言來說,Java標准庫在這一塊是比較弱的。Java SE 8中lambda表達式的引入以及標准庫的增強改進了這種狀況。具體來說體現在兩個方面上的改進:第一個方面是對集合的操作方式上。得益於默認方法的引入,Java集合框架中的接口可以進行更新,添加了更多有用的操作方式,即通常所說的“filter/map/reduce”等操作。第二個方面是對集合的操作邏輯的表示方式上。新添加的操作方式使用了java.util.function包中的新的函數式接口,可以很方便地使用lambda表達式來表示對集合的處理邏輯。這兩個方面結合起來,得到的是更加直觀和簡潔的代碼。

新的集合批量處理操作的核心是新增的java.util.stream包,其中最重要的是java.util.stream.Stream接口。Stream接口的概念類似於Java I/O庫中的流,表示的是一個支持順序和並行操作的元素的序列。在該序列上可以進行不同的轉換操作。序列中包含的元素也可以被消費以產生所需的結果。Stream接口所表示的只是操作層面上的抽象,與底層的數據存儲並沒有關系。通常的使用方式是從集合中創建出Stream接口的對象,再進行各種不同的轉換操作,最後消費操作執行的結果。

Stream接口中包含的操作分成兩類:第一類是對序列中元素進行轉換的中間操作,如filter和map等。這類中間操作是延遲進行的,可以級聯起來。第二類是消費序列中元素的終止操作,如forEach和count等。當對一個Stream接口的對象執行了終止操作之後,該對象無法被再次處理。這點符合一般意義上對於“流”的理解。下面的代碼給出了Stream接口中的filter、map和reduce操作的基本使用方式。Stream接口中的方法大量使用了函數式接口,可以用lambda表達式很方便地進行操作。

IntStream.range(1,10).filter(i -> i % 2 == 0).findFirst().ifPresent(System.out::println); 
//保留偶數並輸出第一個元素

IntStream.range(1,10).map(i -> i * 2).forEach(System.out::println); //所有元素乘以2並輸出

int value = IntStream.range(1, 10).reduce(0, Integer::sum); //求和

Stream接口的reduce操作還支持一種更加復雜的用法,如下面的代碼所示:

List<String> fruits = Arrays.asList(new String[] {"apple", "orange", "pear"});
int totalLength = fruits.stream().reduce(0, (sum, str) -> sum + str.length(), Integer::sum); 
//字符串長度的總和

這種方式的reduce方法需要3個參數,分別是初始值、累積函數和組合函數。初始值是reduce操作的起始值;累積函數把部分結果和新的元素累積成新的部分結果組合函數則把兩個部分結果組合成新的部分結果,最後產生最終結果。這種形式的reduce操作通常可以簡化成一個map操作和另外一個簡單的reduce操作,如下面的代碼所示。兩種方式的效果是一樣的,不過下面的方式更加容易理解一些。

int totalLength = fruits.stream().mapToInt(String::length).reduce(0, Integer::sum);

另外一種特殊的reduce操作是collect操作。它與reduce的不同之處在於,collect操作的過程中所進行的是對一個結果對象進行修改操作。這樣可以避免不必要的對象創建,提高性能。下面代碼中的結果是一個StringBuilder類的對象。

StringBuilder upperCase = fruits.stream().collect(StringBuilder::new, (builder, str) 
-> builder.append(str.substring(0, 1).toUpperCase()), StringBuilder::append); 
//字符串首字母大寫並連接

Stream接口中的操作可以是順序執行或並行執行的。這是在Stream接口的對象創建時所確定的。比如Collection接口提供了stream和parallelStream方法來創建兩種不同執行方式的Stream接口的對象。這兩種不同的方式是可以切換的,通過Stream接口的sequential和parallel方法就可以完成。

日期和時間

Java標准庫中的日期和時間處理API一直為開發人員所诟病。大多數開發人員會選擇Joda Time這樣的第三方庫來進行替代。JSR 310作為Java SE 8的一部分,重新定義了新的日期和時間API,借鑒了已有第三方庫中的最佳實踐。I定義在java.time包中的新的日期和時間API基於標准的ISO 8601日歷系統。

在新的日期和時間API中,核心的類是LocalDateTime、OffsetDateTime和ZonedDateTime。LocalDateTime類表示的是ISO 8601日歷系統中不帶時區的日期和時間信息。OffsetDateTime類在基本的日期和時間基礎上增加了與UTC的偏移量。ZonedDateTime類則加上了時區的相關信息。下面的代碼給出了日期和時間API的基本用法,包括對日期和時間的修改、輸出和解析。

查看本欄目

LocalDateTime.now().plusDays(3).minusHours(1).format(DateTimeFormatter
.ISO_LOCAL_DATE_TIME); //輸出日期和時間
ZonedDateTime.now().withZoneSameInstant(ZoneId.of("GMT+08:00")).format
(DateTimeFormatter.ISO_ZONED_DATE_TIME); //輸出日期、時間和時區
DateTimeFormatter.ofPattern("yyyy MM dd").parse("200101 25").
query(TemporalQuery.localDate()); //日期的解析

除了上述3個類之外,還有幾個值得一提的輔助類。

Instant:表示時間線上的一個點。當程序中需要記錄時間戳時,應該使用該類。Instant類表示的時間類似“2013-07-22T23:18:35.743Z“。

Duration:表示精確的基於時間的間隔。比如Duration.of(30, ChronoUnit.SECONDS)可以獲取30秒的間隔。需要注意的是,Duration類的對象只能從精確的時間間隔創建出來,如秒、小時和天等。在這裡,一天表示精確的24小時。而月份和年是不能使用的,因為它們不能表示精確的間隔。

Period:與Duration類相對應的Period類表示的是基於日期的間隔,只能使用年/月/日作為單位。Period類在計算時會考慮夏令時等因素,適合於計算展示給最終用戶的內容。

Clock:表示包含時區信息的時鐘,可以獲取當前日期和時間。如LocalDateTime.now(Clock.system(ZoneId.of("GMT+08:00")))表示的是當前的北京時間。Clock類的一個重要作用是簡化測試。在測試時可以指定不同時區的時鐘來進行模擬。

其他更新

除了上面提到的幾個比較大的更新之前,還有一些小的改動。

Base64編碼

Base64編碼在Java應用開發中經常會用到,比如在HTTP基本認證中。在Java SE 8之前,需要使用第三方庫來進行Base64編碼與解碼。Java SE 8增加了java.util.Base64類進行編碼和解碼。下面的代碼給出了簡單的示例。

Base64.Encoder encoder = Base64.getEncoder();
String encoded = encoder.encodeToString("username:password".getBytes());
Base64.Decoder decoder = Base64.getDecoder();
String decoded = new String(decoder.decode(encoded));

並發處理

Java SE 8進一步增強了並發處理的相關API。在java.util.concurrent.atomic中新增了LongAccumulator、LongAdder、DoubleAccumulator和DoubleAdder等幾個類。這幾個類用來在多線程的情況下更新某個Long或Double類型的變量。下面的代碼給出了LongAccumulator類的使用示例。

public class ConcurrentSample {
    public static void main(String[] args) throws Exception {
        ConcurrentSample sample = new ConcurrentSample();
        LongAccumulator accumulator = new LongAccumulator(Long::max, 
Long.MIN_VALUE);
        for (int i = 0; i < 100; i++) {
            sample.newThread("Test thread - " + i, accumulator);
        }
        System.out.println(accumulator.longValue());
    }
	
    private void newThread(final String name, final LongAccumulator accumulator)
 throws Exception {
        Thread thread = new Thread(() -> {
            Random random = new Random();
            int value = random.nextInt(5000);
            System.out.println(String.format("%s -> %s", Thread.currentThread
().getName(), value));
            accumulator.accumulate(value);
        }, name);
        thread.start();
        thread.join();
    }
}

當LongAccumulator類的對象上的accumulate方法被調用時,參數中的值會通過Long類的max方法進行比較,所得到的結果作為LongAccumulator類的對象的當前值。經過多次累積操作之後,最終的結果是所有調用操作中提供的最大值。

ConcurrentHashMap類得到了比較大的更新,添加了很多實用的方法,如compute方法用來進行值的計算,merge方法用來進行鍵值的合並,search方法用來進行查找等。這使得ConcurrentHashMap類可以很方便的創建緩存系統。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved