Confused about mapped types

I am very confused about mapped types. Also see this issue I have reported.

/**
 * This demo shows that Kotlin types and their mapped Java types are paradoxically the same and distinct
 * at the same time - at compile time.
 */
fun demo() {
    val ks: kotlin.String = ""
    val js: java.lang.String = "" as java.lang.String

    val kc: kotlin.collections.Collection<Int> = listOf()
    val jc: java.util.Collection<Int> = listOf<Int>() as java.util.Collection<Int>

    // Kotlin type cannot access Java method java.lang.String.getBytes() (OK)
    js.getBytes() // Works (OK)
    js.bytes // (Property access syntax) Works (OK)
    ks.getBytes() // Compile error: "Unresolved reference: getBytes" (OK?)

    // Java type can access Java method java.lang.String.length() without parentheses. (STRANGE)
    // Or is it the property kotlin.String.length and IntelliJ is wrong?
    ks.length // kotlin.String.length - Works (OK)
    js.length // java.lang.String.length() - Works (STRANGE)
    // These do not exist, so the above problems have nothing to do with property access syntax:
    ks.getLength() // Compile error: "Unresolved reference getLength()"
    js.getLength() // Compile error: "Unresolved reference getLength()"

    // Java type can access Kotlin extension property kotlin.CharSequence.indices (STRANGE)
    ks.indices // Works (OK)
    js.indices // Works (STRANGE)

    // Kotlin type can access Java default method java.util.Collection.parallelStream() (STRANGE)
    jc.parallelStream() // Works (OK)
    kc.parallelStream() // Works (STRANGE)

    // =========================================================================================================
    // kotlin.String::class === java.lang.String::class

    assert(kotlin.String::class === java.lang.String::class) // Works (STRANGE)

    ks.getBytes() // But then this should work too, but it doesn't.

    js.length // It would explain why this works, but CTRL-Click then should navigate to kotlin.String.length, not java.lang.String.length().

    assert(kotlin.String::class.isSubclassOf(kotlin.CharSequence::class)) // Of course this works...
    assert(java.lang.String::class.isSubclassOf(kotlin.CharSequence::class)) // so this works too...
    js.indices // ... and explains why this works.

    // =========================================================================================================
    // kotlin.collections.Collection::class === java.util.Collection::class

    assert(kotlin.collections.Collection::class === java.util.Collection::class) // Works (STRANGE)
    kc.parallelStream() // But explains why this works.
}

Can someone please explain this to me?

As I get it on the JVM kotlin string is just compiled to Java’s String, same goes for Collection.
Hence what works for one works for the other.

The only thing which seems off in your examples is ks.getBytes() which I believe to be a bug.

The .length on String is a property of kotlin strings which then “exists” for Java string since they’re the same class. I imagine this is because it’s not called .getLength in Java as one would expect, so this “corrects” a minor nit in Java style.

If I may, what problem are you trying to solve here investigating these behaviors? You should only use kotlin types in kt code.

2 Likes

Kotlin types such as List, MutableList, String, CharSequence etc. are all compiled to their java equivalents, and thus any runtime checks will not be able to distinguish between them. At compile-time, however, they are distinct types with different sets of members. In particular, the Kotlin types do not have all members that the corresponding Java types have. They have those listed in the Kotlin std lib reference, as well as a few extra JVM specific ones (such as Collection.stream()). I have not been able to find any documentation on which JVM specific members are included.

In addition, when working with a class C written in Java, any occurrence of a mapped class within C will appear as the Kotlin version of that class. This, among others, includes base classes and interfaces. This also holds true when C is a class built into Java, and even holds true when C itself is one of the mapped classes. This means, for instance, that java.lang.String is considered a subclass of kotlin.CharSequence. Thus, it supports all operations also supported by kotlin.CharSequence such length (as a property, not just a method).

1 Like

I am not trying to solve a specific problem. I am just trying to understand how mapped types work. I CTRL-clicked parallelStream() and was wondering why it was there for a Kotlin collection.