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

Kor*_*gay 38 java comparable java-8 java-stream

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

public interface Stream<T> extends BaseStream<T, Stream<T>> {
    Stream<T> sorted();
} 
Run Code Online (Sandbox Code Playgroud)

并且很容易在运行时将其清除,并且在编译时不会生成警告.这是一个例子:

class Foo {
    public static void main(String[] args) {
        Arrays.asList(new Foo(), new Foo()).stream().sorted().forEach(f -> {});
    }
}
Run Code Online (Sandbox Code Playgroud)

这将编译得很好,但会在运行时抛出异常:

Exception in thread "main" java.lang.ClassCastException: Foo cannot be cast to java.lang.Comparable
Run Code Online (Sandbox Code Playgroud)

sorted在编译器实际可以捕获这些问题的地方没有定义该方法的原因是什么?也许我错了,但不是这么简单:

interface Stream<T> {
    <C extends Comparable<T>> void sorted(C c);
}
Run Code Online (Sandbox Code Playgroud)

显然,那些实现这一点的人(考虑到编程和工程方面比我早了几年)必须有一个很好的理由,我无法看到,但这是什么原因?

Sla*_*law 29

基本上,你问是否有办法告诉编译器," 嘿,这一方法要求类型参数匹配比类级别定义的更具体的边界 ".这在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
Run Code Online (Sandbox Code Playgroud)

不幸的是,无法保证Class<C>可以分配Class<T>.考虑以下层次结构:

public class Foo implements Comparable<Foo> { /* implementation */ }

public class Bar extends Foo {}

public class Qux extends Foo {}
Run Code Online (Sandbox Code Playgroud)

现在,您可以拥有StreamBar元素,但试图解决它,如果它是一个StreamQux元素.

Stream<Bar> stream = barCollection.stream().sorted(Qux.class);
Run Code Online (Sandbox Code Playgroud)

由于这两个BarQux比赛Comparable<? super Foo>没有编译时错误,因此不添加类型安全.此外,要求Class论证的含义是它将用于铸造.在运行时,如上所示,这仍然可以导致ClassCastExceptions.如果Class 用于铸造那么论证完全没用; 我甚至认为它有害.

下一个逻辑步骤是尝试需要C延长T的,以及Comparable<? super T>.例如:

<C extends T & Comparable<? super T>> Stream<T> sorted(Class<C> clazz);
Run Code Online (Sandbox Code Playgroud)

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


一些相关的说明.

关于Stream.sorted(Comparator):这不是Stream使这种方法类型安全,它是Comparator.该Comparator确保的元素进行比较.为了说明,Stream按元素的自然顺序对类型进行排序的类型安全方法是:

Stream<String> stream = stringCollection.stream().sorted(Comparator.naturalOrder());
Run Code Online (Sandbox Code Playgroud)

这是类型安全的,因为naturalOrder()它需要类型参数extend Comparable.如果Stream未扩展的泛型类型Comparable则边界不匹配,从而导致编译错误.但同样,这Comparator需要元素Comparable*Stream根本不关心.

所以问题就变成了,为什么开发人员首先要包含一个无论证的sorted方法Stream?这似乎是出于历史原因,并在霍尔格的另一个问题的答案中得到解释.


*在这种情况下Comparator要求元素Comparable.通常,a Comparator显然能够处理它定义的任何类型.


Jac*_* G. 18

文件Stream#sorted解释它完美:

返回由此流的元素组成的流,按照自然顺序排序.如果此流的元素不是Comparable,则执行终端操作时可能会抛出java.lang.ClassCastException.

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

如果你问为什么方法不会抛出编译器错误,如果Stream没有实现的内容Comparable,那将是因为T没有强制扩展Comparable,并且T不能在没有调用的情况下改变Stream#map; 它似乎只是一种方便的方法,所以Comparator当元素已经实现时,没有明确的需要提供Comparable.

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

  • @JacobG.实际上,通过方法参数限制接收者的类型是可能的,并且这样的方法甚至存在,即`sorted(Comparator.naturalOrder())`只能在流上调用,如果它的元素是可比较的.故意决定添加另一个没有参数的`sorted()`方法. (8认同)

Eug*_*ene 15

你会如何实现?sorted是一个中间操作(可以在其他中间操作之间的任何地方调用),这意味着你可以从一个不可比较的流开始,但调用sorted一个 Comparable:

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

你提议的东西需要一个参数作为输入,但Stream::sorted不是,所以你不能这样做.重载版本接受Comparator- 意味着您可以按属性排序,但仍然返回Stream<T>.如果您尝试编写Stream接口/实现的最小骨架,我认为这很容易理解.

  • 好吧,替代方法是不要使用零参数`sorted()`方法,因此需要使用`sorted(Comparator.naturalOrder())`.这将是类型安全的.这是一个折衷.与[此答案](/sf/answers/3350749391/)讨论分类操作的特殊处理. (14认同)
  • 在这个例子中,`sorted`再次知道了类型,即Stream <String>,不是吗? (2认同)