数组和泛型的协变性

数组协变性 ,泛型不可协变性

数组的协变性:如果类A是类B的子类,那么A[]是B[]的子类
泛型(<>)不可协变性:如果类A是类B的子类,List<A>List<B>毛线关系都木有

举个例子

Object[] obj = new String[]{};

这个写法是完全OK的,编译通过,而且即便在obj中存放了非string对象,也会在运行时才报异常
这就是数组的协变性,Object[] 是 String[] 的父类

ArrayList<Object> obj = new ArrayList<Integer>();

这个写法完全是错误的,连编译都不通过,因为泛型的非协同性,List<Object>LIst<Integer>是完全没有关系的两个类 ,那有怎么能new

那么为什么呢

数组为什么是协变性的

数组的协变性来源于数组的一个优势
数组记得它内部元素的具体类型,并且会在运行时做类型检查

也正是因为数组会在运行时做类型检查,在String[]插入一个Integer会报错,这也保证虽然数组是协变性的,依然能保证安全

泛型为什么是不协变性的

那么泛型为什么不是协变性的呢,为什么不能为泛型添加运行做类型检查的机制呢,这是因为泛型的一个特性——擦除性

泛型的擦除性

擦除性:去除泛型类型中所有的类型参数信息,只映射为一份字节码,具体的参数信息只能在静态编译期保留。(这个擦除性在泛型中详细解释,包括如何擦除的等等)

所以,作为只能在编译器保留类型的泛型,只能选择在编译期进行类型检查,因为一旦运行起来这个类型就被擦除掉了呀呀呀,这也就是为什么ArrayList<Object> obj = new ArrayList<Integer>(); 编译不会通过的原因。

那么如何能让泛型拥有类似协变性的功能呢

那么再次回到通配符的问题了
像这种实例化对象时使用通配符? ,我好像在哪篇里面写了,在这里再强化一下好了

<? extends xx>
<? super xx>

这样

ArrayList<? extends Object> list = new ArrayList<Integer>;

就OK了,我记得原来说<? extends xx>表示的是xx的一个子类,再并不在意是什么类型,当时书上还写是为了向上转型,这样就串起来了
PS : 是一种类型嗷嗷嗷啊,?表示的也是xx中确定的一种类型

泛型不可协变性的混淆点

父类A 子类B
泛型的不可协变性是说List<A>List<B>没有关系
可没有改变类A 和 类B之间的继承关系,类A的对象和类B的对象依然是可以转型的

但是这里注意一下,向上转型的主客问题,向上转型是子类B转成父类A,存放在父类A的List中
即父类的List中存放子类对象

        ArrayList<Object> list = new ArrayList<Object>();
        Integer i = new Integer(2);
        list.add(i);

这个代码OKOK的

### 无法构造数组的原因 在Java中,无法直接创建数组的主要原因与擦除(type erasure)机制有关。由于信息在编译时会被擦除,运行时将不再保留具体的信息[^1]。因此,当尝试创建一个数组时,编译器无法验证数组元素的安全性。例如,如果尝试执行 `new T[]`,编译器会报错,因为运行时无法确定 `T` 的具体。 此外,数组变的(covariant),这意味着父数组可以引用子类数组。然而,这种变性的安全性要求相冲突。例如,以下代码会导致潜在的运行时错误: ```java Object[] objects = new String[1]; objects[0] = new Integer(42); // 编译时不会报错,但运行时会抛出 ArrayStoreException ``` 对于数组似的错误可能会被隐藏,直到运行时才暴露,这违背了设计的核心目标——确保安全[^2]。 --- ### 解决方案 尽管不能直接创建数组,但可以通过以下几种方法实现似的功能: #### 1. 使用 `ArrayList` 或其他集合替代数组 `ArrayList` 是一种更灵活且安全的选择,因为它完全避免了数组变问题。例如: ```java import java.util.ArrayList; public class Example { public static <T> ArrayList<T> createArrayList(int size) { return new ArrayList<>(size); } } ``` 这种方式不仅解决了擦除的问题,还提供了更多的功能,如动态调整大小[^3]。 #### 2. 使用非数组并进行强制转换 如果必须使用数组,可以通过创建一个非数组并将其强制转换为数组。需要注意的是,这种方法会产生一个未检查的转换警告(unchecked cast warning)。例如: ```java public class Example { public static <T> T[] createGenericArray(Class<T> clazz, int size) { @SuppressWarnings("unchecked") T[] array = (T[]) java.lang.reflect.Array.newInstance(clazz, size); return array; } } ``` 上述代码通过反射创建了一个指定数组,并进行了强制转换。虽然这种方式可行,但需要开发者确保安全[^4]。 #### 3. 使用原始数组 另一种方法是直接使用原始数组(raw type array),但这会失去提供的安全性编译时检查。例如: ```java public class Example { public static LinkedList[] createLinkedListArray(int size) { @SuppressWarnings("unchecked") LinkedList[] array = new LinkedList[size]; return array; } } ``` 尽管这种方式简单直接,但由于缺少检查,可能导致运行时错误。 --- ### 总结 无法构造数组的根本原因是擦除数组变之间的冲突。推荐的解决方案包括使用 `ArrayList` 替代数组、通过反射创建数组或使用原始数组。每种方法都有其优缺点,开发者应根据具体需求选择合适的方式[^5]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值