【问题标题】:Designing interface for hierarchical entity层次实体设计接口
【发布时间】:2016-09-01 21:45:59
【问题描述】:

我必须为分层实体设计一个接口:

interface HierarchicalEntity<T extends HierarchicalEntity<T>> {
    T getParent();
    Stream<T> getAncestors();
}

getParent() 方面实现默认 getAncestors() 方法非常容易,前者将返回所有祖先的Stream

实现示例:

default Stream<T> getAncestors() {
    Stream.Builder<T> parentsBuilder = Stream.builder();
    T parent = getParent();
    while (parent != null) {
        parentsBuilder.add(parent);
        parent = parent.getParent();
    }
    return parentsBuilder.build();
}

但我还需要将this 包含到流中,这里出现了问题。 以下行不正确,因为this 的类型为HierarchicalEntity,而不是T

parentsBuilder.add(this); // type mismatch!

如何重新设计界面以使getAncestors() 包含this 到结果中?

【问题讨论】:

  • 不幸的是,这永远不会是完全类型安全的。 Java 没有自引用类型语法。我可以创建一个class Fake implements HierarchicalEntity&lt;SomeOtherType&gt;,而你的getAncestors 最终可能会以ClassCastException 失败。
  • 明白了。也许可以对整个界面进行一些重新设计。
  • 只需将this 转换为T 并将其添加到流构建器中
  • @federico-peralta-schaffner 可能的选项,但上面 Sotirios Delimanolis 描述的案例存在问题。似乎没有类型安全的方法来解决这个问题。
  • @Aliaxander 是的,没办法。不过,如果层次结构使用正确,我认为可以安全地假设强制转换是有效的。

标签: java interface java-8 hierarchical default-method


【解决方案1】:

在创建自引用类型时,这是一个反复出现的问题。在基类型(或接口)中,您不能强制 thisT 的赋值兼容。

当然,如果您确信所有子类型都将满足该约束条件,则可以将 this 未经检查地强制转换为 T。但是,当您需要 this 引用为 T 时,您必须执行此未经检查的强制转换。

更好的解决方案是添加一个抽象方法,例如

/**
    All subtypes should implement this as:

    public T myself() {
        return this;
    }
 */
public abstract T myself();

然后,当您需要作为T 的自引用时,您可以使用myself() 而不是this

default Stream<T> getAncestors() {
    Stream.Builder<T> parentsBuilder = Stream.builder();
    for(T node = myself(); node != null; node = node.getParent()) {
        parentsBuilder.add(parent);
    }
    return parentsBuilder.build();
}

当然,您不能强制子类将myself() 正确实现为return this;,但至少,您可以轻松地验证它们是否在运行时实现:

assert this == myself();

这个参考比较是一个非常便宜的操作,如果myself()被正确地实现为总是返回this,HotSpot可以预先证明这个比较总是true并且完全省略检查。

缺点是每个特化都必须有这种myself() { return this; } 的冗余实现,但另一方面,它完全没有未经检查的类型转换。另一种方法是在基类中将myself() 的非abstract 声明为@SuppressWarnings("unchecked") T myself() { return (T)this; },以将未经检查的操作限制在类型层次结构的单个位置。但是,您无法验证 this 是否真的属于 T 类型……

【讨论】:

    【解决方案2】:

    正如@SotiriosDelimanolis 所说,没有办法完全执行此操作。但是,如果您愿意假设接口按设计使用,您可以假设 thisT 的一个实例并简单地转换它:

    parentsBuilder.add((T)this);
    

    如果要避免强制转换,可以在子类中添加要覆盖的方法:

    interface HierarchicalEntity<T extends HierarchicalEntity<T>> {
        T getParent();
        T getThis();
        default Stream<T> getAncestors() {
            // ...
            parentsBuilder.add(getThis());
            // ...
        }
    }
    
    class Foo extends HierarchicalEntity<Foo> {
        // ...
        @Override
        public Foo getThis() {
            return this;
        }
    }
    

    现在我们可以以类型安全的方式获取this,但不能保证getThis() 已正确实现。它有可能返回Foo 的任何实例。所以,我猜,选择你的毒药吧。

    【讨论】:

    • @Aliaxander 如果可以选择使用通用抽象类,您可以创建一个构造函数AbstractEntity(T thisObj) 并让子类调用super(this),这样您就不必在每个抽象类上都实现getThis()子类。仍然不优雅,但我不知道是否有优雅的解决方案。
    • 您不能在显式构造函数调用中使用this,请参阅docs.oracle.com/javase/specs/jls/se8/html/…
    【解决方案3】:

    添加this 失败,因为HierarchicalEntity&lt;T&gt; 不一定是T;它可能是一个未知的亚型。但是,T 始终是 HierarchicalEntity&lt;T&gt;,因为您已经这样声明了。

    getAncestorsStream.Builder 的返回类型从T 更改为HierarchicalEntity&lt;T&gt;,这将允许您添加this

    default Stream<HierarchicalEntity<T>> getAncestors() {
        Stream.Builder<HierarchicalEntity<T>> parentsBuilder = Stream.builder();
        T parent = getParent();
        while (parent != null) {
            parentsBuilder.add(parent);
            parent = parent.getParent();
        }
        parentsBuilder.add(this);
        return parentsBuilder.build();
    }
    

    您可能希望声明 getParent 以返回 HierarchicalEntity&lt;T&gt; 以保持一致性。

    【讨论】:

    • 但是 OP 想要T 的流。
    • @shmosel 那是目前的界面设计,但是OP没有这样明确的声明。
    • @rgettman T 的实际类型应该有一些在HierarchicalEntity 中没有定义的方法,所以像getAncestors().map(ActualType::foo) 这样的代码变得无效。
    • @rgettman 我会假设设计表明了 OP 的要求,除非另有明确说明。并且鉴于定义的接口,拥有HierarchicalEntity 的流将毫无用处。
    猜你喜欢
    • 1970-01-01
    • 2010-09-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-02
    • 1970-01-01
    • 2011-05-01
    相关资源
    最近更新 更多