1

Consider the following Kotlin interface:

interface Box<out T : SomeType> {
    val item: T
}

Implementing this with Kotlin would look something like this:

data class BoxImpl<out T : SomeType>(override val item: T) : Box<T>

Now I want to convert that interface to Java:

public interface Box<T extends SomeType> {
    T getItem();
}

But implementing this in Kotlin causes a problem:

data class BoxImpl<out T : SomeType>(private val item: T) : Box<T> {

    override fun getItem(): T {
        return item
    }
}

The problem is that the Kotlin compiler complains that T is invariant on the Java interface, but covariant on the implementation.

So, how do you specify out (covariant) variance in Java?

Additionally, how would you specify in (contra-variant) variance?

Matthew Layton
  • 39,871
  • 52
  • 185
  • 313
  • 1
    As both answers indicate "Java doesn’t support declaration-site variance like Kotlin does". Notice [JEP-300](https://openjdk.org/jeps/300) proposes it, so maybe in a distant future... – Jacob van Lingen Aug 17 '23 at 17:05

2 Answers2

4

Java doesn’t support declaration-site variance like Kotlin does. It only supports use-site variance. In other words, variance can only be used on variable and parameter types, not in class/interface definitions.

So in Java, you would specify your interface and class definitions without variance, and every time you need variance, you would declare the variable with it.

Covariance uses ? extends, and contravariance use ? super, for example:

Box<? extends Number> numberBox = new BoxImpl(1);
Tenfour04
  • 83,111
  • 11
  • 94
  • 154
3

Java doesn't have declaration-site variance like Kotlin does. That is, you cannot specify the variance of a type when you declare it. However, it does have use-site variance - you can specify the variance when you use a type, as the type of a variable, for example.

For example,

// Kotlin
var foo: SomeGenericType<out T>
var bar: SomeGenericType<in T>

is similar to:

// Java
SomeGenericType<? extends T> foo;
SomeGenericType<? super T> bar;

(The mnemonic for this is PECS.)

A crucial difference though, is that you can pass nulls into methods of the Java bar variable,

bar.someMethod(null);

But you cannot pass anything into the Kotlin bar - it literally takes Nothing!

If you are trying to use a generic type from Java in your Kotlin code, I think you'd have to make it @UnsafeVariance to suppress the compiler error for now.

// Of course, only do this if you are sure that Box is covariant!
data class BoxImpl<out T : SomeType>(private val item: T) : Box<@UnsafeVariance T>

There are also discussions about adding the ability to annotate Java type parameters so that the Kotlin compiler knows about their variance. See this and this. Hopefully we can get that some day in the future. Though this doesn't change the fact that there is no declaration-site variance in Java.

Sweeper
  • 213,210
  • 22
  • 193
  • 313