【问题标题】:Implementing parent-child relationship with generics使用泛型实现父子关系
【发布时间】:2018-12-28 22:26:29
【问题描述】:

我正在尝试制作一个容器(父)对象,您可以在其中指定它可以包含的对象(子)的类型。

同样,您可以指定孩子的父母类型。 父母和孩子以后需要交流,所以我需要他们都有相互的对象引用。

此代码是我的应用中实际代码的更简单表示。

class Parent<T extends Child> {
    ArrayList<T> childObjects;
    public void addChildChild(T newChild) {
        childObjects.add(newChild);
        newChild.setParent(this);
    }
    public void newChildConnected(T connectedChild) {
        System.out.println("Child connected");
    }
}

class Child <T extends Parent> {
    T parentObject;
    public void setParent(T newParent) {
        parentObject = newParent;
        parentObject.newChildConnected(this);
    }
}

我的 IDE 说: 未经检查地调用“newChildConnected(T)”作为原始类型“test.Parent”的成员

我一直在尝试不同的方法来更好地使用通配符和其他东西,但这是我能做的最好的事情。 那么实现这种行为的正确方法是什么?

我的目标是能够为父级指定子类型,为子级指定父级类型,并以子级和父级都能够在不使用 intanceof() 运算符和强制转换的情况下使用彼此的功能的方式执行此操作。 (这就是我使用泛型的原因)

在 Java 中甚至可能吗?

【问题讨论】:

  • 我不认为这是可能的,不。请参阅 Joshua Bloch 的 Effective Java 中的“Typesafe Heterogenous Containers”以获得更好的方法。
  • 我的第一种方法忽略原始类型警告是否有任何危险?
  • 当然,这不是类型安全的。您可以/应该阻止原始类型,例如通过编写T extends Child&lt;?&gt;T extends Parent&lt;?&gt;&gt;,只是为了将警告变成错误,表明此时的类型根本不兼容。可能有一些解决方法,但鉴于虚拟代码,很难说这里最合适的是什么......
  • @JerryLundegaard 它允许new ChildA&lt;ParentB&gt;().setParent(new ParentB())class ParentB extends Parent&lt;ChildB&gt; 之类的东西。因此,您最终会得到一个只期望 ChildB 拥有 ChildA 的父母。

标签: java generics


【解决方案1】:

您对泛型类型的使用会创建循环类型引用。如果父子的类型(或接口/基类)相同,则在单个类中使用树结构:

class MyObject<T> {
T parentObject;
ArrayList<T> childObjects = new ArrayList();
public void addChildChild(T newChild) {
    childObjects.add(newChild);
    newChild.setParent(this);
}
public void newChildConnected(T connectedChild) {
    System.out.println("Child connected");
}
public void setParent(T newParent) {
    parentObject = newParent;
    parentObject.newChildConnected(this);
}

使用此类时,您必须检查 parentObject == null 所在的顶级对象和 childObjects.size()==0 所在的叶对象。

如果没有公共接口或基类,这是不可能安全的。

【讨论】:

    【解决方案2】:

    正如@Strom 正确指出的那样,如果没有基类或接口,这不能以类型安全的方式完成。

    如果您可以扩展类/接口,那么没有任何强制转换的类型安全解决方案将如下所示:

    interface BaseParent<P extends BaseParent<P, C>, C extends BaseChild<P, C>> {
    
        List<C> getChildren();
    
        void setChildren(List<C> children);
    
        P self();
    
        default void addChild(C child) {
            if (child.getParent() == null) {
                child.setParent(self());
            }
    
            final ArrayList<C> newChildren = new ArrayList<>(getChildren());
            newChildren.add(child);
            setChildren(newChildren);
        }
    }
    
    interface BaseChild<P extends BaseParent<P, C>, C extends BaseChild<P, C>> {
    
        void setParent(P parent);
    
        P getParent();
    
    }
    
    final class Parent implements BaseParent<Parent, Child> {
    
        private List<Child> children = new ArrayList<>();
    
        @Override
        public List<Child> getChildren() {
            return children;
        }
    
        @Override
        public void setChildren(List<Child> children) {
            this.children = children;
        }
    
        @Override
        public Parent self() {
            return this;
        }
    }
    
    final class Child implements BaseChild<Parent, Child> {
    
        private Parent parent;
    
        public Child(Parent parent) {
            this.parent = parent;
            this.parent.addChild(this);
        }
    
        @Override
        public void setParent(Parent parent) {
            this.parent = parent;
        }
    
        @Override
        public Parent getParent() {
            return parent;
        }
    }
    

    该解决方案使用"recursive" generics 来保证类型安全,并使用self-type reference 来避免强制转换。这两种方法都有一些注意事项并且并不完全安全,因为您必须依赖基/类接口的实现者来返回正确的自身类型并定义正确的类型参数,但对于内部 API 来说应该足够了。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-12-20
      • 1970-01-01
      • 2011-11-17
      • 1970-01-01
      • 2011-01-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多