【问题标题】:Java generics issue in the context of parallel class hierarchies并行类层次结构中的 Java 泛型问题
【发布时间】:2013-12-13 04:39:48
【问题描述】:

我在设计并行类层次结构时遇到了一些编译器错误。这个问题似乎源于 Java 的限制,其中(与 C++ 不同)没有公共的“typedef”来允许从泛型类外部访问类型参数。我想下面的示例代码最能说明我的问题。

public interface Bean { }
public class GreenBean implements Bean { }
public class RedBean implements Bean { }

// All BreanCounter classes implement this interface and interact with their
// corresponding concrete bean classes. 
public interface BeanCounter <B extends Bean> {
     public void addBean(B bean);
     public Collection<B> getBeans();
}

// DefaultBeanCounter implements logic shared by GreenBeanCounter and RedBeanCounter
public class DefaultBeanCounter <B extends Bean> implements BeanCounter<B> {

    private List<B> beans = new ArrayList<B>();

    @Override
    public void addBean(B bean) { beans.add(bean); }

    @Override
    public Collection<B> getBeans() { return beans; }
}

public class GreenBeanCounter extends DefaultBeanCounter<GreenBean> { }
public class RedBeanCounter extends DefaultBeanCounter<RedBean> { }

通过上述设置,编译器抱怨以下代码:

public class BeanCounterMaster <C extends BeanCounter<? extends Bean>> {
    public void mergeBeans(C des, C src) {
        for (Bean bean : src.getBeans()) {
            des.addBean(bean); //compiler error on this line
        }
    }
}

错误信息如下:

BeanCounter类型中的addBean(capture#2-of ? extends Bean)方法不适用于参数(Bean)

这可以理解但很尴尬,因为 src 和 des 显然属于同一类型。然而,我们不能移动它们的 bean,因为我们只能使用基本类型“Bean”。在 C++ 世界中,我们可以使用“C::BeanType”来指代具体的 bean 类型。为了解决这个问题,我决定将合并逻辑放入已知 bean 类型的 DefaultBeanCounter:

所以我添加了以下方法:

public interface BeanCounter <B extends Bean> {
    public void mergeBeans(BeanCounter<B> anotherCounter);
}

public class DefaultBeanCounter <B extends Bean> implements BeanCounter<B> {
    @Override
    public void mergeBeans(BeanCounter<B> anotherCounter) {
        for (B bean : anotherCounter.getBeans())
            addBean(bean);
    }
}

现在 BeanCounterMaster 看起来像:

public class BeanCounterMaster <C extends BeanCounter<? extends Bean>> {
    public void mergeBeans(C des, C src) {
        des.mergeBeans(src); // Still got compiler error
    }
}

新的编译器错误显示:

BeanCounter类型中的mergeBeans(BeanCounter)方法不适用于参数(C)

我在这里错过了什么?

【问题讨论】:

    标签: java generics


    【解决方案1】:

    仅使用C extends BeanCounter&lt;? extends Bean&gt; 时,您会丢失有关特定bean 类型的信息。因此,当迭代 src.getBeans() 时,您只知道您正在使用 Beans,而编译器在将它们添加到 des 时无法判断它们应该是“相同类型”。

    一种解决方法是给BeanCountMaster 两个类型参数:

    public class BeanCounterMaster <C extends BeanCounter<B>, B extends Bean> {
        public void mergeBeans(C des, C src) {
            for (B bean : src.getBeans()) {
                des.addBean(bean);
            }
        }
    }
    

    请注意,如果您愿意,mergeBeans 可能只是静态的generic method

    public static <B extends Bean> mergeBeans(
            BeanCounter<B> des,
            BeanCounter<? extends B> src
    ) {
        for (B bean : src.getBeans()) {
            des.addBean(bean);
        }
    }
    

    在这里,我删除了C,而只取BeanCounters 的B,以防您想考虑这一点。请注意,我还创建了 srcBeanCounter&lt;? extends B&gt;,这意味着您可以复制例如将BeanCounter&lt;FrozenGreenBean&gt; 转换为BeanCounter&lt;GreenBean&gt;(参见What is PECS (Producer Extends Consumer Super)?)。

    【讨论】:

    • 我假设静态方法解决方案仍然需要类采用两个类型参数,是吗?
    • @mr49 不,这(就像任何静态方法一样)可以在任何地方声明 - 这就是为什么我说它可以“只是”一个静态方法,因为它不需要实例化一个对象来调用它开。
    • 啊,我明白了。在实际代码中,我不能真正使这个逻辑静态化。在某些时候,我需要从实例化方法的非静态方法调用静态方法。我又遇到了类似的问题。
    • @mr49 这是可以理解的。我只是把它作为一个例子来说明什么是可能的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-14
    • 2013-03-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多