Home > Software design >  In Kotlin, what does "::class.simpleName" do?
In Kotlin, what does "::class.simpleName" do?

Time:03-10

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

CodePudding user response:

  • 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

  • Related