You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin泛型疑问:Movement<Cat>实例为何不能赋值给Movement<Animal>?

Why can't I assign Movement<Cat> to Movement<Animal> in Kotlin (works in Java)?

Great question—this boils down to a key difference in how Kotlin and Java handle generic variance, plus a common misconception about Java's actual behavior. Let's break this down step by step:

First, the core issue: Kotlin's generics are invariant by default

In Kotlin, generic types are invariant unless explicitly marked otherwise. That means even though Cat is a subtype of Animal, Movement<Cat> is not a subtype of Movement<Animal>. The compiler blocks your second assignment to prevent a critical type safety risk:

  • If you could assign CatMovementImpl (which only handles Cat instances) to a Movement<Animal> variable, you could later call catMovement.moveAnimal(Dog())—but CatMovementImpl has no idea how to process a Dog, leading to a runtime crash.

Wait, does this really work in Java?

Actually, it doesn’t—at least not with proper generic usage. If you write the equivalent Java code:

// Java code matching your Kotlin setup
Movement<Animal> catMovement = new CatMovementImpl(); // COMPILE ERROR: Incompatible types

You’ll get a compiler error, just like in Kotlin. The only way this "works" in Java is if you use raw types (dropping the generic parameter entirely, e.g., Movement catMovement = new CatMovementImpl();) which gives you an unchecked warning and bypasses type safety—something Kotlin intentionally eliminates to enforce safer code.

Alternatively, Java allows you to use a wildcard for covariance:

Movement<? extends Animal> catMovement = new CatMovementImpl(); // Compiles fine

But this comes with a catch: you can’t call moveAnimal() on this variable, because the compiler can’t guarantee the actual type of T (it could be Cat, Dog, or any other Animal subtype).

How to fix this in Kotlin

If you need to assign CatMovementImpl to a variable that accepts a broader Animal-based Movement, you have two main options:

1. Use a covariant projection (out)

This is Kotlin’s equivalent of Java’s ? extends Animal:

var catMovement: Movement<out Animal> = CatMovementImpl()

Like the Java wildcard, this lets you hold the instance safely, but you won’t be able to call moveAnimal() on it—since the compiler can’t verify that the input type matches the actual implementation’s expected Cat.

2. Re-design your interface for covariance (if possible)

Covariance (out modifier on the generic parameter) is only safe if the generic type T is used only as an output (e.g., return values) and never as an input (method parameters). Since your Movement interface uses T as an input to moveAnimal(), you can’t mark T as out directly—this would be a compiler error.

If you can adjust your interface’s design to limit T to output positions, you could make it covariant:

// Example of a covariant Movement interface (only uses T as output)
interface Movement<out T : Animal> {
    fun getMovingAnimal(): T
}

With this, Movement<Cat> would be a valid subtype of Movement<Animal>, and assignments would work without projections.

3. Use star projection (for limited use cases)

If you don’t care about the specific Animal subtype at all, you can use a star projection:

var catMovement: Movement<*> = CatMovementImpl()

This is similar to Movement<out Animal>, but even more restrictive—you can’t call any methods that take T as input, and any returned T values are treated as Animal?.

Key takeaway

Kotlin’s strict invariant generics by default prevent the type safety holes that Java’s raw types or careless wildcard usage can introduce. The "error" you’re seeing is actually a feature—it’s protecting you from accidental runtime crashes. Use projections or adjust your interface design to work within Kotlin’s type system.

内容的提问来源于stack exchange,提问作者Muhammad Ahmed AbuTalib

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 09:13:42