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

2022-09-01 03:45:09

这是来自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 {}

您现在可以拥有一个元素,但尝试像元素一样对其进行排序。StreamBarStreamQux

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

由于两者都匹配,因此没有编译时错误,因此没有添加类型安全。此外,要求参数的含义是它将用于转换。在运行时,如上所示,这仍然会导致 s。如果不用于转换,则该参数完全无用;我甚至认为这是有害的。BarQuxComparable<? super Foo>ClassClassCastExceptionClass

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

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

这在Java中也是不可能的,并且会导致编译错误:“类型参数不能跟在其他边界后面”。即使这是可能的,我也不认为它能解决所有问题(如果有的话)。


一些相关的说明。

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

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

这是类型安全的,因为需要其类型参数扩展 。如果 的泛型类型未扩展,则边界将不匹配,从而导致编译错误。但同样,这是要求元素是*的,而根本不在乎。naturalOrder()ComparableStreamComparableComparatorComparableStream

那么问题来了,为什么开发人员首先要包含一个无参数的方法呢?这似乎是出于历史原因,并在Holger对另一个问题的回答中进行了解释。sortedStream


* 在这种情况下,比较器要求元素具有可比性。通常,比较器显然能够处理它所定义的任何类型的类型。


答案 2

的文档完美地解释了它:Stream#sorted

返回由此流的元素组成的流,该流根据自然顺序排序。如果此流的元素不具有可比性,则在执行终端操作时可能会引发 java.lang.ClassCastException。

您正在使用不接受任何参数(不接受参数)且不实现 的重载方法。ComparatorFooComparable

如果你问为什么该方法不抛出编译器错误,如果不实现,那是因为没有强制扩展,并且不能在没有调用的情况下更改;它似乎只是一个方便的方法,因此当元素已经实现时,不需要提供显式。StreamComparableTComparableTStream#mapComparatorComparable

要使它是类型安全的,必须扩展 ,但这将是荒谬的,因为它会阻止流包含任何不是 的对象。TComparableComparable


推荐