通用方法返回类型-编译错误 [英] Generic method return type - compilation error

查看:52
本文介绍了通用方法返回类型-编译错误的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

给出以下代码示例:

  A类{}公共类TestGenerics {私有静态< T扩展A>T failedToCompile(){返回新的A();}私有静态< T扩展A>T comps(T param){返回参数}} 

第一种方法为什么不编译,而第二种方法可以编译?

错误是:

不兼容的类型.必填:T.找到:A

基本上,我想要实现的是返回一个子类型的具体实例(例如,类B,它扩展了A).某种工厂方法.

编辑:好的,对于那些投票否决的人,我决定进一步阐述一下.请参见更新后的示例,该示例不再使用String(是的,我知道String是最终的,无需声明明显的内容)

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

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

 抽象< T扩展A>T failedToCompile(); 

编辑3 :以下是与我要解决的问题更接近的代码示例:

  Foo类{}类Bar扩展了Foo {}A类< T扩展了Foo>{}B类扩展了A< Bar>{}公共类TestGenerics {私有静态< T扩展Foo>A< T>failToCompile(){返回新的B();}} 

此处,方法的返回类型为 A< T> .考虑到 T 被定义为 Foo或其子类,并且B的定义如下: B扩展了A< Bar> ,为什么我不能只返回 new B()吗?我认为问题归结为您无法分配 List< Animal>dList = new ArrayList< Dog>(); ,我明白为什么.但是,对此有一个优雅的解决方案吗?

解决方案

我正尝试将我的评论进一步阐述为为什么"的答案.

您要在问题中显示的是泛型方法(而不是泛型类型),请参见例如 Oracles"Java™教程",通用方法./p>

推理

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

这将实例化对类 A 的方法调用的通用类型 T .请记住,类型擦除会在编译时删除所有这些时髦的泛型类型,JVM不会看到泛型,并且编译器会处理泛型.编译器现在知道,您要调用通用类型为 T "set"的方法为类 A 的方法.由此,可以推断出返回的类型也是 A 类(因为根据方法code返回了 T ,而由于 T 实例化为 A ).这就是使用泛型所获得的全部.

您可以从方法调用中删除< A> ,并使它看起来像普通"方法调用,因为编译器可以推断出 T 必须是 A : TestGenerics.compiles(new A());

让我们看一下 TestGenerics.< B> compiles(new B()); class B一起扩展了A {} .如上所述,编译器知道此方法调用将返回类 B 的对象.

现在想象一下(未编译)代码 TestGenerics.< B> compiles(new A()); .编译器将引发错误,因为传递给该方法的对象不是 B 类型.否则,该方法将返回类 A 的对象-但泛型类型断言该方法在此返回类型为 B 的对象.实际上,这与示例相同( B b = failToCompile())Andreas在他的评论中给出.

在实例化泛型类型之前-即使您设置了界限(例如 T扩展A ),也没有类类型.因此,您将无法返回具体的类,因为这将无法满足泛型类型参数.

比萨饼示例

仅出于娱乐目的,让我们尝试为上述推理创建一个真实的例子:披萨.为了举例说明,我们假设每个比萨饼都是玛格丽特酒的一个子类型,即您向玛格丽特酒中添加食材以获得自己喜欢的其他比萨饼.

  PizzaMargherita类{};PizzaProsciutto类{PizzaProsciutto(){极好的();addIngredient(new Prosciutto());}} 

您的 compiles()方法现在可以烤披萨了:

 公共静态< P扩展Margherita>P烤(P披萨){热量(披萨);返回披萨;} 

烤玛格丽特(Margherita),然后从烤箱中取出(烤的)玛格丽特(Margherita),烤意大利熏火腿(Prosciutto),然后吃火腿(prosciutto).

现在考虑一下:

 公共静态< P扩展Margherita>P nottBake(P pizza){返回新的PizzaMargherita();} 

总是放回玛格丽塔的烤箱,与您放入的烤箱无关吗?!?编译器不同意.

->您需要混凝土披萨来烘烤它,或者需要一种变通方法,例如类型标记:

 公共静态< P扩展Margherita>P bakePizza(成分P成分){P pizza = buildPizza(成分);返回烤(披萨);} 

解决方法类型令牌

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

 私有静态< T扩展了A>T failedToCompile(Class T c)引发InstantiationException,IllegalAccessException {返回c.newInstance();} 

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

解决方法类型令牌和通用类

根据您的特定要求,通用工厂类可能会帮助您:

  class Factory< T扩展A>{私人最终Class< T>typeToken;public Factory(Class< T> typeToken){this.typeToken = typeToken;}公共T build()引发InstantiationException,IllegalAccessException {返回this.typeToken.newInstance();}} 

您仍然需要某种形式的Map来获取正确的工厂来构建所需的类,但是现在您可以在需要创建T类型的Object时使用任何可用的东西.

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

Given this code sample:

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;
    }

}

how come the first method doesn't compile, but the second one does?

The error is:

Incompatible types. Required: T. Found: A

Essentially what I'm trying to achieve is to return a concrete instance of a sub-type (for example of class B, that extends A). Sort of a factory method.

Edit: OK, for those who downvoted, I decided to elaborate a bit further. See the updated example, which no longer uses String (yes I'm aware that String is final, no need to state the obvious)

Edit 2: alternative version of the question:

How would you implement this method so that it compiles (without unchecked casts and warnings)?

abstract <T extends A> T failsToCompile();

Edit 3: Here's a code sample closer to the problem I'm trying to solve:

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();
    }

}

Here, the method return type is A<T>. Considering that T is defined as a Foo or its subclass, and the definition of B is as follows: B extends A<Bar>, why can't I just return new B()? I guess the problem comes down to the fact that you can't assign List<Animal> dList = new ArrayList<Dog>(); and I understand why. But is there an elegant solution to this?

解决方案

I'm trying to elaborate my comment a bit more into an answer to the "why".

What you're showing in your question are generic methods (and not generic types), see e.g. Oracles "The Java™ Tutorials", Generic Methods.

Reasoning

Let's consider the explicit call for the working method: TestGenerics.<A>compiles(new A());

This instantiates the generic type T of your method call to class A. Remember that type erasure removes all these funky generic types when compiling, that the JVM doesn't see generic types and that the compiler handles generics. The compiler knows now, that you want to call the method with the generic type T "set" to class A. From that, it can deduct that the returned type is also of class A (as T is returned according to the methods code and as T is instantiated to A). That's all you gain here from using generics.

You can remove the <A> from your method call and make it look like an "ordinary" method call, as the compiler can deduct that T has to be A: TestGenerics.compiles(new A());

Let's look at TestGenerics.<B>compiles(new B()); with class B extends A {}. As above, the compiler knows that this method call will return an object of class B.

Now imagine the (not compiling) code TestGenerics.<B>compiles(new A());. The compiler will throw an error as the object passed to the method is not of type B. Otherwise the method would return an object of class A - but the generic type asserts that the method returns here an object of typeB. That's actually equivalent to the example (B b = failsToCompile()) Andreas gave in his comment.

Until you instantiate a generic type - even if you set bounds (like T extends A) - it doesn't have a class type. You therefore can't return a concrete class, as this would not satisfy the generic type parameter.

Pizza Example

Just for fun, let's try to make a real world example for above reasoning: Pizza. For the sake of the example, let's assume that every Pizza is a subtype of the Margherita, i.e. you add ingredients to a Margherita to get your favorite other pizza.

class PizzaMargherita {};

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

Your compiles() method now bakes the pizza:

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

Bake a Margherita and get a (baked) Margherita out of your oven, bake a Prosciutto and get a Prosciutto.

Now think of this:

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

An oven always returning a Margherita, independent of what you put into it ?!? The compiler doesn't approve that.

-> You need the concrete pizza to bake it or you need a workaround, like the type token:

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

Workaround type token

You have to use a runtime-type token as @Elliott Frisch shows in his answer:

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

You can't instantiate a generic type - new T() doesn't work, as the JVM doesn't know anything about the generic type T. What you're looking for in edit 2 therefore doesn't work. But TestGenerics.<A>failedToCompile(A.class); works, as A.class is part of the java byte code.

Workaround type token & generic class

Depending on your specific requirements, a generic factory class might help you:

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();
  }
}

You will still need some form of Map to get the correct factory to build the class you need, but you can now use whatever is available at the point where you need to create the Object of type T.

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

这篇关于通用方法返回类型-编译错误的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆