2016-05-04 26 views
11

我玩弄泛型和发现,让我惊讶的是,下面的代码编译:为什么在推断数组类型时java类型不安全?

class A {} 
class B extends A {} 

class Generic<T> { 
    private T instance; 
    public Generic(T instance) { 
     this.instance = instance; 
    } 
    public T get(){ return instance; } 
} 

public class Main { 
    public static void main(String[] args) { 
     fArray(new B[1], new Generic<A>(new A())); // <-- No error here 
    } 

    public static <T> void fArray(T[] a, Generic<? extends T> b) { 
     a[0] = b.get(); 
    } 
} 

我希望T被推断为BA不延伸B。那为什么编译器不会抱怨呢?

T似乎被推断为Object,因为我也可以通过Generic<Object>

此外,当实际运行的代码,它将引发对a[0] = b.get();线的ArrayStoreException

我没有使用任何原始泛型类型。如果T实际上被推断为B,我觉得这个异常可以通过编译时错误或者至少一个警告来避免。


当与List<...>等效进一步的测试:

public static void main(String[] args) { 
    fList(new ArrayList<B>(), new Generic<A>(new A())); // <-- Error, as expected 
} 

public static <T> void fList(List<T> a, Generic<? extends T> b) { 
    a.add(b.get()); 
} 

这不会产生与误差:

The method fList(List<T>, Generic<? extends T>) in the type Main is not applicable for the arguments (ArrayList<B>, Generic<A>) 

一样更通用的情况下:

public static <T> void fList(List<? extends T> a, Generic<? extends T> b) { 
    a.add(b.get()); // <-- Error here 
} 

的编译器正确地认识到第一个?可能比第二个?更靠后。

例如如果第一个?B而第二个?A那么这是不安全的。


那么,为什么第一个例子没有产生类似的编译器错误呢?这只是一个疏忽吗?还是有技术限制?

我可能会产生一个错误的唯一方法是通过显式地提供类型:从2005年

Main.<B>fArray(new B[1], new Generic<A>(new A())); // <-- Not applicable 

我并没有真正找到过我自己的研究任何东西,除了this article(仿制药之前) ,其中谈到阵列协方差的危险。

数组协变似乎暗示了一个解释,但我想不出一个。


最新的JDK是1.8.0.0_91

+0

我不知道你是否会碰到Java的类型删除泛型?只是一个想法,我没有时间看看更接近的ATM。 – Ukko

回答

4

考虑这个例子:

class A {} 
class B extends A {} 

class Generic<T> { 
    private T instance; 
    public Generic(T instance) { 
     this.instance = instance; 
    } 
    public T get(){ return instance; } 
} 

public class Main { 
    public static void main(String[] args) { 
     fArray(new B[1], new Generic<A>(new A())); // <-- No error here 
    } 

    public static <T> void fArray(T[] a, Generic<? extends T> b) { 
     List<T> list = new ArrayList<>(); 
     list.add(a[0]); 
     list.add(b.get()); 
     System.out.println(list); 
    } 
} 

正如你所看到的,用来推断类型参数的签名是相同的,那是不同的唯一的事情就是fArray()只读取数组元素,而不是写他们,使的T -> A推理在运行时非常合理。

而且也没有办法让编译器告诉你的数组引用将在方法的实现的用途。

+0

@JornVernee是的,我超过它,但你得到的总体思路。当T - > A满足所有条件时,编译器没有理由选择'T - > B'。 – biziclop

+0

是的,这很不错。但重点在于你希望增加额外的类型安全性,因为''''''必须总是扩展''''''''''''(即数组类型)。就像你做'''A a = new A(); a1 = a;'''而不是'''a = new A(); a2 =(A)a;'''有时候第二个很好(当投射时),但要确保我们使用第一种方式,因为它具有类型安全性。 –

+0

@JornVernee够公平的。现在怎么样? :) – biziclop

2

I would expect T to be inferred to B. A does not extend B. So why doesn't the compiler complain about it?

T不是推断为B,它被推断为A。由于B延伸AB[]A[]一个亚型,因此该方法调用是正确的。

与泛型相反,数组的元素类型在运行时可用(它们是使用实例)。所以,当你尝试做

a[0] = b.get(); 

运行时环境知道a实际上是一个B阵列,并且不能持有A

这里的问题是Java的动态扩展。 Arrays自Java的第一个版本开始就存在,而泛型仅在Java 1.5中添加。一般来说,Oracle试图使新的Java版本向后兼容,因此,在较新的版本中(例如,数组协方差)所产生的错误不会在新版本中得到纠正。

+0

就像我说:_“'''T'''似乎被推断为'''Object''',因为我可以通过一个'''通用'''为好。” _但是你能告诉我_why_'''T'''被推断为'''Object'''?或者如果假设'''T'''被推测为'''B''',那么你能否告诉我一个失败案例? –

+0

'T'不是推断为'Object',因为这样你不就能够通过一个通用的''你的方法,因为通用''不是一个子类的通用''。当然,如果你实际上传递了“Generic ”而不是“Generic ”,那么“T”被推断为“Object”。 – Hoopje

相关问题