【问题标题】:Why is Stream.sorted not type-safe in Java 8?为什么 Stream.sorted 在 Java 8 中不是类型安全的?
【发布时间】:2018-11-09 03:40:04
【问题描述】:

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

?

显然,实现这一点的人(在编程和工程方面比我早几年)肯定有一个我看不到的很好的理由,但那是什么原因呢?

【问题讨论】:

  • 你的意思是Comparator?已经有一个overload。不知道为什么你需要C
  • 这是不可能的。
  • 由于泛型的性质。泛型的全部意义在于您正在实现 generic 功能,它排除了特定于类型的逻辑。您的示例代码没有任何意义。我认为您正在尝试重新定义 T(这是不可能的),但实际上您只是为无用的参数定义了一个类型。
  • 但是sorted 不接受任何参数作为输入 - 你要从哪里获取这个参数?
  • 这个问题早于 java 8 btw

标签: java java-8 java-stream comparable


【解决方案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&lt;C&gt; 可以从Class&lt;T&gt; 分配。考虑以下层次结构:

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

public class Bar extends Foo {}

public class Qux extends Foo {}

您现在可以拥有Bar 元素中的Stream,但尝试将其排序为好像它是Qux 元素中的Stream

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

由于BarQux 都匹配Comparable&lt;? super Foo&gt;,因此没有编译时错误,因此没有添加类型安全。此外,需要 Class 参数的含义是它将用于强制转换。在运行时,如上所示,这仍然会导致ClassCastExceptions。如果Class 用于强制转换,那么该参数是完全没用的;我什至认为它有害。

下一个合乎逻辑的步骤是尝试并要求 C 扩展 T 以及 Comparable&lt;? super T&gt;。例如:

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

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


一些相关说明。

关于Stream.sorted(Comparator):不是Stream 使这个方法类型安全,而是ComparatorComparator 确保可以比较元素。为了说明,按元素的自然顺序对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 显然能够处理它定义的任何类型。

【讨论】:

    【解决方案2】:

    Stream#sorteddocumentation 完美诠释了这一点:

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

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

    如果你问为什么如果Stream 的内容没有实现Comparable,该方法不会引发编译器错误,那是因为T 没有被强制扩展Comparable,并且如果不调用Stream#map,就无法更改T;它似乎只是一种方便的方法,因此当元素已经实现 Comparable 时,不需要提供显式的 Comparator

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

    【讨论】:

    • it would be because T is not forced to extend Comparable 我知道,但是为什么 sorted 接受一个 T 而不是我在问题末尾建议的内容?
    • 已经有一个重载的Stream#sorted 方法接受Comparator。接受Comparable 没有多大意义。
    • 我想你想说的是因为sorted 是 Stream 类的一部分(它有类型参数TT 不应该实现Comparable 因为那将使用流管道强制 所有 类型实现 Comparable。
    • @user7 正是 :) 这就是文档声明“按自然顺序排序”的原因。不实现Comparable 的对象没有自然顺序。
    • @JacobG。实际上,通过方法参数限制接收者的类型是可能的,甚至存在这样的方法,即sorted(Comparator.naturalOrder()) 只能在流上调用,如果它的元素是可比较的。添加另一个不带参数的 sorted() 方法是经过深思熟虑的决定。
    【解决方案3】:

    您将如何实现它? sorted 是一个中间操作(可以在其他中间操作之间的任何位置调用),这意味着您可以从一个不可比较的流开始,但在 Comparable 的流上调用 sorted

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

    您提议的内容将参数作为输入,但 Stream::sorted 没有,因此您不能这样做。重载版本接受Comparator - 这意味着您可以按属性对某些内容进行排序,但仍返回Stream&lt;T&gt;。如果您尝试编写 Stream 接口/实现的最小框架,我认为这很容易理解。

    【讨论】:

    • 这个例子中的sorted 又知道了类型,那就是Stream,不是吗?
    • 好吧,替代方法是不使用零参数 sorted() 方法,因此需要您改用 sorted(Comparator.naturalOrder())。那将是类型安全的。这是一个权衡。对比this answer讨论排序操作的特殊处理。
    • @Holger 我希望您提供答案,您在 cmets 中分享的内容非常有价值,但可能会被忽略。
    • @Holger 说得好,既然你已经明确表示了这一点,我真的不明白为什么一开始就提供了简单的sorted
    猜你喜欢
    • 1970-01-01
    • 2013-07-31
    • 2015-05-28
    • 2010-10-10
    • 2010-09-20
    • 2014-12-19
    • 2011-01-27
    • 2014-07-24
    相关资源
    最近更新 更多