Declaration variance on a container class & Java's Spliterator

Consider the following humble example:

class Container<out T>(val element: T): Iterable<T> {
    override fun iterator(): Iterator<T> = listOf(element).iterator()
    override fun spliterator(): Spliterator<T> = listOf(element).spliterator()
    fun x(): Spliterator<T> = listOf(element).spliterator()
}
fun <T> Container<T>.y(): Spliterator<T> = listOf(element).spliterator()

In this example code, I’m defining a hopefully straightforward Container class, which has a parameter of type T, and these things should be covariant with one another, so it’s declared as such in the type parameter <out T>.

We want the Container to be Iterable, so we declare it as such. Iterable becomes kotlin.collections.Iterable, which is just fine because it’s also declared <out T>. But with Kotlin/JVM, this is also java.lang.Iterable, which has a spliterator() method of its own, so you need to override it, right?

Line 3 of the example code above has an error according to Kotlin 1.3.61: Type parameter T is declared as 'out' but occurs in 'invariant' position in type Spliterator<T>. So fine, I tried doing it without any overrides with fun x() on line 4. Same error. The bottom line, with the y() function seems to work, but you cannot use the specific name spliterator() instead of y(), since that would shadow the method name from java.lang.Iterable.

What if I change Container<out T> to Container<T>? This lets the code compile, but now the class doesn’t have the desired substitutability, which was the whole point of the exercise.

I also tried override fun spliterator(): Spliterator<@JvmSuppressWildcards T> but this didn’t change anything.

Wrapping this up: what’s the right way to declare a container class, as above, that has declared covariance, is Iterable, and plays nice with Java’s Spliterator?

I think that what I really want is some way to say “never mind that declaration above, this time it’s just T”. The only available workaround, so far as I can tell, is to get rid of the <out T> and instead make sure that each and every input parameter that accepts a Container declares covariance at the use site, just like Java programmers are forced to do.

Am I missing something? Is there some sneaky way to narrow the type parameter from <out T> to <T> to be compatible with java.lang.Iterable.spliterator()?

Just remove your override of spliterator and the <out T> is perfectly fine.

Methods with default implementations do not need to be overridden.

While it is correct that you don’t have to implement methods with default implementations, you still might want to. The documentation for Iterable.spliterator() states:

Implementation Note:
The default implementation should usually be overridden. The spliterator returned by the default implementation has poor splitting capabilities, is unsized, and does not report any spliterator characteristics. Implementing classes can nearly always provide a better implementation.

So let’s say I get rid of the override and just have the spliterator() method, whose type signature is exactly the same as the default method from java.util.Iterable (but absent from kotlin.collections.Iterable).

I then pass an instance of this Container class into some random Java code, somewhere else, which tries to get the spliterator out of it. Will it get the default spliterator code (which is lacking the desired optimizations), or will regular Java overloading kick in and I’ll get the Container.spliterator method defined above?

If you just add the spliterator method without override, it will in fact work just fine in Java: Your custom implementation will be called.

I tried it out and what’s more interesting is that signature does not even have to be identical to the one declared in Java: You can actually write the following:

class CustomSpliterator<T> : Spliterator<T> {/* snip */}

class Container<out T>(val element: T): Iterable<T> {
    override fun iterator(): Iterator<T> = listOf(element).iterator()
    fun spliterator(): CustomSpliterator<out T> = CustomSpliterator()
}

And it will still work.

Why is that interesting? Well, overriding is normally handled by the JVM rather than the compiler. If a class has a method with a signature identical to that of a superclass, the JVM will make sure that the method is overridden. In other words, even if the Kotlin compiler just completely ignored the fact that Iterablehad a spliterator method and just compiled it as if it was a new, non-overriding method, JVM would still make sure that the method is overriden at runtime. However, JVM only does that for methods with the same signature (after erasing generic types). JVM does not directly support variant overrides, such as where the overriding method specifies a subclass as its return type. Instead, this is implemented by the compiler, which generates a synthetic bridge method with the same signature as that of the superclass (both the Java and the Kotlin compiler do that). For example, the above code is compiled to something like the following:

public final class Container implements Iterable {
   private final Object element;

   public final CustomSpliterator spliterator() {
      return new CustomSpliterator();
   }

   // $FF: synthetic method
   // $FF: bridge method
   public Spliterator spliterator() {
      return (Spliterator)this.spliterator();
   }

   /* Other methods omitted */
}

The fact that the Kotlin compiler generates the above code indicates that the compiler is aware that it is overriding a method, even though the override keyword was omitted.

I might be wrong here, but I think this is a special case when overriding methods that are hidden by the kotlin type of a java type, eg. the spliterator function of Iterable or finalize in Any/java.lang.Object(and I know you shouldn’t use it anyways).
All those functions are not technically part of the kotlin standard lib objects, but of cause they have to exist on the JVM. So you can override them simply by matching signature. I can’t remember if this is documented somewhere, I don’t think this behaviour is documented.