注目イベント!
アドベントカレンダー2024開催中!
一年を締めくくる特別なイベント、アドベントカレンダーを今年も開催しています!
初心者からベテランまで楽しめる内容で、毎日新しい技術トピックをお届けします。
詳細はこちらから!
event banner

Java21がリリースされたので新機能の用途を考える(Sequenced Collections編)

| 8 min read
Author: masato-ubata masato-ubataの画像

はじめに

#

LTSであるJava21が2023/9/19にリリースされました。
Java21で追加された機能の動作を確認し、併せてその用途を考えてみました。

Java21で追加された新機能

#

以下の機能がJava21で提供されています。

  • 430: String Templates (Preview)
  • 431: Sequenced Collections ※本記事の対象
  • 439: Generational ZGC
  • 440: Record Patterns
  • 441: Pattern Matching for switch
  • 442: Foreign Function & Memory API (Third Preview)
  • 443: Unnamed Patterns and Variables (Preview)
  • 444: Virtual Threads
  • 445: Unnamed Classes and Instance Main Methods (Preview)
  • 446: Scoped Values (Preview)
  • 448: Vector API (Sixth Incubator)
  • 449: Deprecate the Windows 32-bit x86 Port for Removal
  • 451: Prepare to Disallow the Dynamic Loading of Agents
  • 452: Key Encapsulation Mechanism API
  • 453: Structured Concurrency (Preview)

概要

#

要素の順序を保持したコレクションのインターフェースです。

下図は新設されたインターフェースを中心に周辺のクラスを説明用にピックアップしたものです。
関連する主要クラス

機能の検証

#

SequencedCollection, SequencedSet

#

提供されているメソッドと概要は以下の通りです。
※Setの挙動は変わらなかったので、本記事への掲載は省略しています。

メソッド 概要
SequencedCollection reversed() 逆順にソートしたコレクションを生成する
void addFirst(E) 先頭に要素を追加する
void addLast(E) 最後に要素を追加する
E getFirst() 最初の要素を取得する
E getLast() 最後の要素を取得する
E removeFirst() 最初の要素を削除する
E removeLast() 最後の要素を削除する
  • 検証コード

      @DisplayName("通常の挙動を確認")
      @Test
      void normal() {
        final List<String> target = new ArrayList<>() {
          {
            add("ONE");
            add("TWO");
            add("THREE");
          }
        };
    
        assertEquals("ONE", target.getFirst());
    
        assertEquals("THREE", target.getLast());
    
        target.addFirst("ZERO");
        assertEquals(List.of("ZERO", "ONE", "TWO", "THREE"), target, "addFirstの結果を確認");
    
        target.addLast("FOUR");
        assertEquals(List.of("ZERO", "ONE", "TWO", "THREE", "FOUR"), target, "addLastの結果を確認");
    
        target.removeFirst();
        assertEquals(List.of("ONE", "TWO", "THREE", "FOUR"), target, "removeFirstの結果を確認");
    
        target.removeLast();
        assertEquals(List.of("ONE", "TWO", "THREE"), target, "removeLastの結果を確認");
    
        final var reversedTarget = target.reversed();
        assertEquals(List.of("ONE", "TWO", "THREE"), target, "元のコレクションに影響がないことを確認");
        assertEquals(List.of("THREE", "TWO", "ONE"), reversedTarget, "reversedの結果を確認");
      }
    
  • 検証コード(Java21より前の実装)

      @DisplayName("通常のListを操作して挙動を確認")
      @Test
      void normal() {
        final List<String> target = new ArrayList<>() {
          {
            add("ONE");
            add("TWO");
            add("THREE");
          }
        };
    
        assertEquals("ONE", target.get(0));
    
        assertEquals("THREE", target.get(target.size() - 1));
    
        target.add(0, "ZERO");
        assertEquals(List.of("ZERO", "ONE", "TWO", "THREE"), target);
    
        target.add("FOUR");
        assertEquals(List.of("ZERO", "ONE", "TWO", "THREE", "FOUR"), target);
    
        target.remove(0);
        assertEquals(List.of("ONE", "TWO", "THREE", "FOUR"), target);
    
        target.remove(target.size() - 1);
        assertEquals(List.of("ONE", "TWO", "THREE"), target);
    
        final List<String> reversedTarget = new ArrayList<>();
        for (int i = target.size(); i > 0; i--) {
          reversedTarget.add(target.get(i - 1));
        }
        assertEquals(List.of("ONE", "TWO", "THREE"), target, "元のコレクションに影響がないことを確認");
        assertEquals(List.of("THREE", "TWO", "ONE"), reversedTarget);
      }
    
Information

要素のない状態で操作した場合の挙動が若干異なります。

要素のない状態でgetFirst();を実行するとNoSuchElementExceptionがスローされます。
ほぼ同等の処理であるget(0);を要素のない状態で実行するとIndexOutOfBoundsExceptionがスローされます。

要素のない状態でgetLast();を実行するとNoSuchElementExceptionがスローされます。
ほぼ同等の処理であるtarget.get(target.size() - 1);を要素のない状態で実行するとIndexOutOfBoundsExceptionがスローされます。

SequencedMap

#

提供されているメソッドと概要は以下の通りです。

メソッド 概要
SequencedMap<K, V> reversed() 逆順にソートしたMapを生成する
Map.Entry<K,V> firstEntry() 最初の要素を取得する
Map.Entry<K,V> lastEntry() 最後の要素を取得する
Map.Entry<K,V> pollFirstEntry() 最初の要素を取得し、その要素を対象のMapから削除する
Map.Entry<K,V> pollLastEntry() 最後の要素を取得し、その要素を対象のMapから削除する
V putFirst(K, V) 先頭の要素を追加する
V putLast(K, V) 最後に要素を追加する
SequencedSet sequencedKeySet() Keyを先頭から順に取得する
SequencedCollection sequencedValues() Valueを先頭から順に取得する
SequencedSet<Map.Entry<K, V>> sequencedEntrySet() Key, Valueを先頭から順に取得する
  • 検証コード
      @DisplayName("通常の挙動を確認")
      @Test
      void normal() {
        final SequencedMap<Integer, String> target = new LinkedHashMap<>() {
          {
            put(1, "ONE");
            put(2, "TWO");
            put(3, "THREE");
          }
        };
    
        assertEquals("ONE", target.firstEntry().getValue());
    
        assertEquals("THREE", target.lastEntry().getValue());
    
        assertEquals("ONE", target.pollFirstEntry().getValue());
        assertEquals(Map.of(2, "TWO", 3, "THREE"), target, "pollFirstEntry後の状態を確認");
    
        assertEquals("THREE", target.pollLastEntry().getValue());
        assertEquals(Map.of(2, "TWO"), target, "pollLastEntry後の状態を確認");
    
        target.putFirst(1, "ONE");
        assertEquals(Map.of(1, "ONE", 2, "TWO"), target, "putFirstの結果を確認");
    
        target.putLast(4, "FOUR"); // sequencedXx, reversedがKeyの逆順ではなくIndexの逆順になることを確認するため追加
        target.putLast(3, "THREE");
        assertEquals(Map.of(1, "ONE", 2, "TWO", 4, "FOUR", 3, "THREE"), target, "現在のMapの状態を確認");
    
        assertEquals("[1, 2, 4, 3]", target.sequencedKeySet().toString());
        assertEquals("[1, 2, 4, 3]", target.keySet().toString(), "keySetの結果と同じか確認");
    
        assertEquals("[ONE, TWO, FOUR, THREE]", target.sequencedValues().toString());
        assertEquals("[ONE, TWO, FOUR, THREE]", target.values().toString(), "valuesの結果と同じか確認");
    
        assertEquals("[1=ONE, 2=TWO, 4=FOUR, 3=THREE]", target.sequencedEntrySet().toString());
    
        final var reversedTarget = target.reversed();
        assertEquals(Map.of(1, "ONE", 2, "TWO", 4, "FOUR", 3, "THREE"), target, "元のコレクションに影響がないことを確認");
        assertEquals(Map.of(4, "FOUR", 3, "THREE", 2, "TWO", 1, "ONE"), reversedTarget, "reversedの結果を確認");
      }
    
Information

下記のメソッドを要素のない状態で操作した場合、Nullが返却されます。
firstEntry(), lastEntry(), pollFirstEntry(), pollLastEntry()

使いどころ

#

Sequenced Collectionsの特性とその使いどころについて挙げます。

  • 順序が保証されるため、コレクションを順に処理したい場合に有効です。
  • 要素を安全に操作できるため、複数のプログラムが同じコレクションを参照しているような場合に有効です。
  • 要素の追加や削除にかかる計算量が少ないため、追加や削除が多い場合に有効です。
    • 要素の追加や削除に伴い、順序を再調整する必要がないため、O(1)で処理できます。
    • Java21未満の場合は、順序を再調整する必要があるため、O(n)となります。
  • Mapをキューやスタックとして扱いたい場合、pollFirstEntry()pollLastEntry()は便利。

豆蔵では共に高め合う仲間を募集しています!

recruit

具体的な採用情報はこちらからご覧いただけます。