當前位置: 妍妍網 > 碼農

幹貨 | Java8的幾個實用新特性教程分享給你

2024-01-29碼農

來自: https://juejin.cn/post/7130883974846480391

在本文中,你可以了解一些可能沒有聽說過的有用的 Java 特性。這是我最近使用的功能的私人列表,或者是我在閱讀有關 Java 的文章時偶然發現的。我不會關註語言方面,而是關註 API。


1.延遲佇列

【Delay Queue】




如您所知,Java 中有許多型別的集合可用。但你聽說了 DelayQueue 嗎?它是一種特定型別的 Java 集合,它允許我們根據元素的延遲時間對元素進行排序。老實說,這是一門非常有趣的課。盡管 DelayQueue 該類是 Java 集合的成員,但它屬於 java.util.concurrent 包。它實作了 BlockingQueue 介面。只有當元素的時間到期時,才能從佇列中取出元素。

為了使用它,首先在類裏面需要實作介面中的 getDelay 方法 Delayed 。它不必是一個類——你也可以使用 Java Record。

public record DelayedEvent(long startTime, String msg) implements Delayed {
publiclonggetDelay(TimeUnit unit){
long diff = startTime - System.currentTimeMillis();
return unit.convert(diff, TimeUnit.MILLISECONDS);
}
publicintcompareTo(Delayed o){
return (int) (this.startTime - ((DelayedEvent) o).startTime);
}
}


假設我們想推遲元素10秒。我們只需要設定當前時間增加了10秒DelayedEvent類。

final DelayQueue<DelayedEvent> delayQueue = new DelayQueue<>();
final long timeFirst = System.currentTimeMillis() + 10000;
delayQueue.offer(new DelayedEvent(timeFirst, "1"));
log.info("Done");
log.info(delayQueue.take().msg());

上面可見程式碼的輸出是什麽?讓我們來看看。


2.時間格式的天數

【Period of Days in Time Format】




Java 8 改進了很多時間處理 API。從這個版本的 Java 開始,在大多數情況下,可能不必使用任何額外的庫,如 Joda Time。你能想象從 Java 16 開始,你甚至可以使用標準格式化程式來表示一天中的時段,例如「早上」或「下午」?有一種稱為 B 的新格式模式。

String s = DateTimeFormatter
.ofPattern("B")
.format(LocalDateTime.now());
System.out.println(s);

這是我的結果。當然結果取決於在一天中的時間。


3. 印戳鎖

【StampedLock】




Java Concurrent 是最有趣的 Java 包之一。同時,它也是開發人員鮮為人知的一種,尤其是當他們主要使用 Web 框架時。有多少人曾經在 Java 中使用過鎖?釘選是一種比「同步」塊更靈活的執行緒同步機制。從 Java 8 開始,您可以使用一種稱為「StampedLock」的新型鎖。 StampedLock 是使用 ReadWriteLock 的替代方法。它允許對讀取操作進行樂觀釘選。此外,它比 ReentrantReadWriteLock 具有更好的效能。

假設我們有兩個執行緒。其中第一個更新余額,而第二個讀取余額的當前值。為了更新余額,我們當然需要先讀取它的當前值。我們在這裏需要某種同步,假設第一個執行緒同時執行多次。第二個執行緒只是說明了如何使用樂觀鎖進行讀取操作。

StampedLock lock = new StampedLock();
Balance b = new Balance(10000);
Runnable w = () -> {
long stamp = lock.writeLock();
b.setAmount(b.getAmount() + 1000);
System.out.println("Write: " + b.getAmount());
lock.unlockWrite(stamp);
};
Runnable r = () -> {
long stamp = lock.tryOptimisticRead();
if (!lock.validate(stamp)) {
stamp = lock.readLock();
try {
System.out.println("Read: " + b.getAmount());
finally {
lock.unlockRead(stamp);
}
else {
System.out.println("Optimistic read fails");
}
};

現在,讓我們透過同時執行兩個執行緒 50 次來測試它。它應該按預期工作 - 余額的最終值為 60000

ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 50; i++) {
executor.submit(w);
executor.submit(r);
}

4. 並列累加器

【Concurrent accumulators】




鎖並不是 Java Concurrent 包中唯一有趣的特性。另一種稱為並行累加器。還有並行加法器,但它是一個非常相似的功能。 LongAccumulator (也有 DoubleAccumulator )使用提供的函式更新值。它允許我們在許多場景中實作無鎖演算法。當多個執行緒更新一個公共值時,通常最好使用 AtomicLong

讓我們看看它是如何工作的。為了建立它,您需要在建構函式中設定兩個參數。第一個是用於計算累加結果的函式。通常,您會使用 sum 方法。第二個參數列示我們的累加器的初始值。

現在,讓我們使用初始值 10000 建立 LongAccumulator ,然後從多個執行緒呼叫 accumulate() 方法。最終結果是什麽?

LongAccumulator balance = new LongAccumulator(Long::sum, 10000L);
Runnable w = () -> balance.accumulate(1000L);
ExecutorService executor = Executors.newFixedThreadPool(50);
for (int i = 0; i < 50; i++) {
executor.submit(w);
}
executor.shutdown();
if (executor.awaitTermination(1000L, TimeUnit.MILLISECONDS))
System.out.println("Balance: " + balance.get());
assert balance.get() == 60000L;

5. 十六進制格式

【Hex Format】




有時我們需要在十六進制格式的字串、字節或字元之間進行轉換。從 Java 17 開始,您可以使用 HexFormat 類。只需建立一個 HexFormat 例項,然後您就可以格式化例如輸入 byte 到十六進制字串的表。你也可以例如將輸入的十六進制字串解析為字節表,如下所示。

HexFormat format = HexFormat.of();
byte[] input = newbyte[] {1270-50105};
String hex = format.formatHex(input);
System.out.println(hex);
byte[] output = format.parseHex(hex);
assert Arrays.compare(input, output) == 0;


6. 陣列的二分法檢索

【Binary Search for Arrays】




假設我們想在排序表中插入一個新元素。 Arrays.binarySearch() 返回搜尋鍵的索引(如果包含)。否則,它返回一個 i 插入點,我們可以使用它來計算新鍵的索引: -(insertion point)-1 。此外, binarySearch 方法是在 Java 中尋找已排序陣列中元素的最簡單、最有效的方法。

範例如下,我們有一個輸入表,其中四個元素按升序排列。我們想在此表中插入數位「3」。這是我們如何計算插入點的索引的方法。

int[] t = newint[] {1245};
int x = Arrays.binarySearch(t, 3);
assert ~x == 2;

7. 位圖

【Bit Set】




如果我們需要對位陣列執行一些操作怎麽辦?你會為此使用``boolean[] ?有一種更有效和記憶體效率更高的方法來實作它。它是 BitSet 類。BitSet 類允許我們儲存和操作位陣列。與 boolean[] 相比,它消耗的記憶體少了 8 倍。我們可以對陣列執行邏輯操作,例如 and , or , xor`。

假設我們有兩個輸入位陣列。我們想對它們執行 xor 操作。事實上,只返回那些元素的操作,這些元素只插入一個陣列,而不是另一個陣列。為此,我們需要建立兩個「BitSet」例項並在其中插入範例元素,如下所示。最後應該在 BitSet 例項之一上呼叫 xor 方法。它將第二個 BitSet 例項作為參數。

BitSet bs1 = new BitSet();
bs1.set(0);
bs1.set(2);
bs1.set(4);
System.out.println("bs1 : " + bs1);
BitSet bs2 = new BitSet();
bs2.set(1);
bs2.set(2);
bs2.set(3);
System.out.println("bs2 : " + bs2);
bs2.xor(bs1);
System.out.println("xor: " + bs2);

這是執行上面可見的程式碼後的結果。


8. 移相器

【Phaser】




和這裏的其他一些例子一樣,它也是 Java Concurrent 包的元素。它被稱為「移相器」。它與更知名的 CountDownLatch 非常相似。但是,它提供了一些附加功能。它允許我們設定在繼續執行之前需要等待的動態執行緒數。使用「Phaser」,定義的執行緒數需要在屏障上等待,然後才能進入下一步執行。多虧了這一點,我們可以協調執行的多個階段。

在下面的範例中,我們設定了 50 個執行緒的屏障,以便在進入下一個執行階段之前到達。然後我們建立一個執行緒,在 Phaser 例項上呼叫方法 arriveAndAwaitAdvance() 。它阻塞執行緒,直到所有 50 個執行緒都不會到達屏障。然後它進入 phase-1 並呼叫方法 arriveAndAwaitAdvance()

Phaser phaser = new Phaser(50);
Runnable r = () -> {
System.out.println("phase-0");
phaser.arriveAndAwaitAdvance();
System.out.println("phase-1");
phaser.arriveAndAwaitAdvance();
System.out.println("phase-2");
phaser.arriveAndDeregister();
};
ExecutorService executor = Executors.newFixedThreadPool(50);
for (int i = 0; i < 50; i++) {
executor.submit(r);
}

這是執行上面可見的程式碼後的結果。


<END>