Instruction 1
Declared and Inferred Kotlin Types
During program execution, a variable could be an Int or a String. Kotlin is a statically typed language. This means the type of a variable or object remains fixed throughout the duration of a program.
You don’t have to be explicit when defining the type for a variable in Kotlin. In other programming languages, a variable can take on many different types. The only reason you’re able to leave out the type information is because Kotlin infers the type for you. This is known as an Inferred type. Kotlin always chooses the best type that fits your variable. It bases its choice on the variable’s initialization code.
Inferred types are less verbose and could lead to a cleaner code in that sense. But at times, Kotlin may assign a type that doesn’t quite fit what you’re looking for. Or you may choose to be explicit for readability or documentation purposes. In this case, you’ll have to specify a type explicitly.
When you assign some data like a whole number to a variable, its type is automatically an Int.
val amount = 200
This syntax is common to a lot of dynamic programming styles for initializing a variable. But Kotlin isn’t a dynamic programming language. It’s static. So, a variable will always have one type throughout the duration of the program execution.
What then is the type of amount, and how does Kotlin know that?
Visit Kotlin Playground to open a new Kotlin session. Use the following Int initialization syntax to define a variable and print out its type:
fun main() {
val amount = 200
println(amount::class.simpleName)
}
Here’s the output:
Int
Kotlin analyzed the variable’s initialization amount = 200, and assigned it a type of Int. This is how type inference works. Type inference is cool because it means Kotlin is able to make smart guesses for you.
If you wanted to specify amount as a float, add ‘f’:
fun main() {
val amount = 200f
println(amount::class.simpleName)
}
Here’s the output:
Float
What if you wanted a double instead?
fun main() {
val amount = 200.0
println(amount::class.simpleName)
}
You’ll see the following response in the Output window:
Double
Type inference is efficient. But, if you need to explicitly specify the type, you only need to follow the name of the variable with a colon and the type:
fun main() {
val amount: Float = 200f
println(amount::class.simpleName)
}
Here’s the output:
Float
But, the specified type and the initialization data will have to match. If you change Float to Double and keep the f at the end, you get an error:
fun main() {
val amount: Double = 200f
println(amount::class.simpleName)
}
Here’s the error response:
The floating-point literal does not conform to the expected type Double
Now use String initialization syntax to define a variable and print out its type:
fun main() {
val name = "Bob"
println(name::class.simpleName) // Prints the type or class representing the type for the object
}
Here’s the output:
String
The console displays String without specifying a type. The variable is automatically assigned the String type. To specify a type of String, add a colon after the name of the variable, then add the name of the type or the class:
fun main() {
val name: String = "Bob"
println(name::class.simpleName) // Prints the type or class representing the type for the object
}
You’ll see the following response in the Output window:
String
Checking Types
There could be times when you’re unsure of the type of data you’re working with. Kotlin has a type called Any from which all other classes inherit. This means Any is the parent of every non-nullable object in Kotlin. If, at any point in your code, you have an Any type, you can use the is operator to check if an object is some other type. This expression will result in a Boolean - either true or false.
Initialize a String as an Any type in your code to simulate an instance where you don’t know the type of an object:
fun main() {
val name: Any = "Bob"
val isString = name is String
println("name is String: " + isString)
}
You’ll see the following response in the Output window:
name is String: true
Note: To negate the operation, prepend
iswith an exclamation point, ‘!’, to haveval isString = name !is String
Every data type is an object in Kotlin. It has the object, Any, as its parent. Because of this, you can type-check for any object type. To type-check, use the equality operator == or the equals() function. Get the simple name of a variable and compare it with the name of the expected class:
fun main() {
val name: Any = "Bob"
val isString = name::class.simpleName == "String"
println("name is String: " + isString)
}
Note: As with
!is, to negate the equality operator, prepend==with ‘!’ to have!==
Using Smart Casts
Kotlin is a programming language that helps you write concise and clear code. It has a feature called smart cast. Smart cast allows you to cast a variable to a different type. If the conversion succeeds, execution continues with the variable. This means you don’t have to be verbose while writing code and can achieve more with less. Once converted, you can use any functions and operators available for that type. This feature is especially useful when you are working with variables of an unknown type.
In your code, assert that input is a String, then call the uppercase() function of the String class on the name.:
fun main() {
val input: Any = "Bob"
if (input is String){
print(input.uppercase()) // Calling String methods on the variable
}
}
Run the code, and it displays input in capital letters:
BOB
Now, use the same is operator to assert that input is an Int, then call the div method on it:
fun main() {
val input: Any = 10
if (input is Int){
print(input.div(2)) // Calling Int methods on the variable
}
}
Kotlin supports operations on immutable types. Immutability ensures that the type remains the same throughout the program’s execution. With mutable variables, a float could change to a double or even null during the program’s runtime. Functions would expect one type but would receive another type. For instance, an Int or a null object instead of a String would cause an error.
Hey, what’s the deal? I ordered a soda and got an ice skate! It can be that bad. :[
When you get any of the following errors, you’ll have to make sure to use a val to make your variable immutable before Kotlin’s smart cast feature can work:
Smart cast to 'String' is impossible, because 'name' is a local variable that is captured by a changing closure
Smart cast to 'String' is impossible, because 'name' is a mutable property that could have been changed by this time
So, if you see that a given variable is some other type, you can rely on Kotlin’s smart cast to use the variable. This allows you to go ahead and use the variable as the expected type.
Cast Operator
In the previous section, you used the is operator to check for a specific type. If the variable was the inquired type, Kotlin’s smart cast feature confirms its safe use. You can use the variable as expected. Kotlin did the declaration conversion (casting) for you. You can also do this casting yourself, but this cast uses the unsafe cast operator as.
In your code, cast the unknown type to an Int using the following code:
fun main() {
val input: Any = 2
println(input::class.simpleName) // Kotlin intelligently treats the input as an Int even though it's explicitly declared as an Any
val inputCast = input as Int
println(inputCast::class.simpleName) // You manually cast the variable as an Int
println(inputCast.minus(2)) // You call an Int method on the variable
}
You have to be sure about a type to be able to manually cast it, otherwise, you’d get an error. Change input to a String “2”, and re-run the program:
fun main() {
val input: Any = "2"
println(input::class.simpleName) // Kotlin intelligently treats the input as a String even though it's explicitly declared as an Any
val inputCast = input as Int
println(inputCast::class.simpleName) // You manually cast the variable as an Int when it's a String
println(inputCast.minus(2)) // You call an Int method on the variable
}
You get the following error:
String
Exception in thread "main" java.lang.ClassCastException: class java.lang.String cannot be cast to class java.lang.Integer (java.lang.String and java.lang.Integer are in module java.base of loader 'bootstrap')
at FileKt.main (File.kt:10)
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0 (:-2)
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke (:-1)
This type of casting is an unsafe cast. Instead, you can use a safe cast using the nullable type, as?. A nullable type makes sure that if the cast fails, the variable receives a null value. Kotlin will ensure that you take the necessary steps to handle this variable. The nullable type guards against accidental misuse during program execution. Here’s how to make the cast safe. Add ‘?’ to as. Add ‘?’ to inputCast for the minus call. A null object has no references to methods, so comment the inputCast::class.simpleName call:
fun main() {
val input: Any = "2"
println(input::class.simpleName) // Kotlin intelligently treats the input as a String even though it's explicitly declared as an Any
val inputCast = input as? Int
//println(inputCast::class.simpleName) // You manually cast the variable as an Int when it's a String
println(inputCast?.minus(2)) // You call an Int method on the variable
}
The output for the nullable type exercise:
String
null
It’s always best to depend on smart casting to confirm a type before using it. Don’t do manual casting unless you’re certain of the type of the variable.
You’ve been using Strings a lot in this course. It’s time you learned much more to understand what it is and the many ways you can use it. Keep reading to learn more about Strings.