为什么Java允许将Object类型的表达式显式转换为A <b <c>&gt;类型A <! - ? - >到A <b <c>&gt;但不是类型A <b <?>&gt;到A <b <c>&gt;?</b <c> </b <?> </b <c> </b <c>

时间:2013-07-16 14:55:42

标签: java generics casting

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可以显式地将对象转换为A<B<C>>,从A<?>转换为A<B<C>>,但不能从A<B<?>>转换为A<B<C>>

为什么会这样?

4 个答案:

答案 0 :(得分:3)

看来,您的第三个示例违反了first JLS 5.5.1 rule

  

如果S是班级类型:

     

如果T是类类型,则为| S | &lt ;: | T |,或| T | &lt;:| S |。   否则,发生编译时错误。

     

此外,如果存在T的超类型X,并且存在超类型Y.   S,使得X和Y都可证明是不同的参数化类型   (§4.5),并且X和Y的擦除是相同的,编译时   发生错误。

的确,让SList<SomeType<?>>>TList<SomeType<X>>。这些ST可以证明是不同的参数化类型,因为<?>不等于<X>。同时他们的删除是相同的,只是:ListList

因此,根据规范,这会导致编译时错误。

当您首次投放m.get(...)Object时,您没有违反上述条件:ObjectList<SomeType<X>>没有相同的删除,此外,{{1} }

P.S。至于|List<SomeType<X>>| <: |Object|的情况,这也不违反上述规则,因为List<?>

答案 1 :(得分:2)

Java不会编译可证明无法成功的类型转换(假设事物是它们被声明为的类型,并假设该值不是null)。为了使类型转换成功,必须(理论上)可以使用非null类型作为两种类型的子类型。

  • ObjectA<B<C>>:这有可能成功。例如,类型A<B<C>>是两者的子类型。

  • A<?>A<B<C>>:这有可能成功。例如,类型A<B<C>>是两者的子类型。

  • A<B<?>>A<B<C>>:这不可能成功。即不存在属于两者的子类型的类型。

要查看最后一个的原因,请回想一下,对于参数化类型,Foo<A>如果Foo<B>A不同且B不同,则A<B<?>>不能是B<?>的子类型通配符。所以考虑?。其参数? extends something不是通配符(它是实际类型;它不是? super somethingA<B<?>>SubclassOfA<B<?>>)。

因此,唯一可以是A<B<C>>子类型的类型本身和A<B<C>>。同样的事情也适用于SubclassOfA<B<C>>:唯一可以是{{1}}子类型的类型本身和{{1}}。

那么你能看到不可能有一个两种类型的类型吗?

答案 2 :(得分:1)

因为它不是类型安全的。一个例子:

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允许从ObjectList<Class<Boolean>>以及从List<?>List<Class<Boolean>>进行投射,但两者都会产生未经检查的警告。它们不是错误,因为它们可以被认为是类型安全的,而List<Class<?>>List<Class<Boolean>>不能是类型安全的。

答案 3 :(得分:0)

public static <X> List<SomeType<X>> getList(Class<X> clazz)
{
    return (List<SomeType<X>>)m.get(clazz);//will not compile
}

不允许这样做,因为编译器对SomeType一无所知。

您已创建一个类型参数为X且参数为Class的方法,但结果您希望返回一个存储SomeType X类型的List。您没有指定如何从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 ;-)
}