为什么 Stream.sorted 在 Java 8 中不是类型安全的?

Why is Stream.sorted not type-safe in Java 8?

这是来自 Oracle 实现的 Stream 接口 JDK 8:

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);
}

?

显然,实现这个的人(就编程和工程而言,他们比我领先几年)一定有一个我看不到的很好的理由,但那是什么原因?

您将如何实施? sorted 是一个中间操作(可以在其他中间操作之间的任何地方调用),这意味着您可以从一个不可比较的流开始,但在 是 [= 的流上调用 sorted 22=] Comparable:

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

您提议的内容需要参数作为输入,但 Stream::sorted 没有,所以您不能那样做。重载版本接受 Comparator - 这意味着您可以按 属性 对某些内容进行排序,但仍然 return Stream<T>。我认为,如果您尝试编写 Stream 的最小框架 interface/implementation,那么这很容易理解。

Stream#sorteddocumentation 完美地解释了它:

Returns a stream consisting of the elements of this stream, sorted according to natural order. If the elements of this stream are not Comparable, a java.lang.ClassCastException may be thrown when the terminal operation is executed.

您正在使用不接受参数的重载方法(不是接受 Comparator 的方法),并且 Foo 未实现 Comparable.

如果您问如果 Stream 的内容没有实现 Comparable,为什么该方法不会抛出编译器错误,那是因为 T 不是强制扩展 Comparable,如果不调用 Stream#map,则无法更改 T;它似乎只是一种方便的方法,因此当元素已经实现 Comparable.

时,不需要提供明确的 Comparator

为了类型安全,T 必须扩展 Comparable,但那将是荒谬的,因为它会阻止流包含任何不是 Comparable.

本质上,您是在问是否有办法告诉编译器,“嘿,这个方法需要类型参数匹配比在 class 级别定义的更具体的边界”。这在 Java 中是不可能的。这样的功能可能有用,但我也希望混淆 and/or 复杂。

也没有办法使 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 {}

您现在可以拥有 StreamBar 个元素,但尝试将其排序为 StreamQux 个元素。

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

因为 BarQux 匹配 Comparable<? super Foo> 没有编译时错误,因此没有添加类型安全。此外,要求 Class 参数的含义是它将用于转换。在运行时,如上所示,这仍然会导致 ClassCastExceptions。如果 Class 不是 用于强制转换,那么这个参数是完全无用的;我什至认为它有害。

下一个合乎逻辑的步骤是尝试并要求 C 扩展 T 以及 Comparable<? super T>。例如:

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

这在 Java 中也是不可能的,会导致编译错误:"type parameter cannot be followed by other bounds"。即使这是可能的,我也不认为它会解决所有问题(如果有的话)。


一些相关的笔记。

关于 Stream.sorted(Comparator):使此方法类型安全的不是 Stream,而是 ComparatorComparator 确保可以比较元素。为了说明,按元素的自然顺序对 Stream 进行排序的类型安全方法是:

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

这是类型安全的,因为 naturalOrder() 需要其类型参数扩展 Comparable。如果 Stream 的泛型类型没有扩展 Comparable 那么边界将不匹配,导致编译错误。但同样,Comparator 要求元素为 Comparable*Stream 根本不在乎。

那么问题就变成了,为什么开发人员首先要为 Stream 添加一个无参数的 sorted 方法?这似乎是出于历史原因,Holger 在 an answer to another question 中对此进行了解释。


* 在这种情况下,Comparator 要求元素为 Comparable。一般来说,Comparator 显然能够处理它定义的任何类型。