使用泛型的 Java 工厂,.class 与 .getClass()

标签 java generics reflection factory

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

我得到的错误是:

方法 doStrategy(capture#2-of ? extends I) 类型为 IStrategy 不适用于参数 (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;
    }
}

我尝试了另一种使用 guava TypeTokens ( https://github.com/google/guava/wiki/ReflectionExplained ) 的方法。但是我没有设法让它工作,因为我真的没有 因为我得到的只是实现该接口(interface)的实例集合。

虽然使用访客模式,但我有一个有效且不错的解决方案。因为我有真正的类(class)

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

编译时一切正常。但在这个特殊情况下,我花了相当长的时间为 I 的 30 多个子类实现该访问者。所以我真的很想为将来有一个更好的解决方案。

非常感谢任何意见。

最佳答案

问题与删除无关。 Java 的类型系统不够强大,无法静态推理 current 之间的关系。和 current.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)辅助函数,该函数的唯一工作是将特定类型变量名称赋予否则为通配符的类型.我们将把测试循环的主体拉出到它自己的函数中,该函数严格采用类型参数:

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 的类型系统不理解 current 之间的关系和 current.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);
}

一切正常。

关于使用泛型的 Java 工厂,.class 与 .getClass(),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34919346/

相关文章:

java - java中的深度优先搜索 - 无法将节点转换为int

java - 如何在 CSV 文件或 Excel 文件中获取该目录内的文件路径、名称、创建日期、上次修改日期和文件扩展名?

java - 使用泛型匹配参数和返回类型

Java 枚举泛型

c# - 实例化实现特定接口(interface)的所有类

java - JVM 安全管理器文件权限 - 自定义策略

java - 摆脱 Collections.unmodifyingList() 产生的警告

C# 向 .net 枚举添加额外值

Java通过反射查找setter和getter

java - 使用 AND 运算符 CriteriaBuilder 的规范和谓词集合