在推断数组类型时,为什么java类型不安全?

Jor*_*nee 12 java arrays generics type-inference type-safety

我正在玩泛型,并发现,令我惊讶的是,以下代码编译:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

而且,当实际运行代码时,它会抛出一个ArrayStoreExceptiona[0] = b.get();行了.

我没有使用任何原始泛型类型.我觉得这个异常可以通过编译时错误来避免,或者至少是一个警告,如果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());
}
Run Code Online (Sandbox Code Playgroud)

这确实产生了错误:

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

和更通用的情况一样:

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

编译器正确地识别出第一个?可能比继承层次结构更低?.

例如,如果第一个?B和第二个?A那么这不是类型安全的.


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

我可以产生错误的唯一方法是明确提供一个类型:

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

我没有通过自己的研究找到任何东西,除了2005年的这篇文章(仿制药之前),它讨论了数组协方差的危险.

数组协方差似乎暗示了解释,但我想不出一个.


当前的jdk是1.8.0.0_91

biz*_*lop 5

考虑这个例子:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,用于推断类型参数的签名是相同的,唯一不同的是fArray()只读取数组元素而不是编写它们,使得T -> A推理在运行时完全合理.

并且编译器无法告诉您在执行该方法时将使用哪个数组引用.