使用泛型的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 楼答案
问题与擦除无关。这是因为Java的类型系统不够强大,无法在没有帮助的情况下静态地推断
current
和current.getClass()
之间的关系在代码中:
current.getClass()
的结果是Class<? extends I>
类型的对象;也就是说,我们静态地不知道I
的某些亚型。我们程序员知道,无论它是什么类型,这也是current
的具体类型,因为我们已经阅读了getClass
的文档,但类型系统不知道这一点。所以,当我们得到一个IStrategy<? extends I>
时,我们所知道的是,它是一种作用于I
的一些子类型的策略,而不一定是I
本身。此外,通配符类型(带有?
的类型)被设计为丢失更多信息,因此类型系统甚至不知道我们的策略接受与getClass()
相同的类型因此,为了进行程序类型检查,我们需要(a)为类型系统提供一些非通配符名称,用于
I
的特定子类型,即current
,以及(b)说服它current
的值实际上具有该类型。好消息是,我们可以两者兼得为了给通配符类型命名,我们可以使用一种称为“通配符捕获”的技术,在这种技术中,我们创建了一个私有助手函数,它的唯一任务是为一个通配符类型指定一个特定的类型变量名。我们将把测试循环的主体拉到它自己的函数中,该函数严格地接受一个类型参数:
这个函数的作用是有效地引入类型参数
T
,它让Java知道我们打算在任何地方引用相同的未知类型T
。有了这些信息,它可以理解我们从工厂获得的策略在输入类的同一类型上工作,也就是当前current
在类型签名中的同一类型不幸的是,当我们调用此方法时,仍然会出现编译错误:
这里的问题是类型系统不知道
current
有自己的类型!Java的类型系统不理解current
和current.getClass()
之间的关系,所以它不知道无论current.getClass()
返回什么类型,我们都可以将current
视为该类型的值。幸运的是,我们可以通过一个简单的下调来解决这个问题,因为我们(程序员)知道current
有自己的类型。我们必须在我们的助手内这样做,因为在助手外,我们没有任何子类型的名称,我们想要断言current
具有。我们可以这样更改代码:现在我们可以将测试中的循环更改为:
一切正常