Search code examples
kotlinvariable-assignmenttype-inferenceassignment-operatortype-mismatch

In Kotlin, what does "::class.simpleName" do?


val var1: Any = "Carmelo Anthony"

I'm under the impression ::class.simpleName returns the variable type of an object
when I do the following:

val var1Type = var1::class.simpleName
print(var1Type)

I get String and not Any

but when I do this

val var2: String = var1

I get a Type mismatch: inferred type is Any but String was expected


Solution

    • In Kotlin, the ::class operator exists in 2 forms:
    • In your case, var1 has a runtime tytpe of String but a static type of Any.
      • So var1::class returns the KClass for String, not Any.
    • But Kotlin's type system, like most statically typed languages, does not allow for implicit narrowing conversion (i.e. given a variable var2 typed as String, you cannot assign-to var2 from another variable (var3) statically-typed as Any, because var3 could have a runtime type that's completely incompatible with String, e.g. an InputStream object.
      • ...even if it's provable (by following the program by-hand) that the Any-typed value will always be a String.
      • Fortunately, however, Kotlin's type-checker is modern and its "Smart cast" feature follows the scope of type-narrowing when the is operator is used, which is neat (TypeScript has it too, I don't think any other language does though).
        • In situations where you can't use Smart-casts or can otherwise prove to yourself that a downcast is safe then use the as operator to perform an unsafe cast. Like so: var2: String = var1 as String.
          • (Somewhat confusingly, other languages use as as the operator for safe casts, argh).

    In context:

    fun main() {
    
        val var1: Any = "Carmelo Anthony"
        val var1Type = var1::class.simpleName
        println("var1's type: " + var1Type) // <-- This will print the *runtime type* of `var1` (String), not its static type (which is `Any`, *not* `String`).
    
        /*
        val var2: String = var1 // <-- Fails beause `var1` is `Any`, and `Any` is "wider" than `String`, and narrowing conversions always considered unsafe in languages like Kotlin, Java, etc.
        */
        val var2Unsafe: String  = var1 as  String; // <-- Doing this is unsafe because it will throw if `var1` is not a String.
        val var2Safe  : String? = var1 as? String; // <-- Doing this is safe because it `var2Safe` will be null if `var1` is not a String.
        
        println(var2Unsafe)
        println(var2Safe)
    }
    

    If you're familiar with other languages, then here's an incomplete table of equivalent operations and their syntax:

    Kotlin Java JavaScript C# C++
    Get static type TypeName::class TypeName.class ConstructorName typeof(TypeName) typeid(TypeName)
    Get runtime type variableName::class variableName.getClass() typeof variableName (intrinsics) variableName.constructor (objects) variableName.GetType() typeid(variableName)
    Get type from name (string) Class.forName( typeName ).kotlin Class.forName( typeName ) eval( typeName ) (never do this)
    Statically-defined runtime type check variableName is TypeName variableName instanceof TypeName typeof variableName === 'typeName' (intrinsics) or variableName instanceof ConstructorName (objects) variableName is TypeName
    Runtime dynamic type check otherKClass.isInstance( variableName ) or otherKType.isSubtypeOf() otherClass.isAssignableFrom( variableName.getClass() ) otherType.IsAssignableFrom( variableName.GetType() )
    Unsafe narrowing (aka downcast) val n: NarrowType = widerVar as NarrowType; NarrowType n = (NarrowType)widerVar; variableName as TypeName (TypeScript only) NarrowType n = (NarrowType)widerVar;
    Safe narrowing (downcast or null) val n: NarrowType? = widerVar as? NarrowType; NarrowType n? = widerVar as NarrowType; dynamic_cast<NarrowType>( widerVar )
    Conditional narrowing in scope variableName is TypeName func(x: unknown): x is TypeName guard functions (TypeScript only) widerVar is TypeName n