为什么 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#sorted
的 documentation 完美地解释了它:
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 {}
您现在可以拥有 Stream
个 Bar
个元素,但尝试将其排序为 Stream
个 Qux
个元素。
Stream<Bar> stream = barCollection.stream().sorted(Qux.class);
因为 Bar
和 Qux
匹配 Comparable<? super Foo>
没有编译时错误,因此没有添加类型安全。此外,要求 Class
参数的含义是它将用于转换。在运行时,如上所示,这仍然会导致 ClassCastException
s。如果 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
,而是 Comparator
。 Comparator
确保可以比较元素。为了说明,按元素的自然顺序对 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
显然能够处理它定义的任何类型。
这是来自 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#sorted
的 documentation 完美地解释了它:
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 {}
您现在可以拥有 Stream
个 Bar
个元素,但尝试将其排序为 Stream
个 Qux
个元素。
Stream<Bar> stream = barCollection.stream().sorted(Qux.class);
因为 Bar
和 Qux
匹配 Comparable<? super Foo>
没有编译时错误,因此没有添加类型安全。此外,要求 Class
参数的含义是它将用于转换。在运行时,如上所示,这仍然会导致 ClassCastException
s。如果 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
,而是 Comparator
。 Comparator
确保可以比较元素。为了说明,按元素的自然顺序对 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
显然能够处理它定义的任何类型。