Quality Outreach Heads-up – JDK 21: Sequenced Collections Incompatibilities

原文はこちら。
The original article was written by Stuart Marks (Consulting Member of Technical Staff, Oracle).
https://inside.java/2023/05/12/quality-heads-up/

OpenJDK Quality Groupは、リリースの全体的な品質向上の手段としてOpenJDK Early Accessビルドを使ってのFOSSプロジェクトのテストを推進しています。

Quality Outreach
https://wiki.openjdk.java.net/display/quality/Quality+Outreach

このHeads upは、関係するプロジェクトに送られる定期的なコミュニケーションの一部です。

JDK 21 EA builds 22 & Sequenced Collections Heads-up
https://mail.openjdk.org/pipermail/quality-discuss/2023-May/001118.html

このプログラムの詳細と参加方法については、上記wikiをご覧ください。

Heads-Up – JDK 21 – Potential Sequenced Collections Incompatibilities

Sequenced Collection JEPがJDK 21 build 20に統合されました。

JEP 431: Sequenced Collections
https://openjdk.org/jeps/431

このJEPではいくつかの新たなインターフェースをcollectionフレームワークのインターフェース階層に導入し、これらのインターフェースでは新しいデフォルトメソッドが導入されます。このような変更が行われると、ソースやバイナリの非互換性をもたらすコンフリクトが発生する可能性があります。発生するコンフリクトは、新しいコレクションを実装するコードや、既存のコレクションクラスをサブクラス化するコードで発生します。コレクションの実装を単に使用するコードは、ほとんど影響を受けません。

発生し得るコンフリクトにはいくつかの種類があります。

1つ目は、単純なメソッド名の衝突です。同名のメソッドがすでに存在しているが、戻り値の型やアクセス修飾子が異なっている場合です。もうひとつは、covariant overrides (共変のオーバーライド) によって生じる、継承されたデフォルト・メソッドの実装が異なることによるコンフリクトです。collectionフレームワークの異なる部分から複数のインターフェースを実装している場合、クラスは複数のデフォルトメソッドを継承する可能性があります。3つ目の例は、型推論で発生します。型推論(例えばvarの使用)では、コンパイラはそのローカル変数の型を推論します。他のコードでは、推論された型と一致の必要がある、明示的に宣言された型を使用する可能性があります。インターフェイスの階層を変更すると、推論される型が異なってしまい、非互換性が生じる可能性があります。

以下の例で、潜在的な非互換性を緩和するための詳細と戦略を説明します。

Method Naming Conflict

新しいメソッドを持つ新しいインターフェースが、既存のCollections型階層に後付けされました。これらの新しいメソッドは、既存のクラスのメソッドと衝突する可能性があります。例えば、次のようなクラスがあったとします。

class MyList<E> implements List<E> {
    /**
    * Returns the first element of this list, or an empty
    * Optional if this list is empty.
    */
    public Optional<E> getFirst() { … }
        …
    }
}

SequencedCollectionインターフェースはListを継承しており、新たなメソッドE getFirst()を定義しています。

戻り値の型が異なるので、ソースの非互換性が発生します(ただし、既存のバイナリは古いメソッドを呼び続けるので、バイナリの非互換性にはならないはずです)。アクセス修飾子をめぐって関連するコンフリクトが発生する可能性があります。例えば、パッケージアクセスのメソッドは、パブリックアクセスでなければならないインターフェイスで定義されたメソッドをオーバーライドできません。残念ながら、ソースの非互換性を緩和する唯一の方法は、競合するメソッドの名前を変更するか、MyListがListを実装しないように型階層を再編成するしかありません。

Covariant Override Conflicts

ListインターフェースとDequeインターフェースの両方で、reversed()メソッドのcovariant overrides (共変のオーバーライド)共変のオーバーライドが起こりえます。

  • List – List<E> reversed();
  • Deque – Deque<E> reversed();

これは、コレクションの実装が、ListまたはDequeのようなコレクションの1つの「ファミリー」だけを実装し、両方を実装しない限り、問題なく動作します。しかし、場合によっては、コレクションの実装がListDequeの両方を実装することを決定するかもしれません:

class MyDoubleEndedList<E> implements List<E>, Deque<E> { … }

これはJDK20では問題なく動作しますが、JDK21のビルド20から失敗するようになります。その理由は、reversed()メソッドの定義が、一方はListを返し、他方はDequeを返すという相反するものを継承しているためです。コンパイラはどちらか一方を選ぶことができないため、コンパイル時エラーが発生します。

解決策は、MyDoubleEndedListに、ListDequeの両方を返すreversed()メソッドを追加することです。これは、MyDoubleEndedList自体(またはサブクラス)であってもよいし、この目的のために定義された別のインタフェースであってもかまいません。返されたオブジェクトは、元のコレクションを逆順に並べたビューを実装する必要があります。

JDK自体にこの例があります。java.util.LinkedListクラスはListDequeの両方を実装しており、LinkedListの逆順ビューのインスタンスを返すreversed()メソッドを追加することでこの問題を解決しました。

jdk/LinkedList.java at jdk-21+20
https://github.com/openjdk/jdk/blob/jdk-21%2B20/src/java.base/share/classes/java/util/LinkedList.java#L1276

逆順ビューの実装は多少面倒ですが、難しいものではありません。実際のロジックはありません。基本的には、ほとんどのメソッドを逆順Listビューや逆順Dequeビューにオーバーライドして委譲します。この2つのビューには、これらのインターフェースのデフォルトメソッドで利用できる実装があります。

Type Inference

型推論の結果は異なる場合があり、既存のソースコードにより仮定された推論結果と衝突することがあります。例えば、次のようなコードを考えてみましょう。

List<Collection<String>> m() {
    var list = List.of(new ArrayDeque<String>(), List.of("foo"));
    return list;
}

これはJDK 20ではコンパイルできますが、JDK 21 build 20以後ではコンパイル時エラーが発生します。その理由は、新しいコレクション型の追加に伴い、推論されるlistの型が変化するためです。

List.of(a,b)の型はList<T>で、Tは引数abの共通のスーパータイプ(より正式にはleast upper bound「最小上限」)です。JDK 20ではTCollection<String>だったので、リストの型は List<Collection<String>でした。これはメソッドの戻り値の型と一致するので、エラーにはなりません。

JDK 21 build 20では、SequencedCollectionインターフェースがListDequeの両方に後付けされたので、新たな共通のスーパータイプTSequencedCollectionに変わります。そのため、listの型はList<SequencedCollection<String>>となり、メソッドの戻り値の型と一致しないため、コンパイル時にエラーが発生します。

修正方法にはいくつかありますが、おそらく一番簡単なのは、varを使わずに明示的にlistに対して型を宣言することでしょう。

List<Collection<String>> list = List.of(new ArrayDeque<String>(), List.of("foo"));

これはメソッドの戻り値の型に一致する型でlistを宣言しています。結果としてメソッドの戻り値と一致しない、異なる型の推論を抑止できます。

Sequenced Collectionの詳細情報は、以下のリソースを必ず確認してください。

JEP 431 – Sequenced Collection

https://openjdk.org/jeps/431

Inside Java Podcast Episode 31

https://inside.java/2023/04/25/podcast-031/

Inside Java Podcast Newscast 45

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト /  変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト /  変更 )

%s と連携中