web-dev-qa-db-ja.com

Stream.sortedがJava 8でタイプセーフではないのはなぜですか?

これは、OracleのJDK 8実装のStreamインターフェースからのものです。

public interface Stream<T> extends BaseStream<T, Stream<T>> {
    Stream<T> sorted();
} 

また、実行時にこれを爆破することは非常に簡単であり、コンパイル時に警告は生成されません。以下に例を示します。

class Foo {
    public static void main(String[] args) {
        Arrays.asList(new Foo(), new Foo()).stream().sorted().forEach(f -> {});
    }
}

正常にコンパイルされますが、実行時に例外がスローされます。

Exception in thread "main" Java.lang.ClassCastException: Foo cannot be cast to Java.lang.Comparable

コンパイラが実際にそのような問題をキャッチできる場所でsortedメソッドが定義されなかった理由は何でしょうか?たぶん私は間違っていますが、これは簡単ではありません:

interface Stream<T> {
    <C extends Comparable<T>> void sorted(C c);
}

明らかに、これを実装している人たち(プログラミングとエンジニアリングに関しては何年も先を行っている人)には、私が見ることができない非常に正当な理由があるに違いありませんが、その理由は何ですか?

38
Koray Tugay

基本的に、コンパイラーに「ちょっと、この1つのメソッドはクラスレベルで定義されたよりも特定の境界に一致する型パラメーターを要求する」という方法があるかどうかを尋ねています。これはJavaでは不可能です。このような機能は便利かもしれませんが、混乱や複雑化が予想されます。

また、ジェネリックが現在どのように実装されているかでStream.sorted()タイプセーフにする方法もありません。 Comparatorの要求を避けたい場合はそうではありません。たとえば、次のようなものを提案していました。

public interface Stream<T> {

    <C extends Comparable<? super T>> Stream<T> sorted(Class<C> clazz);

} // other Stream methods omitted for brevity

残念ながら、Class<C>Class<T>から割り当て可能であるという保証はありません。次の階層を考慮してください。

public class Foo implements Comparable<Foo> { /* implementation */ }

public class Bar extends Foo {}

public class Qux extends Foo {}

Stream of Bar要素を持つことができますが、Stream of Qux要素であるかのようにソートしてみてください。

Stream<Bar> stream = barCollection.stream().sorted(Qux.class);

BarQuxの両方がComparable<? super Foo>と一致するため、コンパイル時エラーは発生せず、したがってタイプセーフは追加されません。また、Class引数を必要とする意味は、キャストに使用されることです。実行時に、これは上記のようにClassCastExceptionsになります。 Classがキャストに使用されない場合、引数はまったく役に立ちません。私もそれを有害だと思います。

次の論理ステップは、C extend TおよびComparable<? super T>を試して要求することです。例えば:

<C extends T & Comparable<? super T>> Stream<T> sorted(Class<C> clazz);

これもJavaで不可能であり、コンパイルエラーが発生します。「型パラメーターの後に他の境界を続けることはできません」。これが可能であったとしても、すべてを解決するとは思わない(もしあれば)。


関連するメモ

Stream.sorted(Comparator)に関して:このメソッドをタイプセーフにするのはStreamではなく、Comparatorです。 Comparatorは、要素を比較できるようにします。例として、Streamを要素の自然な順序でソートするタイプセーフな方法は次のとおりです。

Stream<String> stream = stringCollection.stream().sorted(Comparator.naturalOrder());

naturalOrder()はその型パラメーターextend Comparableを必要とするため、これは型安全です。 Streamのジェネリック型がComparableを拡張しなかった場合、境界は一致せず、コンパイルエラーが発生します。しかし、繰り返しますが、要素がComparatorであることを要求するのはComparableです* Streamは単に気にしません。

では、なぜ開発者が最初にsortedに引数なしのStreamメソッドを含めたのかという疑問になります。これは歴史的な理由によるものと思われ、Holgerによる 別の質問への回答 で説明されています。


*この場合、Comparatorは要素がComparableである必要があります。一般に、Comparatorは明らかに、定義されているすべての型を処理できます。

29
Slaw

documentation for Stream#sortedは完全に説明しています:

自然順序に従ってソートされた、このストリームの要素で構成されるストリームを返します。このストリームの要素がComparableでない場合、端末操作の実行時にJava.lang.ClassCastExceptionがスローされる場合があります。

引数を受け入れないオーバーロードメソッド(Comparatorを受け入れるものではない)を使用しており、FooComparableを実装していません。

Streamの内容がComparableを実装していない場合にメソッドがコンパイラエラーをスローしない理由を尋ねている場合、TComparable、およびTは、Stream#mapへの呼び出しなしでは変更できません。これは便利なメソッドにすぎないようですので、要素がすでにComparatorを実装している場合、明示的なComparableを提供する必要はありません。

タイプセーフであるためには、TComparableを拡張する必要がありますが、Comparable以外のオブジェクトがストリームに含まれないようにするのはばかげています。

18
Jacob G.

どのように実装しますか? sortedは中間操作です(他の中間操作の間のどこでも呼び出すことができます)。つまり、Comparableではないストリームから開始できますが、その上でsortedを呼び出しますisComparable

Arrays.asList(new Foo(), new Foo())
      .stream()
      .map(Foo::getName) // name is a String for example
      .sorted()
      .forEach(f -> {});

提案しているものは入力として引数を取りますが、Stream::sortedはそうではないので、それはできません。オーバーロードバージョンはComparatorを受け入れます。つまり、プロパティで何かを並べ替えることができますが、Stream<T>。 Streamインターフェイス/実装の最小限のスケルトンを記述しようとすると、これは非常に理解しやすいと思います。

15
Eugene