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

时间:2018-11-09 03:40:05

标签: java java-8 java-stream comparable

这来自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);
}

显然,实现这一目标的人(就编程和工程而言,我比我轻了几年)必须有一个很好的理由使我看不见,但是那是什么原因呢?

3 个答案:

答案 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);

由于BarQux都与Comparable<? super Foo>匹配,因此没有编译时错误,因此也没有添加类型安全性。同样,要求使用Class参数的含义是它将用于强制转换。如上所示,在运行时这仍可能导致ClassCastException s。如果没有使用Class 进行强制转换,则该参数将完全无用;我什至认为这很有害。

下一个合理的步骤是尝试要求CT一起扩展。例如:

Comparable<? super T>

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


一些相关注释。

关于<C extends T & Comparable<? super T>> Stream<T> sorted(Class<C> clazz); :使得此方法类型安全的不是Stream.sorted(Comparator),而是StreamComparator确保可以比较元素。为了说明这一点,按元素的自然顺序对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接口/实现的最小框架,这很容易理解。