有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

使用泛型的Java工厂。阶级vs。getClass()

我现在搜索和尝试了一天多,找不到解决Java中常见问题的方法。原因很明显——类型擦除。但我的问题是:在Java中真的没有解决这个问题的好方法吗?我愿意调查更多的时间,因为这种问题每一次都会出现

我得到的错误是:

类型策略中的方法剂量策略(捕获#2-of?扩展I)<;捕获#2-of?扩展I>;不适用于(I)

因此,我将问题简化为以下示例

想象一下这个模型:

package model;

public interface I {

//there are actual 30 classes implementing I...
}

public class A implements I {

    public void someSpecificMagicForA(){
        System.out.println("A");
    }
}

public class B implements I {

    public void someSpecificMagicForB() {
        System.out.println("B");
    }

}

以及选择逻辑

package strategy;

import model.A;

public interface IStrategy<T> {

    public void doStrategy(T t);
}

public class AStrategy implements IStrategy<A> {

    @Override
    public void doStrategy(A a) {
        a.someSpecificMagicForA();
    }
}

public class BStrategy implements IStrategy<B> {

    @Override
    public void doStrategy(B b) {
        b.someSpecificMagicForB();
    }
}

还有一个通用策略工厂

package strategy;

import java.util.HashMap;
import java.util.Map;

import model.A;
import model.B;

public class StrategyFactory {

    static {
        strategies.put(A.class, AStrategy.class);
        strategies.put(B.class, BStrategy.class);
    }
    private static final Map<Class<?>, Class<? extends IStrategy<?>>> strategies = new HashMap<>();

    @SuppressWarnings("unchecked") // I am fine with that suppress warning
    public <T> IStrategy<T> createStategy(Class<T> clazz){
        Class<? extends IStrategy<?>> strategyClass = strategies.get(clazz);

        assert(strategyClass != null);

        try {
            return (IStrategy<T>) strategyClass.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
            return null;
        }
    }
}

这是测试

import java.util.ArrayList;
import java.util.List;

import junit.framework.TestCase;
import model.A;
import model.B;
import model.I;
import strategy.IStrategy;
import strategy.StrategyFactory;


public class TestCases extends TestCase {

    public void testWithConcreteType(){

        B b = new B();

        StrategyFactory factory = new StrategyFactory();
        IStrategy<B> createStategy = factory.createStategy(B.class);
        createStategy.doStrategy(b); //awesome
    }

    public void testWithGenericType(){

        List<I> instances = createTestData(); // image this is the business data

        StrategyFactory factory = new StrategyFactory();

        for (I current : instances){
            IStrategy<? extends I> createStategy = factory.createStategy(current.getClass());
            createStategy.doStrategy(current); //meh
            //The method doStrategy(capture#2-of ? extends I) in the type IStrategy<capture#2-of ? extends I> 
            //is not applicable for the arguments (I)
        }
    }

    private List<I> createTestData(){
        A a = new A();
        B b = new B();

        List<I> instances = new ArrayList<>();
        instances.add(a);
        instances.add(b);

        return instances;
    }
}

我尝试了另一种使用番石榴类型标记(https://github.com/google/guava/wiki/ReflectionExplained)的方法。但由于我真的没有<;T>;因为我得到的只是实现该接口的实例集合

虽然使用访问者模式,但我有一个有效且不太糟糕的解决方案。因为我有真正的课程

...visitor class
public void visit(A a){
   doVisit(A.class, a); //private generic method now works of course
}

编译时一切都恢复正常。但是在这个特殊的例子中,我花了相当长的时间来实现I的30多个子类的访问者。所以我真的希望将来有一个更好的解决方案

如有任何意见,我们将不胜感激


共 (1) 个答案

  1. # 1 楼答案

    问题与擦除无关。这是因为Java的类型系统不够强大,无法在没有帮助的情况下静态地推断currentcurrent.getClass()之间的关系

    在代码中:

    for (I current : instances){
      IStrategy<? extends I> createStategy = factory.createStategy(current.getClass());
      createStategy.doStrategy(current);
    }
    

    current.getClass()的结果是Class<? extends I>类型的对象;也就是说,我们静态地不知道I的某些亚型。我们程序员知道,无论它是什么类型,这也是current的具体类型,因为我们已经阅读了getClass的文档,但类型系统不知道这一点。所以,当我们得到一个IStrategy<? extends I>时,我们所知道的是,它是一种作用于I一些子类型的策略,而不一定是I本身。此外,通配符类型(带有?的类型)被设计为丢失更多信息,因此类型系统甚至不知道我们的策略接受与getClass()相同的类型

    因此,为了进行程序类型检查,我们需要(a)为类型系统提供一些非通配符名称,用于I的特定子类型,即current,以及(b)说服它current的值实际上具有该类型。好消息是,我们可以两者兼得

    为了给通配符类型命名,我们可以使用一种称为“通配符捕获”的技术,在这种技术中,我们创建了一个私有助手函数,它的唯一任务是为一个通配符类型指定一个特定的类型变量名。我们将把测试循环的主体拉到它自己的函数中,该函数严格地接受一个类型参数:

    private <T> void genericTestHelperDraft1(Class<T> currentClass, T current) {
      StrategyFactory factory = new StrategyFactory();
      IStrategy<T> createStrategy = factory.createStategy(t);
      createStrategy.doStrategy(current); // works
    }
    

    这个函数的作用是有效地引入类型参数T,它让Java知道我们打算在任何地方引用相同的未知类型T。有了这些信息,它可以理解我们从工厂获得的策略在输入类的同一类型上工作,也就是当前current在类型签名中的同一类型

    不幸的是,当我们调用此方法时,仍然会出现编译错误:

    for (I current : instances){
      genericTestHelperDraft1(current.getClass(), current);
      // Type error because current is not of type "capture of ? extends I"
    }
    

    这里的问题是类型系统不知道current有自己的类型!Java的类型系统不理解currentcurrent.getClass()之间的关系,所以它不知道无论current.getClass()返回什么类型,我们都可以将current视为该类型的值。幸运的是,我们可以通过一个简单的下调来解决这个问题,因为我们(程序员)知道current有自己的类型。我们必须在我们的助手内这样做,因为在助手外,我们没有任何子类型的名称,我们想要断言current具有。我们可以这样更改代码:

    private <T> void genericTestHelperDraft2(Class<T> t, Object current) {
      T currentDowncast = t.cast(current);
      StrategyFactory factory = new StrategyFactory();
      IStrategy<T> createStrategy = factory.createStategy(t);
      createStrategy.doStrategy(currentDowncast);
    }
    

    现在我们可以将测试中的循环更改为:

    for (I current : instances){
      genericTestHelperDraft2(current.getClass(), current);
    }
    

    一切正常