これは、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);
}
?
明らかに、これを実装している人たち(プログラミングとエンジニアリングに関しては何年も先を行っている人)には、私が見ることができない非常に正当な理由があるに違いありませんが、その理由は何ですか?
基本的に、コンパイラーに「ちょっと、この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);
Bar
とQux
の両方がComparable<? super Foo>
と一致するため、コンパイル時エラーは発生せず、したがってタイプセーフは追加されません。また、Class
引数を必要とする意味は、キャストに使用されることです。実行時に、これは上記のようにClassCastException
sになります。 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
は明らかに、定義されているすべての型を処理できます。
documentation for Stream#sorted
は完全に説明しています:
自然順序に従ってソートされた、このストリームの要素で構成されるストリームを返します。このストリームの要素がComparableでない場合、端末操作の実行時にJava.lang.ClassCastExceptionがスローされる場合があります。
引数を受け入れないオーバーロードメソッド(Comparator
を受け入れるものではない)を使用しており、Foo
はComparable
を実装していません。
Stream
の内容がComparable
を実装していない場合にメソッドがコンパイラエラーをスローしない理由を尋ねている場合、T
がComparable
、およびT
は、Stream#map
への呼び出しなしでは変更できません。これは便利なメソッドにすぎないようですので、要素がすでにComparator
を実装している場合、明示的なComparable
を提供する必要はありません。
タイプセーフであるためには、T
はComparable
を拡張する必要がありますが、Comparable
以外のオブジェクトがストリームに含まれないようにするのはばかげています。
どのように実装しますか? 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インターフェイス/実装の最小限のスケルトンを記述しようとすると、これは非常に理解しやすいと思います。