泛型方差场景下ArrayList与List的差异及编译报错原因
问题:为什么Wrapper1编译报错?ArrayList与List的差异是什么?
先看两段代码:
可正常编译的代码
class Wraper { private fun foo(list: ArrayList<out Number>) { } private fun bar(list: ArrayList<in Number>) { } fun doSome() { foo(arrayListOf<Int>()) bar(arrayListOf<Any>()) } }
编译报错的代码
// error: // Projection is conflicting with variance of the corresponding // type parameter of List. Remove the projection or replace it with '*' class Wrapper1 { private fun foo(list: List<out Number>) { } private fun bar(list: List<in Number>) { } // error fun doSome() { foo(listOf<Int>()) bar(listOf<Any>()) } }
报错原因
核心问题出在List和ArrayList的泛型方差声明不同:
- ArrayList是不变类:它的泛型参数没有默认的
out/in标记,属于**不变(invariant)**类型。因此你可以通过手动添加out/in投影,临时改变它的协变/逆变特性——比如ArrayList<out Number>表示这个集合只能作为生产者(读取Number类型的值),ArrayList<in Number>表示只能作为消费者(写入Number类型的值),这两种写法都是合法的。 - List是协变接口:Kotlin中
List接口的定义是interface List<out E>,泛型参数E天生被标记为out(协变)。这意味着List本身只能作为生产者,不能被当作消费者使用。此时你给它添加in Number投影,就和它原本的协变声明冲突了,编译器自然会报错。
另外,Wrapper1中的foo(list: List<out Number>)写法是冗余但合法的——因为List已经是协变的,重复加out不会冲突,但完全没必要。
ArrayList与List的关键差异
- 泛型方差特性:
- List是只读接口,天生协变(
out E),因此你可以安全地将List<Int>赋值给List<Number>,因为只读集合不会出现类型不安全的写入操作。 - ArrayList是可变集合的具体实现类,泛型参数默认不变,没有
out/in标记。如果需要临时调整它的协变/逆变特性,必须手动添加投影。
- List是只读接口,天生协变(
- 读写权限:
- List只提供只读方法(如
get()、size),协变特性保证了类型安全。 - ArrayList是MutableList的实现,支持读写操作,不变的泛型参数避免了像
List<in Number>这种矛盾的用法,防止不安全的写入。
- List只提供只读方法(如
内容的提问来源于stack exchange,提问作者BollMose
相关产品推荐
相关产品推荐

