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

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

In Kotlin, the ::class operator exists in 2 forms:
TypeName::class - which returns a KClass object for the static type TypeName.
variableName::class - which returns a KClass object corresponding to the runtime type of variableName, and not variableName's static type. (Kotlin calls this the "bound type" in their documentation).
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

How to skip specification of the generic type parameter in Kotlin?

This is the main body of my function
val client = ConnectionFactory.createClient() # <- Return lettice.io RedisClusterClient
val conn = client.connect()
val command = conn.sync()
var index: String? = null
index = readDataStructure(command, key)
This is my first try to define my readDataStructure function:
fun readDataStructure(command: RedisCommand, key: String): String {
...
kotlin complaints error: 3 type arguments expected for interface RedisCommand<K : Any!, V : Any!, T : Any!>
I want to be able to NOT specifying K, V and T because I am just writing a throwaway script.
Is there any Kotlin lang syntax and can allow me to just pass the command variable as is?
I suppose you are after:
fun readDataStructure(command: RedisCommand<*,*,*>, key: String): String {
?
From Kotlin docs https://kotlinlang.org/docs/tutorials/kotlin-for-py/generics.html:
If you don't have any idea (or don't care) what the generic type might be, you can use a star-projection:
fun printSize(items: List<*>) = println(items.size)
When using a generic type where you have star-projected one or more of its type parameters, you can:
Use any members that don't mention the star-projected type parameter(s) at all
Use any members that return the star-projected type parameter(s), but the return type will appear to be Any? (unless the type parameter is constrained, in which case you'll get the type mentioned in the constraint)
Not use any members that take a star-projected type as a parameter

Why "!!" is required when converting the readline string value into int

I am new to kotlin, and I have been doing research on the syntax of the language. It is to my understanding that in kotlin you can cast data types using integrated functions like :
.toInt()
converting 3.14 to an integer :
3.14.toInt()
since it is known that the readline() function returns a string i am not sure why this syntax is correct:
fun main() {
println("please enter a int:")
val num1 = readLine()!!.toInt()
println("one more")
val num2 = readLine()!!.toInt()
println("sum : ${num1 + num2}")
}
and this syntax is incorrect
fun main() {
println("please enter a int:")
val num1 = readLine().toInt()
println("one more")
val num2 = readLine().toInt()
println("sum : ${num1 + num2}")
}
returns the error:
Error:(5, 26) Kotlin: Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String
Just looking for a bit more of an explanation on casting and how the syntax differs when it comes to the readline() function and functions alike.
The method readLine() returns a String? - the question mark means it can either be null or a String. In Kotlin, you need to handle instances with nullable type with either ? or !! when you're invoking a method onto that instance.
The difference is that ? only proceeds when the instance is not null, and !! forces it to proceed. The latter may give you a NullPointerException.
For example:
val num1 = readLine()?.toInt()
// Here, num1 could either be a String or null
val num1 = readLine()!!.toInt()
// if it goes to this next line, num1 is not null. Otherwise throws NullPointerException
readLine() returns String? (nullable version of String?)
Function toInt() receives String (non-nullable type).
fun String.toInt(): Int // non-nullable
fun String?.toInt(): Int // nullable (call)
You must do some kind of a null check to be sure that toInt will called on a non-nullable object. The !! operator converts nullable String? type to non-nullable String.

Kotlin - Type of `if` and `when` Expressions

I understand that Kotlin is a statically-typed language, and all the types are defined at the compile time itself.
Here is a when expression that returns different types:
fun main(){
val x = readLine()?.toInt() ?: 0
val y = when(x){
1 -> 42
2 -> "Hello"
else -> 3.14F
}
println(y::class.java)
}
During runtime (Kotlin 1.3.41 on JVM 1.8) this is the output:
When x = 1, it prints class java.lang.Integer
When x = 2, it prints class java.lang.String
Otherwise, it prints class java.lang.Float
When does the compiler determine the type of y? Or, how does the compiler infers the type of y during compile-time?
Actually, the type of the when expression resolves to Any in this case, so the y variable can have any value. An IDE even warns you, that Conditional branch result of type X is implicitly cast to Any, at least Android Studio does, as well as Kotlin Playground.
The type of that variable for you is Any (as the smallest possible superclass for all that types), but underlying value is untouched.
What does it mean? You can safely access only properties that are common for all that types (so only properties available for Any type. And property ::class.java is available for all types.
See this example - I use some other types to good visualise what is it about.
abstract class FooGoo {
fun foogoo(): String = "foo goo"
}
class Foo: FooGoo() {
fun foo(): String = "foo foo"
}
class Goo: FooGoo() {
fun goo(): String = "goo goo"
}
class Moo {
fun moo(): String = "moo moo"
}
fun main(x: Int) {
val n = when (x) {
0 -> Foo()
1 -> Goo()
else -> throw IllegalStateException()
} // n is implicitly cast to FooGoo, as it's the closes superclass of both, Foo and Goo
// n now has only methods available for FooGoo, so, only `foogoo` can be called (and all methods for any)
val m = when (x) {
0 -> Foo()
1 -> Goo()
else -> Moo()
} // m is implicitly cast to Any, as there is no common supertype except Any
// m now has only methods available for Any() - but properties for that class are not changed
// so, `m::class.java` will return real type of that method.
println(m::class.java) // // Real type of m is not erased, we still can access it
if (m is FooGoo) {
m.foogoo() // After explicit cast we are able to use methods for that type.
}
}
During compile-time, the inferred type of y is Any which is the supertype of all types in Kotlin. During run-time, y can reference [literally] any type of object. The IDE generates a warning "Conditional branch result of type Int/String/Float is implicitly cast to Any".
In the example,
When x = 1, it refers to an object of type java.lang.Integer.
When x = 2, it refers to an object of type java.lang.String.
Otherwise, it refers to an object of type java.lang.Float.
Thanks Slaw for the quick explanation:
There's a difference between the declared type of a variable and the actual type of the object it references. It's no different than doing val x: Any = "Hello, Wold!";

Safe cast vs cast to nullable

What is the difference between
x as? String
and
x as String?
They both seem to produce a String? type. The Kotlin page doesn't answer it for me.
UPDATE:
To clarify, my question is:
What is the purpose of having an as? operator at all, since for any object x and for any type T, the expression x as? T can be (I think) rephrased as x as T? ?
The difference lies in when x is a different type:
val x: Int = 1
x as String? // Causes ClassCastException, cannot assign Int to String?
x as? String // Returns null, since x is not a String type.
as? is the safe type cast operator. This means if casting fails, it returns null instead of throwing an exception. The docs also state the returned type is a nullable type, even if you cast it as a non-null type. Which means:
fun <T> safeCast(t: T){
val res = t as? String //Type: String?
}
fun <T> unsafeCast(t: T){
val res = t as String? //Type: String?
}
fun test(){
safeCast(1234);//No exception, `res` is null
unsafeCast(null);//No exception, `res` is null
unsafeCast(1234);//throws a ClassCastException
}
The point of the safe cast operator is safe casting. In the above example, I used the original String example with integers as the type. unsafeCast on an Int of course throws an exception, because an Int is not a String. safeCast does not, but res ends up as null.
The main difference isn't the type, but how it handles the casting itself. variable as SomeClass? throws an exception on an incompatible type, where as variable as? SomeClass does not, and returns null instead.

Generalize method with nullable arguments and return type

I have a method that converts ByteArray? to base64 String? so that if argument was null output will be null as well. This is its implementation:
fun toBase64String(array: ByteArray?): String? = if(array == null) null else
Base64.getEncoder().encodeToString(array)
But when I pass in not nullable ByteArray method returns String? which is expected. Is there a way to make it generic so such use case will be possible:
val base64 = toBase64String(ByteArray(4))
where base64 will be of type String and not String? since argument was not nullable?
I just started to work with Kotlin and probably don't know language feature that can make this possible.
You can make two overloads, one for nullable ByteArray? and one for non-null ByteArray:
fun toBase64String(array: ByteArray): String =
Base64.getEncoder().encodeToString(array)
#JvmName("toBase64StringNullable")
fun toBase64String(array: ByteArray?): String? =
if (array == null) null else toBase64String(array)
We need #JvmName("...") to avoid the declaration clash in the bytecode.
Also, this allows to distinguish the functions in Java.
Usage:
val nonNullBytes: ByteArray = TODO()
val nonNullString = toBase64String(nonNullBytes) // the inferred type is String
val nullableBytes: ByteArray? = TODO()
val nullableString = toBase64String(nullableBytes) // the inferred type is String?
When the argument is of the non-null type ByteArray, the compiler will choose the overload that returns a non-null String.
Probably overloading methods is the best solution for your case, but for the sake of completeness here are two other ways to realise that using only one method (the nullable one):
Not-Null-Asserted operator:
val base64: String = toBase64String(ByteArray(4))!!
Evlis operator:
val base64: String = toBase64String(ByteArray(4)) ?: "defaultString"
if argument was null output will be null as well
If that is the only thing the function does when it encounters null argument, it's better to declare it accepting non-null values and use safe call to deal with nulls:
fun toBase64String(array: ByteArray): String =
Base64.getEncoder().encodeToString(array)
val bytes: ByteArray? = ...
val base64 = bytes?.let { toBase64String(it) }
// the same can be written with function reference instead of lambda
val base64 = bytes?.let(::toBase64String)
Here let function is called only when bytes is not null, otherwise the result of the expression is null. When called it invokes the lambda function or the function reference specified as its argument, passing ByteArray which is already checked to be non-null to that function.
Also it can be more convenient to declare toBase64String as an extension for ByteArray, so it can be invoked with safe call without the helper function let"
fun ByteArray.toBase64String(): String =
Base64.getEncoder().encodeToString(this)
val bytes: ByteArray? = ...
val base64 = bytes?.toBase64String()