泛型方法返回类型 - 编译错误

时间:2017-06-28 00:48:26

标签: java generics

鉴于此代码示例:

class A {

}

public class TestGenerics {

    private static <T extends A> T failsToCompile() {
        return new A();
    }

    private static <T extends A> T compiles(T param) {
        return param;
    }

}

为什么第一种方法不能编译,但第二种方法呢?

错误是:

  

不兼容的类型。要求:T。发现:A

基本上我想要实现的是返回子类型的具体实例(例如,类B,扩展A)。工厂方法的排序。

编辑:好的,对于那些投票的人,我决定进一步详细说明。请参阅更新的示例,该示例不再使用String(是的,我知道String是最终的,不需要说明明显的)

编辑2 :问题的替代版本:

如何实现此方法以便编译(没有未经检查的强制转换和警告)?

abstract <T extends A> T failsToCompile();

编辑3 :这里的代码示例更贴近我试图解决的问题:

class Foo { }
class Bar extends Foo { }
class A<T extends Foo> { }
class B extends A<Bar> { }
public class TestGenerics {

    private static <T extends Foo> A<T> failsToCompile() {
        return new B();
    }

}

此处,方法返回类型为A<T>。考虑到T被定义为 Foo或其子类,并且B的定义如下:B extends A<Bar>,为什么我只能返回{{1} }}?我想这个问题归结为你不能指定new B(),我理解为什么。但这有一个优雅的解决方案吗?

3 个答案:

答案 0 :(得分:4)

忽略Stringfinal的事实(因此您不能拥有String的子类),您可以将通用类Class<T>传递给通用案例的方法。像,

private static <T extends String> T get(Class<T> cls) 
            throws InstantiationException, IllegalAccessException {
    return cls.newInstance();
}

答案 1 :(得分:2)

我试图更详细地阐述我的评论,以回答&#34;为什么&#34;。

您在问题中显示的是通用方法(而非通用类型),请参阅例如Oracles "The Java™ Tutorials", Generic Methods

推理

让我们考虑对工作方法的明确调用:TestGenerics.<A>compiles(new A());

这会实例化对类T的方法调用的泛型类型A。请记住,类型擦除在编译时会删除所有这些时髦的泛型类型,JVM不会看到泛型类型,并且编译器会处理泛型。编译器现在知道,您想要使用泛型类型T&#34; set&#34;上课A。从中可以推断出返回的类型也是类A(因为T根据方法代码返回,并且T被实例化为A)。这就是你使用泛型所获得的一切。

您可以从方法调用中移除<A>,使其看起来像普通&#34;普通&#34;方法调用,因为编译器可以推断出T必须是ATestGenerics.compiles(new A());

TestGenerics.<B>compiles(new B());class B extends A {}一起查看。如上所述,编译器知道此方法调用将返回类B的对象。

现在想象一下(不编译)代码TestGenerics.<B>compiles(new A());。由于传递给方法的对象不是B类型,编译器将抛出错误。否则该方法将返回类A的对象 - 但泛型类型断言该方法在此返回类型为B的对象。 这实际上相当于Andreas在评论中给出的例子(B b = failsToCompile())。

直到你实例化泛型类型 - 即使你设置了边界(如T extends A) - 它也没有类类型。因此,您无法返回具体类,因为这不符合泛型类型参数。

比萨示例

为了好玩,让我们试着为上述推理制作一个真实世界的例子:比萨饼。为了举例,我们假设每个披萨都是Margherita的一个子类型,即您将成分添加到Margherita以获得您最喜欢的其他披萨。

class PizzaMargherita {};

class PizzaProsciutto {
  PizzaProsciutto() {
    super(); 
    addIngredient(new Prosciutto());
  }
}

您的compiles()方法现在烘焙披萨:

public static <P extends Margherita> P bake(P pizza) {
  heat(pizza); 
  return pizza;
}

烘烤玛格丽特(Margherita)并从烤箱中取出(烘烤过的)玛格丽特(Margherita),烘烤意大利熏火腿并获得意大利熏火腿。

现在想一想:

public static <P extends Margherita> P doesntBake(P pizza) {
  return new PizzaMargherita();
}

一个烤箱总是返回玛格丽特,独立于你投入的东西?!?编译器不会批准。

- &GT;您需要使用混凝土比萨来烘焙它,或者您需要一种解决方法,例如类型标记:

public static <P extends Margherita> P bakePizza(Ingredients<P> ingredient) {
  P pizza = buildPizza(ingredients);
  return bake(pizza);
}

变通办法类型标记

您必须使用运行时类型令牌,如@Elliott Frisch在其答案中所示:

private static <T extends A> T failedToCompile(Class<T> c)
        throws InstantiationException, IllegalAccessException {
    return c.newInstance();
}

您无法实例化通用类型 - new T()无法正常工作,因为JVM对通用类型T一无所知。因此,您在编辑2中寻找的内容并不起作用。但是TestGenerics.<A>failedToCompile(A.class);有效,因为A.class是java字节代码的一部分。

变通办法类型令牌&amp;泛型类

根据您的具体要求,通用工厂类可能会对您有所帮助:

class Factory<T extends A> {
  private final Class<T> typeToken;
  public Factory(Class<T> typeToken) {
    this.typeToken = typeToken;
  }

  public T build()
        throws InstantiationException, IllegalAccessException {
    return this.typeToken.newInstance();
  }
}

你仍然需要某种形式的Map来获得正确的工厂来构建你需要的类,但你现在可以使用你需要创建类型为T的对象的任何可用的东西。

Map<String, Factory<?>> factories = new HashMap<>();
Map<DAO, Factory<?>> factories = new HashMap<>();
Map<ValueObject, Factory<?>> factories = new HashMap<>();
factories.get(valueObjectTypeA);

答案 2 :(得分:1)

其他人已经清楚地解释了failsToCompile方法无法编译的原因。安德烈亚斯甚至展示了一个例子......

至于如何规避这一点,我认为你应该考虑使用Supplier,这只是你需要的工厂:

private static <T extends A> T nowItCompilesFine(Supplier<? extends T> factory) {
    return factory.get();
}

你称之为:

A a = nowItCompilesFine(() -> new A());

或者使用对构造函数的引用:

A a = nowItCompilesFine(A::new);

A的任何后代相同:

class Child extends A { }

List<Supplier<? extends A>> factories = Arrays.asList(A::new, Child::new);

Supplier<? extends A> factoryA = factories.get(0);

A a1 = nowItCompilesFine(factoryA);

Supplier<? extends A> factoryChild = factories.get(1);

A a2 = nowItCompilesFine(factoryChild);

System.out.println(a2.getClass().getSimpleName()); // Child