[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$f32675y006yv6z":3},{"_id":4,"slug":5,"title":6,"subtitle":7,"kind":8,"cards":9,"tags":14,"categories":15,"source":16,"lang":19,"author":20,"audioState":23,"stats":24,"publishedAt":27,"renderer":28},"6abb1957ca21c797c7e9bf1d","javakotlin-76948d75","深入理解 Java\u002FKotlin 协变与逆变","型变（Variance）是泛型编程中最容易让人困惑的概念之一。本文从问题出发，讲清原理，落到实战。 型变（Variance）是泛型编程中最容易让人困惑的概念之一。本文从问题出发，讲清原理，落到实战。 直觉上 Cat 是 Animal 的子类，List\u003CCat> 应该也能赋值给 List\u003CAnimal>。但编译器拒绝了——为什么？ 如果允许这种赋值，类型安全就会被破坏。…","news",[10],{"headline":6,"body":11,"imageUrl":12,"images":13},"型变（Variance）是泛型编程中最容易让人困惑的概念之一。本文从问题出发，讲清原理，落到实战。 型变（Variance）是泛型编程中最容易让人困惑的概念之一。本文从问题出发，讲清原理，落到实战。 直觉上 Cat 是 Animal 的子类，List\u003CCat> 应该也能赋值给 List\u003CAnimal>。但编译器拒绝了——为什么？ 如果允许这种赋值，类型安全就会被破坏。 这就是\"型变\"要解决的问题：如何在保持类型安全的前提下，让泛型也能享受继承带来的灵活性。 假设 Cat extends Animal，对于泛型 Box\u003CCat> 和 Box\u003CAnimal>： Java 数组是协变的，这在编译期不会报错，但运行时可能崩溃： 数组在运行时仍记得自己是 Cat[]，存入 Dog 时抛出异常。这是 Java 早期为了兼容没有泛型的代码而做的妥协，后来被泛型的\"不变\"设计纠正。 Java 泛型默认不变，灵活性靠使用处型变（在使用泛型的地方声明）： 口诀：extends 只能读，super 只能写。 Producer Extends, Consumer Super —— 生产者用 extends，消费者用 super。 Producer Extends, Consumer Super —— 生产者用 extends，消费者用 super。 JDK 经典案例 Collections.copy： Kotlin 认为 Java 的\"每次使用都要写通配符\"太啰嗦，于是把型变声明移到了类的定义处。 当类本身没有声明型变时，Kotlin 也支持在使用处投影： 设计启示：只读接口加 out，只写接口加 in，读写都有就保持不变。 Kotlin 函数类型的定义完美体现了 PECS： 记忆：参数逆变，返回值协变（函数类型也是\"消费参数、生产结果\"）。","\u002Fapi\u002Fmedia\u002Fposts\u002Fjavakotlin-76948d75\u002F0.webp",{"local":12},[],[],{"name":17,"url":18},"Juejin","https:\u002F\u002Fjuejin.cn\u002Fpost\u002F7689077368330682419","zh",{"handle":21,"displayName":22},"spots","Spots",null,{"views":25,"likes":26,"saves":26,"shares":26,"completions":26,"opens":26,"skips":26,"depthSum":26},3,0,"2026-09-29T01:50:15.323Z","local"]