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:TypeName::class
- which returns aKClass
object for the static typeTypeName
.variableName::class
- which returns aKClass
object corresponding to the runtime type ofvariableName
, and notvariableName
's static type. (Kotlin calls this the "bound type" in their documentation).
- In your case,
var1
has a runtime tytpe ofString
but a static type ofAny
.- So
var1::class
returns theKClass
forString
, notAny
.
- So
- But Kotlin's type system, like most statically typed languages, does not allow for implicit narrowing conversion (i.e. given a variable
var2
typed asString
, you cannot assign-tovar2
from another variable (var3
) statically-typed asAny
, becausevar3
could have a runtime type that's completely incompatible withString
, e.g. anInputStream
object.- ...even if it's provable (by following the program by-hand) that the
Any
-typed value will always be aString
. - 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).
- (Somewhat confusingly, other languages use
- In situations where you can't use Smart-casts or can otherwise prove to yourself that a downcast is safe then use the
- ...even if it's provable (by following the program by-hand) that the
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 |