【问题标题】:Java: Generic class with generic methodJava:具有泛型方法的泛型类
【发布时间】:2017-04-29 09:12:22
【问题描述】:

我正在为我的 java 应用程序编写一个集成测试框架,但遇到了一个我无法解决的问题。请让我解释一下:

以下代码 sn-ps 是简化的类,以提高可见性:

我得到了以下抽象类:

public abstract class AbstractTestModel<T extends AbstractTestModel> {

  public T doSomething(){
    return getThis();
  }
  public T getThis(){
    return (T) this;
  }

  public <U extends AbstractTestModel<T>> U as(Class<U> type){
    AbstractTestModel<T> obj = this;
    if (obj.getClass().isAssignableFrom(type)){
      return type.cast(obj);
    }else{
      throw new AssertionError("This (" + obj.getClass().getName() +")could not be resolved to the expected class " + type.getName());
    }
  }

}

并且可能有像这样的具体类:

public class ConcreteTestModel1 extends AbstractTestModel<ConcreteTestModel1> {

  public void doSomethingElse(){

  }
}

我还写了一个工厂类。该工厂从服务器下载 JSON 对象并实例化其中一个具体类 - 取决于 JSON 响应。这个具体的类有许多用于测试 JSON 响应的辅助方法。问题是,工厂方法总是返回一个“AbstractTestModel”。

集成测试如下所示(简化):

public class SomeTest {

  TestModelFactory testModelFactory;
  @Before
  public void init(){
    testModelFactory = new TestModelFactory();
  }
  @Test
  public void someTest(){
    AbstractTestModel anyModel = testModelFactory.createModel("someIdOfTheServer");

    //ERROR: this does not work! Cannot resolve method doSomethingElse():
    anyModel.doSomething().as(ConcreteTestModel1.class).doSomethingElse();

    //as() method returns AbstractTestModel instead of ConcreteTestModel1
    //this works:
    AbstractTestModel as = anyModel.as(ConcreteTestModel1.class);
    //this does not work:
    ConcreteTestModel1 asConcreteTestModel1 = anyModel.as(ConcreteTestModel1.class);
  }
}

方法 as(Class type) 应该检查给定的类是否有效,将“this”转换为所需的类并返回它,但它总是返回 AbstractTestModel。

如果我将“as”方法设为静态,或者如果我像这样摆脱泛型类...

public abstract class AbstractTestModel {

  /*
  Not possible to return the superclass anymore
  public T doSomething(){
    return getThis();
  }
  public T getThis(){
    return (T) this;
  }
  */

  public <U extends AbstractTestModel> U as(Class<U> type){
    AbstractTestModel obj = this;
    if (obj.getClass().isAssignableFrom(type)){
      return type.cast(obj);
    }else{
      throw new AssertionError("This (" + obj.getClass().getName() +")could not be resolved to the expected class " + type.getName());
    }
  }

}

...然后它工作正常,但我当然不能再以所有其他方法返回具体类。

感谢您阅读这篇长文。 你知道我在这里做错了什么吗?有解决办法吗?

感谢您的任何提示,祝您有美好的一天!

曼努埃尔

【问题讨论】:

标签: java unit-testing generics casting integration-testing


【解决方案1】:

问题是编译器需要在编译时推断类型。这段代码大部分是提供的,但为了演示,我在doSomethingElse 中添加了一些输出,并添加了ConcreteTestModelX 扩展ConcreteTestModel1注意我删除了从as 方法中输入T,以探索它在进一步探索性测试中与泛型类型交互的方式。

public abstract class AbstractTestModel<T extends AbstractTestModel> {

    public T doSomething() {
        return getThis();
    }

    public T getThis() {
        return (T) this;
    }

    public <U extends AbstractTestModel> U as(Class<U> type) {
        if (getClass().isAssignableFrom(type)) {
            return type.cast(this);
        } else {
            throw new AssertionError("This (" + getClass().getName()
                + ") could not be resolved to the expected class " + type.getName());
        }
    }

}

class ConcreteTestModel1 extends AbstractTestModel<ConcreteTestModel1> {

    public void doSomethingElse() {
        System.out.println("This is \"" + getClass().getSimpleName() + "\" doing something else");
    }
}

class ConcreteTestModelX extends ConcreteTestModel1 {

}

通过这个测试

import org.junit.Test;

public class SomeTest {

    @Test
    public void someTest(){
        AbstractTestModel<ConcreteTestModel1> anyModel = new ConcreteTestModel1();
        ConcreteTestModel1 asConcreteTestModel1 = anyModel.as(ConcreteTestModel1.class);
        asConcreteTestModel1.doSomethingElse();

        AbstractTestModel anyModelX = new ConcreteTestModelX();
        ConcreteTestModel1 asConcreteTestModelX = (ConcreteTestModel1)anyModelX;
        asConcreteTestModelX.doSomethingElse();
    }
}

似乎您在测试中遇到的问题是您用于模型的变量没有泛型,编译器然后剥离泛型请参阅此答案https://stackoverflow.com/a/18277337/7421645

基于此,我创建了一些新的测试来探索:

import org.junit.Test;

public class SomeTest {

    @Test
    public void concreteTest(){
        ConcreteTestModel1 asConcreteTestModel1 = getConcreteModel(new ConcreteTestModel1(), ConcreteTestModel1.class);
        asConcreteTestModel1.doSomethingElse();
    }

    @Test
    public void concreteExtendsTest(){
        ConcreteTestModel1 asConcreteTestModelX = getConcreteModel(new ConcreteTestModelX(), ConcreteTestModelX.class);
        asConcreteTestModelX.doSomethingElse();
    }

    private <T extends ConcreteTestModel1> T getConcreteModel(T anyModel, Class<T> classType) {
        return anyModel.as(classType);
    }

    @Test
    public void vanillaCastingTest(){
        AbstractTestModel anyModelX = new ConcreteTestModelX();
        ConcreteTestModel1 asConcreteTestModelX = (ConcreteTestModel1)anyModelX;
        asConcreteTestModelX.doSomethingElse();
    }

    @Test
    public void abstractGenericTest(){
        AbstractTestModel<ConcreteTestModel1> anyModel = new ConcreteTestModel1();
        ConcreteTestModel1 asConcreteTestModel1 = anyModel.as(ConcreteTestModel1.class);
        asConcreteTestModel1.doSomethingElse();
    }

    @Test
    public void failedGenericTest(){
        AbstractTestModel anyModel = new ConcreteTestModel1();
        ConcreteTestModel1 asConcreteTestModel1 = getAs(anyModel);
        asConcreteTestModel1.doSomethingElse();
    }

    private ConcreteTestModel1 getAs(AbstractTestModel<ConcreteTestModel1> anyModel) {
        return anyModel.as(ConcreteTestModel1.class);
    }
}

【讨论】:

    【解决方案2】:

    感谢所有答案,我终于找到了问题所在。其实很简单:

    工厂需要返回“AbstractTestModel”而不仅仅是“AbstractTestModel”。我已将 添加到每个返回 AbstractTestModel 的方法中。现在它工作得很好:) 谢谢大家。没有你我找不到答案。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-08-28
      • 1970-01-01
      • 1970-01-01
      • 2022-01-03
      • 2014-05-08
      • 1970-01-01
      相关资源
      最近更新 更多