[英]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.