Java泛型地狱 [英] Java Generics Hell

查看:113
本文介绍了Java泛型地狱的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我怀疑这个问题在此之前已经提过(并回答过),但我不知道如何命名该问题。为什么只有当我没有通过课程本身时才能表达通配符而没有问题?

这一切归结为这个代码。除了调用 genericsHell(ShapeSaver.class)

  interface Shape {} 

interface Circle扩展Shape {}
$ b $ interface ShapeProcessor< T extends Shape> {}

类CircleDrawer实现了ShapeProcessor< Circle> {}

类ShapeSaver< T扩展形状>实现ShapeProcessor< T> {}

class Test {
void genericsHeaven(ShapeProcessor< ;? extends Shape> a){}

void genericsHell(Class< ;? extends ShapeProcessor< ;? extends Shape>> ;> a){}

void test(){
genericsHeaven(new CircleDrawer());
genericsHeaven(新的ShapeSaver< Circle>());
genericsHell(CircleDrawer.class);
genericsHell(ShapeSaver.class); //错误:方法genericsHell不适用于参数(Class< ShapeSaver>)
}
}


解决方案 。编译器需要检查 Class< ShapeSaver> 是否是类< ;?扩展了ShapeProcessor<?> ,这被缩减为 ShapeSaver ShapeProcessor的一个子类型<> code>。子类型关系不成立,方法调用失败。



@波希米亚的解决方案同样适用。这里的子类型检查发生在推断出 T 之后的 T 的绑定检查。它也应该失败。这似乎是一个编译器错误,它以某种方式错误地解释了 Raw 可分配给 Raw的规则 Raw 原始的子类型。另请参阅 Enum.valueOf throws对未知类扩展Enum的警告?



解决您的问题的一个简单方法是声明

  void genericsHell(Class< ;? extends ShapeProcessor> a)

确实, ShapeSaver ShapeProcessor 的子类型,并且调用编译。



这不仅仅是一种解决方法。这有一个很好的理由。严格地说,对于任何 Class X 必须是原始类型。例如, Class< List> 是可以的, Class< List< String>> 不是。因为实际上没有类表示 List< string> ;只有一个类代表 List



忽略不应使用原始类型的严重警告。鉴于Java类型系统的设计方式,我们有时必须使用原始类型。即使Java的核心API( Object.getClass())也使用原始类型。




您可能打算做这样的事情。

  genericsHell(ShapeSaver&Circle; .class); 

不幸的是,这是不允许的。 Java可能有但并未引入类型字面量和泛型。这给很多图书馆带来了很多问题。 java.lang.reflect.Type 乱七八糟,无法使用。每个图书馆都必须介绍他们自己的类型系统表示法来解决问题。

您可以借一个例如来自Guice,你就可以

  genericsHell(new TypeLiteral< ShapeSaver&Circle;>>(){}) 
------------------

(阅读代码时,学习跳过 ShaveSaver< Circle> 周围的胡扯)



genericsHell(),您将拥有完整的类型信息,而不仅仅是类。


I suspect this has been asked here (and answered) before, but I don't know how to name the problem. Why can I express the wildcards without problem only when I'm not passing the class itself?

It all boils down to this code. Everything works as expected except for the call to genericsHell(ShapeSaver.class):

interface Shape { }

interface Circle extends Shape { }

interface ShapeProcessor<T extends Shape> { }

class CircleDrawer implements ShapeProcessor<Circle> { } 

class ShapeSaver<T extends Shape> implements ShapeProcessor<T> { }

class Test {
    void genericsHeaven(ShapeProcessor<? extends Shape> a) {}

    void genericsHell(Class<? extends ShapeProcessor<? extends Shape>> a) {}

    void test() {
        genericsHeaven(new CircleDrawer());
        genericsHeaven(new ShapeSaver<Circle>());
        genericsHell(CircleDrawer.class);
        genericsHell(ShapeSaver.class); // ERROR: The method genericsHell is not applicable for the arguments (Class<ShapeSaver>)
    }
}

解决方案

The type of ShapeSaver.class is Class<ShapeSaver>. When feed it to genericsHell(), compiler needs to check if Class<ShapeSaver> is a subtype of Class<? extends ShapeProcessor<?>, which is reduces to whether ShapeSaver is a subtype of ShapeProcessor<?>. The subtype relation does not hold, the method call fails.

The same thing should be true for @Bohemian's solution. Here the subtype checking occurs at bound checking of T after T is inferred. It should fail too. This appears to be a compiler bug, which somehow misinterprets the rule that Raw is assignable to Raw<X> as if Raw is a subtype of Raw<X>. see also Enum.valueOf throws a warning for unknown type of class that extends Enum?

A simple solution to your problem is to declare

void genericsHell(Class<? extends ShapeProcessor> a)

indeed, ShapeSaver is a subtype of ShapeProcessor, and the call compiles.

That's not just a workaround. There's a good reason for it. Strictly speaking, for any Class<X>, X must be a raw type. For example, Class<List> is ok, Class<List<String>> is not. Because there is really no class that represents List<string>; there is only a class representing List.

Ignore the stern warning that you shall not use raw type. We must use raw types sometimes, given how Java type system is designed. Even Java's core APIs (Object.getClass()) use raw types.


You probably intended to do something like this

genericsHell(ShapeSaver<Circle>.class);

Unfortunately, that's not allowed. Java could have, but did not, introduce type literal along with generics. That created lots of problems for lots of libraries. java.lang.reflect.Type is a mess and unusable. Every library has to introduce their own representation of type system to solve the problem.

You can borrow one, e.g. from Guice, and you'll be able to

genericsHell( new TypeLiteral< ShapeSaver<Circle> >(){} )
                               ------------------  

(learn to skip the craps around ShaveSaver<Circle> when reading the code)

In the method body of genericsHell(), you'll have full type information, not just the class.

这篇关于Java泛型地狱的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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