引用自身作为通用的类?

时间:2014-08-10 23:05:30

标签: java class generics java-8 java-stream

grepcode上浏览java 8的流实现时,我在第772行的java.util.stream.ReduceOps.java中发现了以下声明:

 private static final class ReduceTask<P_IN, P_OUT, R,
                                    S extends AccumulatingSink<P_OUT, R, S>>
      extends AbstractTask<P_IN, P_OUT, S, ReduceTask<P_IN, P_OUT, R, S>> {

似乎类ReduceTask正在扩展或更好地引用自身。这真的有可能吗?

2 个答案:

答案 0 :(得分:2)

这里发生的是AbstractTask的第四个类型参数(K)本质上是自我类型AbstractTask的声明如下:

abstract class AbstractTask<P_IN, P_OUT, R,
                            K extends AbstractTask<P_IN, P_OUT, R, K>>

和K的Javadoc说:

* @param <K> Type of parent, child and sibling tasks

它有抽象的方法,如:

protected abstract K makeChild(Spliterator<P_IN> spliterator);

这种模式实际上显示了一些频率,通常在构建器中,您在基类中有功能(如AbstractTask),它想要引用类型的具体子类 - 安全的方式。它看起来奇怪的自我指涉,但事实并非如此。

考虑使用类型为Foo的构建器:

public interface FooBuilder {
    FooBuilder a(String s);
    FooBuilder b(String s);
    Foo build();
}
对于BarBuilder类型,

Bar扩展了Foo

public interface BarBuilder extends FooBuilder {
    BarBuilder c(String s);
    Bar build();
}

现在你要创建一个Bar:

Bar b = makeBarBuilder()
            .a("a")
            .c("c")       // ERROR
            .build();

您在调用c()时出错,因为继承的方法a()返回FooBuilder,其中没有c()方法。哎呀。你可以通过使用强制转换来解决这个问题,但也很丑陋,因为你还没有告诉类型系统你可能拥有的关于FooBuilder和BarBuilder之间关系的一切。

这里的治疗方法是使用这种棘手的自我型模式:

BarBuilder

现在您可以创建一个BarBuilder,其继承的Foo方法仍然返回Bar / BarBuilder。当你第一次看到它时它很难读,但非常强大。

答案 1 :(得分:0)

ReduceTask选择扩展AbstractTask,使其选择AbstractTask中的最后一个类型参数作为其自身。这是它的选择。这类似于String选择实施Comparable<String>的方式,即它选择Comparable的类型参数作为其自身。

如果作者想要的话,还可以编写ReduceTask来选择扩展AbstractTask,其中最后一个类型参数是其他类型的参数:

class ReduceTask<P_IN, P_OUT, R, S extends AccumulatingSink<P_OUT, R, S>>
      extends AbstractTask<P_IN, P_OUT, S, SomeOtherTask<P_IN, P_OUT, R, S>>

(假设SomeOtherTask满足AbstractTask中该类型参数的给定界限。)

AbstractTask在Java中没有办法保证子类扩展它以使最后一个类型参数与类完全相同。