'Surprising: Java wildcard codes cannot turn to Kotlin

It is a question about using wildcards in Kotlin as the same in Java and my description will comes with multiple codes example where I tried to make them work and it may be a long question.

Recently, I want to design a series of classes in Kotlin like

abstract class A<out T : B> {
    abstract fun doASth()
}

interface B

open class A1<out T : B1>(
    val myB: T,
) : A<T>() {
    override fun doASth() {
        myB.doBSth(this)
    }
}

open class B1(
    open val doBSth: (A1<out B1>)->Unit = {}
) : B

// A2 missed since I cannot defined proper B2

open class B2(
    override val doBSth: (A1<out B2>)->Unit = {}  // ERROR: from A1<out B2> to A1<out B1>
):B1(doBSth)

There I found Kotlin cannot do wildcard out since out B2 is clearly a child of out B1.

Next comes my work.

First try: move wildcard to class announcement and I got these codes I got first error

abstract class A<out T : B> {
    abstract fun doASth()
}

interface B

open class A1<out T : B1<out B1<*>>>(
    open val myB: T,
) : A<T>() {
    override fun doASth() {
        myB.doBSth(this)//              ERROR: require A<Nothing> but get A1<T>

        // SOLUTION: here if I use cast them no error but comes warning of unchecked cast
        // and I really hate that. Though I can mark no warning by annotaion, I am 
        // seeking for a better way rather than jump it.

        // By the way, this solution is the only one with no error but only warnings
        // which meaning I can go compile!!! :-)
    }
}

open class B1<T : B>(
    open val doBSth: (A<T>) -> Unit = {}
) : B

open class A2<out T : B2<out B2<*>>>(
    override val myB: T,
) : A1<T>(myB)

open class B2<T : B>(
    override val doBSth: (A<T>) -> Unit = {}
) : B1<T>(doBSth)

I got second error

abstract class A<out T : B> {
    abstract fun doASth()
}

interface B

open class A1<out T : B1< B1<*>>>(
    open val myB: T,
) : A<T>() {
    override fun doASth() {
        myB.doBSth(this)
    }
}

open class B1<T : B>(
    open val doBSth: (A<T>) -> Unit = {}
) : B

open class A2<out T : B2< B2<*>>>(
    override val myB: T,
) : A1<T>(myB) //                         ERROR: except B1<B1<*>> but got T

open class B2<T : B>(
    override val doBSth: (A<T>) -> Unit = {}
) : B1<T>(doBSth)

These two error tell:

  1. use nest out will bring you Nothing in type analysis which is not acceptable for me:

    I use wildcard to declared all types I needed, why you just bring me Nothing!

  2. Kotlin out cannot out nested type since they will be erased:

    This is problem of compile since I agree Apple<Apple> is safe to be used as Fruit<Fruit>.

Next is my final try, I wrote following codes in Java:

abstract class A<T extends B> {
}

interface B {
}

class A1<T extends B1> extends A<B1> {
    public A1() {
        this.self = null;
    }

    public A1(A1<? extends B1> self) {
        this.self = self;
    }

    public A1<? extends B1> self;
}

class B1 implements B {
    public B1() {
        this.aPointer = null;
    }

    public B1(A1<? extends B1> aPointer) {
        this.aPointer = aPointer;
    }

    public A1<? extends B1> aPointer;
}

class A2<T extends B2> extends A<B2> {
    public A2() {
        this.self = null;
    }

    public A2(A2<? extends B2> self) {
        this.self = self;
    }

    public A2<? extends B2> self;
}

class B2 extends B1 {
    public B2() {
        this.aPointer = null;
    }

    public B2(A2<? extends B2> aPointer) {
        this.aPointer = aPointer;
    }

    public A2<? extends B2> aPointer;
}

Then I use Android Studio to convert it to Kotlin automatically, and I got

internal abstract class A<T : B?>
internal interface B
internal open class A1<T : B1?> : A<B1?> {
    constructor() {
        self = null
    }

    constructor(self: A1<B1>?) {
        this.self = self
    }

    var self: A1<B1>?
}

internal open class B1 : B {
    constructor() {
        aPointer = null
    }

    constructor(aPointer: A1<B1>?) {
        this.aPointer = aPointer
    }

    open var aPointer: A1<out B1>? // here I add open & out to fix convert
}

internal class A2<T : B2?> : A<B2?> {
    constructor() {
        self = null
    }

    constructor(self: A2<B2>?) {
        this.self = self
    }

    var self: A2<B2>?
}

internal class B2 : B1 {
    constructor() {
        aPointer = null
    }

    constructor(aPointer: A2<B2>?) {
        this.aPointer = aPointer
    }

    override var aPointer: A2<B2>? // here I add override to fix convert
                                   // ERROR: A2<B2> or A1<B2> is both not acceptable
}

That's all and as you can see, I cannot make my Java code to Kotlin and use wildcard in Kotlin as I used them in Java.

UPDATE

Cause name like A1&B1 is quite confusing, here updated a named example for my fisrt try above which failed on cast Nothing.



abstract class BaseExecutor<out T : BaseState> {
    abstract fun doSth()
}

interface BaseState

open class ExecutorG1<out T : StateG1<out StateG1<*>>>(
    open val state: T,
) : BaseExecutor<T>() {
    override fun doSth() {
        state.doSth(this)//              ERROR: require A<Nothing> but get A1<T>

        // SOLUTION: here if I use cast them no error but comes warning of unchecked cast
        // and I really hate that. Though I can mark no warning by annotaion, I am 
        // seeking for a better way rather than jump it.

        // By the way, this solution is the only one with no error but only warnings
        // which meaning I can go compile!!! :-)
    }
}

open class StateG1<T : BaseState>(
    open val doSth: (BaseExecutor<T>) -> Unit = {}
) : BaseState

open class ExecutorG2<out T : StateG2<out StateG2<*>>>(
    override val state: T,
) : ExecutorG1<T>(state)

open class StateG2<T : BaseState>(
    override val doSth: (BaseExecutor<T>) -> Unit = {},
    val moreConfig: String = "",
) : StateG1<T>(doSth)

UPDATE 2

For J's ask, here is an example of my final java codes which works: (This look a little complex because I did't use A<? extends A> in example since it is not allowed in kotlin. Aha, that's also a difference between java & kotlin.) (Java and koltin do have some difference and I tried my best to make my Java code similar to kotlin, there maybe improvement and suggestions is welcome)

// interface for lambda
interface FunctionPointer<T> {
    public void doSth(T input);
}

interface BaseState {
}

abstract class BaseExecutor<S extends BaseState> {
    public S myState;

    public abstract void doSth();
}

// G for generation
class StateG1<E extends BaseExecutor<? extends BaseState>> implements BaseState {
    public StateG1(FunctionPointer<E> partJobPointer) {
        this.partJobPointer = partJobPointer;
    }

    public FunctionPointer<E> partJobPointer;
}

class ExecutorG1<S extends StateG1<BaseExecutor<? extends StateG1<?>>>,
        E extends BaseExecutor<S>> extends BaseExecutor<S> {
    @Override
    public void doSth() {
        myState.partJobPointer.doSth(this);

        // here I treat any descendant state as G1 state, which is safe
    }
}

class StateG2<E extends BaseExecutor<? extends BaseState>> extends StateG1<E> {
    public StateG2(FunctionPointer<E> partJobPointer) {
        super(partJobPointer);
    }

    public StateG2(FunctionPointer<E> partJobPointer, Map<String, String> moreConfig) {
        super(partJobPointer);
        this.moreConfig = moreConfig;
    }

    public Map<String, String> moreConfig;
}

class ExecutorG2<S extends StateG2<BaseExecutor<? extends StateG1<?>>>,
        E extends BaseExecutor<S>> extends ExecutorG1<S, E> {

    @Override
    public void doSth() {
        super.doSth();

        // here I treat any descendant state as G2 state, which is safe

        myState.partJobPointer.doSth(this);
        myState.moreConfig.clear();
    }
}


Solution 1:[1]

What you try to do in B2 is breaking the type safety and this is why Kotlin does not allow it. You are correct T in A1 is covariant (out), so A1<B2> is a subtype of A1<B1>. However, A1<B1> is used as a parameter to lambda, so this whole type is contravariant. This is similar as you would have a function in B1 that receives A1<B1>. And because it is contravariant, you can't override it with its subtype, i.e. A1<B2>.

To show the problem with this we can use the following example. Assume B2 implements this property as:

override val doBSth: (A1<out B2>)->Unit = {
    val b2 = it.myB // we acquire B2 object from the provided A1
    // do something with B2 object
}

Now, someone creates B2 and uses it as B1:

val b2: B1 = B2()
val a1ProvidingB1: A1<B1> = ...
b2.doBSth(a1ProvidingB1) // this is allowed, but shouldn't be - broken type safety.

b2 expects that it will get B2 object from the passed a1ProvidingB1. But in fact, our a1ProvidingB1 is A1<B1>, so it will provide B1 object, not B2. So this breaks type safety.

Now, I didn't read your all examples thoroughly, but regarding your Java example and why it works. Well, it does a much different thing. First, in Java your aPointer is not a lambda that receives A1/A2, but it is a A1/A2 object itself. If you do the same with Kotlin code, it will start working, because then it will be covariant.

Second, in Java code aPointer is a field, but in Kotlin doBSth is a getter. We can't override fields, aPointer in B2 is not overriding aPointer in B1 - it is just hiding it. But doBSth in Kotlin has to override the function of the super class, which is a much different thing.

Update

Your example is problematic not due to some technical limitations of the compiler. Your example is conceptually wrong, it can't be safe, so depending on the language it will be either disallowed or the compiler will allow it, but it will break the type safety.

You need to consider what happens when your base class MyClass produces or consumes some other data e.g. Bird class and MyClass is subtyped. If it produces birds then subtypes can produce more specific types, e.g. Eagle. This is safe, because each produced eagle is also a bird. But if MyClass consumes birds then the subtype can't decide it will consume only eagles, because then it couldn't be represented as the MyClass which consumed all birds. Actually, in that case the subtype could consume all animals and that would be safe, because if the subtype can consume any animal then it surely can consume birds. But again, if we produce birds then the subtype can't produce animals, because that would mean some of them would not be birds and MyClass produced birds only.

So wildcards work in the opposite direction depending if the data is produced or consumed. If it is produced then subtypes can produce more specific types only (subtypes). If it is consumed, subtypes can only produce more generic types (supertypes).

In your example A1 is a lambda parameter and that means B1 consumes A1. This is why you can't use subtypes of A1 in subtypes of B1. If you say B2 only consumes A2, then still someone can use B2 as B1 and feed it with A1 which B2 couldn't handle. This breaks the type safety.

Also, this is why it is much different if you have lambda accepting A1 (consume) or just A1 itself (produce - assuming it is val).

Solution 2:[2]

Kotlin cannot do wildcard out since out B2 is clearly a child of out B1.

Not sure what you mean by this, but as @broot already mentioned in the comments, what you're trying to achieve (at least in the first snippet) is conceptually flawed.

In particular B2 IS-NOT a B1, because an instance of B1 must have a doBSth that accepts any instance of A1<out B1>. But your definition of B2's doBSth only accepts a subset of those.

To put it in technical terms, functions are contravariant in their argument types, so the function type (A1<out B2>)->Unit is not a subtype of (A1<out B1>)->Unit, because it would require A1<out B1> to be a subtype of A1<out B2> (the opposite of what you provide).

We can demonstrate that your first definition doesn't work. Imagine you were allowed to define B2 like you did, now this would compile fine but would be broken:

val b2ViewedAsB1: B1 = B2 { arg: A<B2> -> println("I only want A<B2>") }

val aRealB1: B1 = B1 { arg: A<B1> -> println("I can handle all A<B1>") }

// must be allowed per B1's definition, yet B2 can't deal with this
b2ViewedAsB1.doBSth(A1<B1>(aRealB1))

this solution is the only one with no error but only warnings

Well you had an error, but you're desperately trying to hide it. Adding an unsafe cast and suppressing the associated warning will not solve the underlying conceptual problem. B2 cannot be a subtype of B1 in the way you define it.

use nest out will bring you Nothing in type analysis which is not acceptable for me: I use wildcard to declared all types I needed, why you just bring me Nothing!!!

Nothing is the perfect expression of the fact that no type can satisfy all the generic constraints you defined at the same time (because they contradict each other).


EDIT: the conversation is getting quite long under @broot's answer, so let me have another stab at showing the problem with real things here.

In your first example, A and B aren't used anywhere, so let's remove them for simplicity. I'll rename A1 to Toy and BX classes to animals for clarity.

You're trying to do this:

internal open class Toy<out T : Animal>(
    open val owner: T
) {
    fun amuseOwner() {
        owner.playWith(this)
    }
}

internal open class Animal(
    open val playWith: (Toy<Animal>) -> Unit
)

internal class Dog(
    override val playWith: (Toy<Dog>) -> Unit // thankfully does not compile
) : Animal(playWith)

internal class Cat(
    override val playWith: (Toy<Cat>) -> Unit // thankfully does not compile
) : Animal(playWith)

fun main() {
    val cat: Cat = Cat { catToy: Toy<Cat> -> /* do something */ }
    val dog: Dog = Dog { dogToy: Toy<Dog> -> /* do something */ }
    val catToy: Toy<Cat> = Toy(cat)

    val dogAnimal: Animal = dog // allowed by Liskov substitution principle
    dogAnimal.playWith(catToy) // should NOT be allowed, but compiles fine
}

Even though Toy<Dog> is indeed a subtype of Toy<Animal>, the function (Toy<Dog>)->Unit is NOT a subtype of (Toy<Animal>)->Unit - it's in fact a supertype of it.

You can see that it's conceptually incorrect to want at the same time that Animal can playWith any Toy<Animal>, but that Dog can only play with Toy<Dog> - if that is so, Dog cannot be an Animal in the OO sense, because it cannot play with Toy<Cat>.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2