[英]Why does Java allow the explicit conversion of expressions of type Object to A<B<C>>, of type A<?> to A<B<C>> but not of type A<B<?>> to A<B<C>>?
Java将允许我这样做:
public static class SomeType<I>{}
private static Map<Class<?>, Object> m = new HashMap<Class<?>, Object>();
public static <X> List<SomeType<X>> getList(Class<X> clazz)
{
return (List<SomeType<X>>)m.get(clazz);//warning
}
它还可以让我做到这一点:
public static class SomeType<I>{}
private static Map<Class<?>, List<?>> m = new HashMap<Class<?>, List<?>>();
public static <X> List<SomeType<X>> getList(Class<X> clazz)
{
return (List<SomeType<X>>)m.get(clazz);//warning
}
但这不会让我这样做:
public static class SomeType<I>{}
private static Map<Class<?>, List<SomeType<?>>> m = new HashMap<Class<?>, List<SomeType<?>>>();
public static <X> List<SomeType<X>> getList(Class<X> clazz)
{
return (List<SomeType<X>>)m.get(clazz);//will not compile
}
除非我采用以下解决方法:
public static class SomeType<I>{}
private static Map<Class<?>, List<SomeType<?>>> m = new HashMap<Class<?>, List<SomeType<?>>>();
public static <X> List<SomeType<X>> getList(Class<X> clazz)
{
return (List<SomeType<X>>)(Object)m.get(clazz);//warning
}
因此,java使得可以将从显式转换为Object到A<B<C>>
,从A<?>
为A<B<C>>
但不能从A<B<?>>
为A<B<C>>
。
这是为什么?
看来,您的第三个示例违反了第一个JLS 5.5.1规则 :
如果S是类类型:
如果T是类类型,那么| S | <:| T |或| T | <:| S |。 否则,将发生编译时错误。
此外,如果存在T的超类型X和S的超类型Y,以致X和Y都是可证明是截然不同的参数化类型(第4.5节),并且X和Y的擦除相同,则编译时发生错误。
实际上,令S
为List<SomeType<?>>>
而T
为List<SomeType<X>>
。 这些S
和T
是可证明是截然不同的参数化类型,因为<?>
不等于<X>
。 同时,它们的擦除相同,即: List
和List
。
因此,根据规格,这会导致编译时错误。
当您第一次将m.get(...)
为Object
,您不会违反提到的条件: Object
和List<SomeType<X>>
没有相同的擦除,此外, |List<SomeType<X>>| <: |Object|
|List<SomeType<X>>| <: |Object|
PS对于List<?>
情况,这也没有违反提到的规则,因为|List<SomeType<X>>| <: |List<?>|
|List<SomeType<X>>| <: |List<?>|
。
Java不会编译可证明无法成功的类型转换 (假设事物是它们被声明为的类型,并假定该值不为null
)。 为了使类型转换成功,从理论上讲,必须有一个非空类型,它是这两种类型的子类型。
Object
到A<B<C>>
:这是可能的这一成功。 例如,类型A<B<C>>
是两者的子类型。
A<?>
到A<B<C>>
:这有可能成功。 例如,类型A<B<C>>
是两者的子类型。
A<B<?>>
到A<B<C>>
:不可能成功。 即,不能存在作为两者的子类型的类型。
要了解为什么要使用最后一个,请回想一下对于参数化类型,如果A
和B
不同,并且Foo<A>
都不是Foo<B>
的子类型,并且都不是通配符。 因此,考虑A<B<?>>
。 它的参数B<?>
不是通配符(它是实际类型;它不是?
, ? extends something
或? super something
)。
因此,唯一可以作为A<B<?>>
子类型的类型是本身,以及SubclassOfA<?B<?>>
。 同样的情况也适用于A<B<C>>
:唯一可以成为A<B<C>>
的子类型的类型本身就是SubclassOfA<?B<C>>
。
那么,您能否看到不可能同时拥有两者的子类型呢?
因为它不是类型安全的。 一个例子:
List<Class<?>> classes = new ArrayList<Class<?>>();
classes.add(String.class);
// This is invalid!
List<Class<Boolean>> casted = (List<Class<Boolean>>) classes;
// We've somehow assigned a Class<String> to a Class<Boolean>!
Class<Boolean> invalid = casted.get(0);
此外,尽管Java允许从Object
到List<Class<Boolean>>
的强制转换以及从List<?>
到List<Class<Boolean>>
的强制转换,但两者都会产生未经检查的警告。 它们不是错误,因为可以想象它们是类型安全的,而List<Class<?>>
到List<Class<Boolean>>
不能是类型安全的。
这个
public static <X> List<SomeType<X>> getList(Class<X> clazz)
{
return (List<SomeType<X>>)m.get(clazz);//will not compile
}
不允许这样做是因为,编译器对SomeType一无所知。
您已经创建了带有类型参数X
和参数Class的方法,但是结果是您期望返回一个存储X
类型的SomeType
的列表。 您没有指定如何从X传递到SomeType。 获取期望Object
的映射,但返回声明的类型的女巫是SomeType<?>
。
作为?
在这种情况下,它与X
更不一样,您可以执行以下操作。
为了解决这个问题,如果要在返回类型中使用它,则必须说X必须是。
public static <X extends SomeType<?>> List<? super X> getList(Class<X> clazz)
{
return m.get(clazz); //No error, no warring ;-)
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.