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

时间:2016-05-04 10:03:39

标签: 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();
    }
}

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

T似乎推断为Object,因为我也可以传递Generic<Object>

此外,在实际运行代码时,会在ArrayStoreException行上抛出a[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());
}

这确实产生了错误:

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
}

编译器正确识别出第一个?可能位于继承层次结构的下方,而不是第二个?

e.g。如果第一个?B而第二个?A,那么这不是类型安全的。

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

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

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

我自己的研究中并没有找到任何东西,除了2005年的this article(仿制药之前),它讨论了数组协方差的危险。

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

当前jdk为1.8.0.0_91

2 个答案:

答案 0 :(得分: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);
    }
}

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

编译器没有办法告诉你的数组引用在实现方法时会用到什么。

答案 1 :(得分:2)

  

我希望将T推断为B. A不会扩展B.那么编译器为什么不抱怨呢?

T未被推断为B,推断为A。由于B扩展AB[]A[]的子类型,因此方法调用是正确的。

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

a[0] = b.get();

运行时环境知道a实际上是B数组,并且无法容纳A

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