Types Conversion

In this lesson, we’re gonna learn a bit more about type conversions.

And to be even more specific, this time we will cover:

  • how to convert between data types?
  • whether Kotlin supports implicit conversions,
  • what is the type promotion and demotion,
  • smart casts.


Implicit and Explicit Conversions

In Kotlin, type conversions need to be done explicitly, as Kotlin does not perform implicit conversions between different types.

This design aims to prevent potential errors and increase code safety.

fun main() {
  val someDouble: Double = 1   // Invalid
  val someDouble: Double = 1.0 // Valid

  val someInt: Int = 1L // Invalid
  val someInt: Int = 1  // Valid
}


How to Convert Between Data Types

If we would like to perform an implicit conversion between different types, then we can simply use helper functions: 

  • toInt(),
  • toDouble(),
  • toFloat(),
  • toLong(),
  • toShort() ,
  • toByte()


Exercise 1

  1. Please use appropriate helper functions to fix the following code snippet.

fun main() {
  val someByte: Byte = 34L.toByte()
  val someInt: Int = 11L.toInt()
  val someLong: Long = (11.2).toLong()
}

As we can see, thanks to helper functions we can explicitly specify the type we would like to have.

And although parenthesis around value 11.2 are not necessary, it’s better to keep them for better readability.


Type Promotion and Demotion

Although Kotlin creators did their best to reduce the number of possible errors, we have to remember about type promotion and demotion. 

Type promotion is the process of converting a smaller data type into a larger one (for example, converting Int to Long), whereas type demotion is the opposite process. 

So, what will happen if we try to denote a number, which exceeds the range of a target type? Let’s say we would like to convert a number greater than 127 to Byte.

Let’s have an exercise then 🙂 


Exercise 2

  1. Please convert the byteValue to a Long and longValue to Byte. 
  2. Will the code compile? What are your thoughts? 

fun main() {
  val byteValue: Byte = 42
  val longValue: Long = 250L

  // Type promotion
  val promotedLong: Long = byteValue.toLong()
  println(promotedLong)

  // Type demotion
  val demotedByte: Byte = longValue.toByte()
  println(demotedByte) 
}

Well, the output of the above code will be 42 and -6. Quite unexpected, isn’t it?

Demoting types may lead to a loss of data if the original value is outside the range of the target type!

Please remember about that, because as you can see, it may lead to unexpected results.


Safe and Unsafe Cast Operator

Although when working with basic types, Kotlin provides helper functions for use, life isn’t that easy when working with classes and other types. We will learn more about them later in the course, but for now, let’s focus on the safe and unsafe cast operator: 

  • “unsafe” cast operator– with an as keyword (variableOne as String)- attempts to cast a variable to a specific type and if the cast is not successful, then a ClassCastException will be thrown, 
  • “safe” cast operator– with an as? keyword (variableOne as? String)- returns simply null, instead of throwing an exception. 


Exercise 3

  1. Please cast the value of intAsAny to Int and String types using both techniques. 
  2. What was the result?

fun main() {
  val intAsAny: Any = 42
  val anyAsInt = intAsAny as Int
  val anyAsStringOne = intAsAny as String  // Exception is thrown
  val anyAsStringTwo = intAsAny as? String // Null value is returned

  println(anyAsInt)
  println(anyAsStringOne)
  println(anyAsStringTwo)
}

As we might have expected, when trying to cast using the unsafe operator, an exception is thrown, because we specify the invalid type for an Int value.

To avoid exceptions, we can use the safe operator. But then, we will have to deal with a nullable String type.


Type Checks With 'is'

In Kotlin, the is keyword is used to check if a variable is of a specific type.

As a result, we simply get a Boolean value indicating whether a particular variable is of a given type, or not. 

Let’s take a quick look at the following exercise to put our knowledge into practice.


Exercise 4

  1. Please perform type checks to verify whether intAsAny value is of the Int type and whether it is of the String type. 

fun main() {
  val intAsAny: Any = 42
  val isInt = intAsAny is Int
  val isString = intAsAny is String

  println(isInt)
  println(isString)
}

We shouldn’t be surprised that the output of the program is true followed by false.

Great! So given what we’ve learned about thrown exceptions when a cast is not successful, we may think that it would be good to check our value before casting. 

It’s a great idea, but let’s learn about one, cool Kotlin feature first- smart casts.  


Smart Casts

Basically, with smart casts we do not have to explicitly perform a cast once we perform the is check. 

Let’s take a look at the example of a simple if statement (we haven’t discussed control flow yet, but to put it simply- code within the if block will execute only when the condition inside is true).

fun main() {
  val stringAsAny: Any = "Hello!"

  if(stringAsAny is String) {
    println(stringAsAny.length)
  }

  stringAsAny.length // Will not compile
}

As we can see, inside the if statement we can use the length property of a String value, which we cannot use on the Any type value.

Thanks to that we don’t have to perform a manual cast: 

if(stringAsAny is String) {
  val stringValue = stringAsAny as String
  println(stringValue.length)
}


Exercise 5

Please put the appropriate condition inside the if statement, which will check that the value is not a String. If the value will not fulfill the requirement, then the program will simply finish thanks to the return.

fun main() {
  val stringAsAny: Any = "Hello!"

  if (stringAsAny !is String) return

  println(stringAsAny.length)
}

As we can see, the compiler can track the is checks and perform a smart cast when it is 100% sure that the value is always String.


Summary Quiz


Complete and Continue  
Discussion

0 comments