这来自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);
}
?
显然,实现这一目标的人(就编程和工程而言,我比我轻了几年)必须有一个很好的理由使我看不见,但是那是什么原因呢?
答案 0 :(得分:28)
本质上,您在问是否有一种方法可以告诉编译器:“ 嘿,这个方法需要类型参数匹配比在类级别定义的更具体的边界”。在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
个元素中的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>
在Java中这也是不可能的,并且会导致编译错误:“类型参数不能跟随其他界限”。即使这是可能的,我也不认为它可以解决所有问题(如果有的话)。
一些相关注释。
关于<C extends T & Comparable<? super T>> Stream<T> sorted(Class<C> clazz);
:使得此方法类型安全的不是Stream.sorted(Comparator)
,而是Stream
。 Comparator
确保可以比较元素。为了说明这一点,按元素的自然顺序对Comparator
进行排序的类型安全方法是:
Stream
这是类型安全的,因为Stream<String> stream = stringCollection.stream().sorted(Comparator.naturalOrder());
要求其类型参数扩展为naturalOrder()
。如果Comparable
的通用类型未扩展Stream
,则边界将不匹配,从而导致编译错误。但同样,Comparable
要求元素为Comparator
* ,而Comparable
根本不在乎。
问题就变成了,为什么开发人员首先要为Stream
包含无参数的sorted
方法?它似乎是出于历史原因,由Holger在an answer to another question中进行了解释。
*在这种情况下,Stream
要求元素为Comparator
。通常,Comparable
显然能够处理其定义为的任何类型。
答案 1 :(得分:18)
Stream#sorted
的{{3}}可以完美地说明这一点:
返回由该流的元素组成的流,并根据自然顺序进行排序。如果此流的元素不可比较,则在执行终端操作时可能会抛出java.lang.ClassCastException。
您正在使用的重载方法不接受任何参数(而不是接受Comparator
的参数),并且Foo
不实现Comparable
。
如果您要问为什么Stream
的内容未实现Comparable
时该方法为什么不引发编译器错误,那是因为没有强制T
扩展Comparable
,并且T
不能在不调用Stream#map
的情况下进行更改;这似乎只是一种方便的方法,因此当元素已经实现Comparator
时就不需要提供显式的Comparable
。
要使其具有类型安全性,T
必须扩展Comparable
,但这将是荒谬的,因为它将阻止流包含任何非{{1} }。
答案 2 :(得分:15)
您将如何实施? sorted
是一个中间操作(可以在其他中间操作之间的任何位置调用),这意味着您可以从不可比较的流开始,但是在是的流上调用sorted
Comparable
:
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接口/实现的最小框架,这很容易理解。