David Kubecka
01/09/2023, 3:12 PMinterface Dog : Animal
interface AnimalHouse {
val animals: List<Animal>
}
interface DogHouse : AnimalHouse {
override val animals: List<Dog>
}
But trying to implement the DogHouse
interface in Java
class DogHouseImpl implements DogHouse {
@Override
public List<Dog> getAnimals() {
return animals;
}
}
leads to an error DogHouseImpl is not abstract and does not override abstract method getAnimals() in AnimalHouse
. That's because contrary to Kotlin List
is invariant in Java.
How does actually Kotlin achieve that on the bytecode level? Isn't there a way to use a similar technique also in Java? In my case, the Java code is generated so I can use any dirty tricks there 🙂Rob Elliot
01/09/2023, 3:41 PMAnimalHouse
you can fix it:
interface AnimalHouse<A : Animal> {
val animals: List<A>
}
David Kubecka
01/09/2023, 5:20 PMAnimalHouse
is used at lot of places and those are used recursively and so on... which requires quite a lot of refactoring).
Anyway, why it actually works? 🙂Rob Elliot
01/09/2023, 5:35 PMDavid Kubecka
01/09/2023, 5:45 PMRob Elliot
01/09/2023, 5:53 PMjava.util.List<T>
and kotlin.collections.List<T>
get compiled to the erased type java.util.List
. So at runtime the JVM is happy that DogHouse.animals
has the same return type as AnimalHouse.animals
- they both just return a type erased List
.
kotlinc
is its own compiler - it knows as you say that the type parameter of kotlin.collections.List
is covariant, so it's fine for DogHouse.animals
to have a covariant return type when it overrides AnimalHouse.animals
.
But it emits the generic type in the class's metadata, so when you use javac
to try and compile a class that implements both DogHouse
and AnimalHouse
it sees the generic type as invariant on java.util.List
. So you effectively get the same compile error you would have got if you'd tried to write AnimalHouse
and DogHouse
in Java.
That's my guess, at any rate.David Kubecka
01/10/2023, 7:24 AMA
in the class metadata, isn't it?Rob Elliot
01/10/2023, 8:54 AMA : Animal
on AnimalHouse
allows you to declare a Java instance with the covariant type ? extends Animal
as so:
AnimalHouse<? extends Animal> animalHouse = new DogHouseImpl()
- at that point javac
will correctly prevent you calling animalHouse.animals.add(new CatImpl())
.
It's just the same in Java, see the snippet.Rob Elliot
01/10/2023, 8:56 AMkotlinc
knows about kotlin.collections.List
- that it's covariant because it's unmodifiable without downcasting - and what kotlinc
emits - a modifiable, and hence invariant, java.util.List
.David Kubecka
01/10/2023, 4:25 PMDogHouseImpl
usage but in the class itself. With your setup above DogHouseImpl
compiles but I'm more interested in why it doesn't compile with my original code.
Oh.. and thanks for your answers! They are very useful so far 🙂Rob Elliot
01/10/2023, 4:28 PMDogHouse
would not compile using javac
, so DogHouseImpl
can't compile in javac
. It's seeing Animals.getAnimals()
as having return type java.util.List<Animal>
and javac
will not allow you to override a method that returns java.util.List<Animal>
and narrows the type to java.util.List<Dog>
because then you could upcast it and add a Cat
.David Kubecka
01/10/2023, 4:30 PMilya.gorbunov
01/10/2023, 7:16 PMinterface AnimalHouse {
val animals: List<@JvmWildcard Animal>
}
This way, getAnimals
method will get the signature
java.util.List<? extends Animal> getAnimals()
instead of just
java.util.List<Animal> getAnimals()
and it will be possible to override its return type covariantly in Java with List<Dog>
ilya.gorbunov
01/10/2023, 7:18 PMRob Elliot
01/10/2023, 8:29 PMWhen it's a return value, wildcards are not generated, because otherwise Java clients will have to deal with them (and it's against the common Java coding style)Hmm... not immediately convinced by that argument
ilya.gorbunov
01/10/2023, 8:34 PMList<? extends Animal>
when calling `getAnimals`: https://youtrack.jetbrains.com/issue/KT-10254/Inconsistent-translation-of-covariant-types-to-Java#focus=Comments-27-1240427.0-0
Probably, in Java >= 10 where it became possible to use var
to avoid spelling types, it is much less of concern, but the train has gone now.David Kubecka
01/10/2023, 8:57 PM@Nullable
List getApplicants();
Is this just a limitation of the IntelliJ decompiler?Rob Elliot
01/10/2023, 8:59 PMList<? extends Animal>
and have the type system tell me I couldn't add to the List, particularly if it avoided inheritance pain like this. Too late now though as you say.