4

我要为我的一个项目添加通用性。我喜欢泛型,因为这使我的代码更加健壮、自我记录并消除了所有那些丑陋的演员表。

但是,我遇到了一个棘手的案例,并且在尝试为我的一个结构表达“递归”约束时遇到了一些问题。

这基本上是某种“通用”树,带有双链接(到子节点和父节点)。我最大程度地简化了课程以显示问题:

public class GenericTree<
    ParentClass extends GenericTree<?, ?>, 
    ChildClass extends GenericTree<?, ?>> 
{
    // Attributes
    private ArrayList<ChildClass> children = new ArrayList<ChildClass>();
    private ParentClass parent = null;

    // Methods 
    public void setParent(ParentClass parent) {
        this.parent = parent;
    }

    public void addChild(ChildClass child) {
        child.setParent(this);
        this.children.add(child);
    }
}

问题在于指令:child.setParent(this)

Java给出以下错误:

绑定不匹配:ChildClass 类型的方法 setParent(?) 不适用于
参数 (GenericTree)。通配符参数 ? 没有下限,实际上可能比参数 GenericTree 更严格

我想要的是能够表达类似的东西:

public class GenericTree<
    ParentClass extends GenericTree<?, ?>, 
    ChildClass extends GenericTree<[THIS_VERY_CLASS], ?>> 

要说子类的父类应该是它自己...

我看过一些关于自边界泛型的文章,但我不知道如何在这种情况下应用它。

任何帮助,将不胜感激。

4

2 回答 2

2

不要对不均匀的树使用泛型,使用接口和强制转换。

虽然您可以使用泛型解决一些问题,但生成的代码会很脆弱,向您显示以前从未见过的错误消息,修复错误通常会导致尝试和错误,即使您编译它,您也不会知道为什么 ;-)

[EDIT2]addChild()在下面添加了一个使用示例。

[编辑] 还和我在一起吗?如果你真的必须,使用这个 API:

interface ParentNode<Child> {
    List<Child> getChildren();
    void addChild(Child child);
}
interface ChildNode<Parent> {
    void setParent(Parent parent);
    Parent getParent();
}
// There is no way to avoid this because we would need to define
// "Node" recursively.
@SuppressWarnings( "rawtypes" )
class Node<
    Parent extends ParentNode<? extends Node>,
    Child extends ChildNode<? extends Node>
>
implements
    ParentNode<Child>,
    ChildNode<Parent>
{
    private Parent parent;
    public Parent getParent() { return parent; }
    public void setParent(Parent parent) { 
        this.parent = parent;
        // Here, we must case the child to a type that will accept Node
        @SuppressWarnings( "unchecked" )
        ParentNode<Node> cast = (ParentNode)parent;
        cast.addChild(this); // Note: Either add the child here ...
    }

    private List<Child> children;
    public List<Child> getChildren() { return children; }
    public void addChild( Child child ) { 
        children.add(child);
        // Here, we must case the child to a type that will accept Node
        @SuppressWarnings( "unchecked" )
        ChildNode<Node> cast = (ChildNode)child;
        cast.setParent(this); // ... or here but not twice :-)
    }
}

即将两个函数(向上和向下)拆分为两个接口,然后创建一个实现两者的节点类型。这允许您将叶子和根节点定义为特殊节点(没有两个 API 之一),或者您可以像定义任何其他节点一样定义它们并null以“不受支持”的方法返回。

用法:

public class DirFileNode extends Node<DirFileNode, DirFileNode> {
}
public class TreeUsage {
    public static void main( String[] args ) {
        DirFileNode node = new DirFileNode();
        DirFileNode node2 = new DirFileNode();
        node.addChild( node2 );
        // Why yes, I do love infinite loops. How can you tell?
        node2.addChild( node );
    }
}

您可以看到 API 确保所有内容都是类型安全的,但在内部,您必须强制转换。只要您不在节点中使用泛型类型,这很简单。如果你这样做了,声明就会变得一团糟。

于 2010-07-26T09:55:18.543 回答
1

我能得到的最接近的是遵循 Enum 的自我引用模式。它仍然需要未经检查的强制转换,但假设您的子类正确定义了 T,它应该是安全的。

public class GenericTree<T extends GenericTree<T, P, C>, P extends GenericTree<P, ?, T>, C extends GenericTree<C, T, ?>> {

    // Attributes
    private ArrayList<C> children = new ArrayList<C>();
    private P parent = null;

    // Methods
    public void setParent(P parent) {
        this.parent = parent;
    }

    public void addChild(C child) {
        @SuppressWarnings("unchecked")
        final T thisAsType = (T) this;
        child.setParent(thisAsType);
        this.children.add(child);
    }
}

编辑:实现示例

public static class SingleTypeTree<T> extends
    GenericTree<SingleTypeTree<T>, SingleTypeTree<T>, SingleTypeTree<T>> {

}
于 2010-07-26T14:27:30.327 回答