# Exploring the Kotlin Type System and understanding Null Safety

Welcome to the fourth article in [this series](https://hardiksachan.hashnode.dev/series/android-zero-to-hero) on Android Development. In the [previous articles](https://hardiksachan.hashnode.dev/kotlin-polymorphism-and-inheritance-essential-concepts-for-android-developers), we covered some basics of Kotlin and OOPS. In this article, we're going to dive deep into Kotlin's type system and learn all about null safety.

First, let's define what a type system is. In programming, a type system is a set of rules that determine how data is stored, modified, and used. Coming to the second part of our article, we'll discuss why we need to be concerned with null safety. Accessing a type that can be null can result in a null reference exception, commonly known as a `NullPointerException`. According to a [survey](https://www.overops.com/blog/the-top-10-exceptions-types-in-production-java-applications-based-on-1b-events/), `NullPointerException` is the most common exception in java applications. But don't worry, Kotlin offers a solution for this.

The type system of Kotlin is designed to provide an effective way to manage null references. It makes a clear distinction between references that can hold null values (nullable references) and those that cannot (non-null references). Hence, the compiler can track values which can be null and warn you if you're trying to access nullable value, preventing an `NullPointerException` from happening.

With the introduction out of the way, let's get started!

## Basic Types

The data type is one of the most fundamental concepts in any programming language. Simply put, a type is a category of data that a variable or expression can hold. In Kotlin, several basic types are available to use when declaring variables and constants. These basic types include numbers, characters, and booleans. These different types are very neatly organized in a hierarchy that we'll take a look at after we understand nullable types.

### Numbers

Numbers in Kotlin come in two forms: Integers and Floating-point numbers. Integers are represented by types such as `Byte`, `Short`, `Int`, and `Long`. For example,

```kotlin
val age: Int = 25
val weight: Long = 150
```

Floating-point numbers include types such as Float and Double. For example,

```kotlin
val price: Double = 12.99
val temperature: Float = 32.5f
```

Also, you can use `_` to make increase the readability of code.

```kotlin
val largeNum: Long = 1_000_000_000
```

### Characters

Characters in Kotlin are represented by the `Char` type. They are enclosed in single quotes, for example:

```kotlin
val letter: Char = 'A'
```

### Booleans

Booleans in Kotlin is represented by the `Boolean` type. They can be either `true` or `false`. For example,

```kotlin
val isStudent: Boolean = true
val isTired: Boolean = false
```

In addition to these basic types, Kotlin also supports strings, arrays and enum types.

### Strings

Kotlin's string type is similar to the string type in most programming languages. It is used to represent a sequence of characters. For example,

```kotlin
val name: String = "John Doe"
```

### Arrays

Arrays in Kotlin are similar to arrays in most programming languages. They are used to store a fixed-size sequential collection of elements of the same type. For example,

```kotlin
val numbers: IntArray = intArrayOf(1,2,3,4,5)
val names: Array<String> = arrayOf("John", "Doe", "Jane", "Smith")
```

Let's also discuss three special types in Kotlin, `Any`, `Unit` and `Nothing`.

### Any

As I mentioned above, all types in Kotlin are organized into a hierarchy, `Any` is at the root of that hierarchy. For instance, `Int` and `Boolean` are subtype of `Any`. Also, all the classes that you create are subtypes of `Any`

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1673715969179/06472cf2-26c9-45db-a3f5-aaa15054fc1b.png align="center")

### Unit

`Unit` is a pre-defined type in Kotlin. It is a singleton, meaning there is only one instance of it. The unit type is used to represent the absence of a value. For example, if you have a function that does not need to return a value, you'd use `Unit` as its return type.

```kotlin
fun greet(): Unit {
    println("Hello, World!")
}
```

Also, if you don't specifically mention a return type for the function, it's assumed to return Unit. Hence, the above code can be rewritten as:

```kotlin
fun greet() {
   println("Hello, World!")
}
```

### Nothing

`Nothing` is a special type in Kotlin. It is at the very bottom of the type hierarchy. This means that Nothing is a subtype of every type. Also, the type `Nothing` can not be initialized and hence, can not have any instance. Please note the difference between `Unit` and `Nothing`. Evaluation of an expression type Unit results in the singleton value `Unit`. Evaluation of an expression of type Nothing never returns at all.

```kotlin
fun throwsException(): Nothing {
    throw Exception()
}
```

Please note that Kotlin is a type-safe language, which means that the compiler checks the types of variables and constants at *compile time* to ensure that they match the expected types.

## Null Safety

As we previously discussed, accessing a type that can be null can result in a null reference exception, commonly known as a `NullPointerException`. Let's see how we can deal with null references in Kotlin.

Kotlin's type system is designed in such a way that it distinguishes between variables that can hold a `null` value(nullable references) and those that cannot(non-nullable references).

For example, a regular variable of type `String` cannot hold `null`:

```kotlin
// Regular initialization means non-null by default
var a: String = "abc" 

a = null // compilation error
```

To declare a variable that can be set to `null`, append `?` at the end of the type. For example, a nullable String should be declared as `String?`

```kotlin
var b: String? = "abc" // can be set to null
b = null               // ok
```

In this way, if you call a method or access a property on `a`, it's guaranteed not to cause throw a `NullPointerException`, so it is safe to say:

```kotlin
val l = a.length
```

However, if you want to access the same property on `b`, that would not be safe and the compiler will report an error:

```kotlin
val l = b.length // error: variable 'b' can be null
```

But don't worry, there are ways to safely access properties on nullable variables, which we will discuss later in the article. But first, let's take a look at the type hierarchy in Kotlin.

## Type Hierarchy in Kotlin

As we discussed above, `Any` type is the supertype for all the types, hence it is placed at the "top" of the hierarchy

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1673716064486/1ea5944b-be9e-4296-8a25-86784a488d51.png align="center")

All the custom types (classes, interfaces, etc) that don't have an explicit type are also a sub-type of `Any` type. For instance,

```kotlin
class Box {
    fun render() {
        // function body
    }
}
```

The type `Box` is a subtype of `Any`.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1673716334502/fda42cfa-3fe3-46db-a95d-bab08259b018.png align="center")

Now, the types that inherit other types form a tree structure in the hierarchy. A type can have multiple parent types. For instance,

```kotlin
interface IClickable
interface IDragable

class Card: Box, IClickable, IDragable {
    // ...
}
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1673716866485/b8e643b9-73db-4358-916e-551f846c675e.png align="center")

Coming to the nullable references, each type has its nullable counterpart which is a supertype for the type. For example, `Unit` is a subtype of `Unit?` .

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1673717047737/ff7ec63e-9c6b-4449-886f-faf9088776ec.png align="center")

Expanding the complete graph to include nullable types results in the following

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1673717537473/d3958c4b-8c89-451e-a92a-42cd2d250264.png align="center")

Now, at the "bottom" of the type hierarchy is the `Nothing` type. So here's the complete type hierarchy in Kotlin.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1673717709431/039a229e-fc42-4172-a209-2db2571ab374.png align="center")

## Working with Nullable Types

As we saw that we cannot directly access the properties and methods of nullable types, hence it's a bit tricky to work with them. There are two very useful operators to work with nullable types - safe call operator `?` and Elvis operator `?:`.

Let's first see how the safe-call operator works. This operator returns null if the operand is null. Hence, it allows you to access properties and methods of nullable variables without the risk of Null Pointer Exception.

```kotlin
var name: String? = "John"
var firstChar = name?.get(0) // Evaluates to J

name = null
firstChar = name?.get(0) // Evaluates to null
```

In this example, if the `name` variable is `null`, `null` will be assigned to the `firstChar` variable.

Now, let's take a look at the Elvis operator (`?:`). It is similar to an if-else statement, like so:

```kotlin
val name: String? = "John"
val firstChar = name?.get(0) ?: '?'
```

This is the same as writing:

```kotlin
val name: String? = "John"
val firstChar = if(name != null) name.get(0) else '?'
```

One more way to handle nullable variables is the not-null assertion operator (`!!`). It is used to explicitly assert that a nullable type is not null, and this will throw a null pointer exception if it is. We can use this operator when we are sure that a nullable variable holds a non-null value. For example:

```kotlin
val name: String? = "John"
val upperCaseName = name!!.toUpperCase()
```

## Type checks and casts

### `is` operator

In Kotlin, we can check the type of an object using the `is` operator and its negated form `!is`. These operators perform a runtime check that identifies whether an object conforms to a given type.

For example, if we have an object `obj` and want to check if it's of type `String`, we can do the following:

```kotlin
if (obj is String) {
    print(obj.length)
}

// OR

if (obj !is String) { // same as !(obj is String)
    print("Not a String")
} else {
    print(obj.length)
}
```

### `as` and `as?`operator

Kotlin offers two types of cast operators that can be used to change the type of the object. The first is the unsafe cast operator, denoted by the keyword "as". This operator will throw an exception if the cast is not possible. For example, if we try to cast a variable of type `Any` to a `String` using the unsafe cast operator, the code will throw a `ClassCastException` if the variable is not a `String`.

```kotlin
val x: Any = "hello"
val y: String = x as String // This will work
val z: Any = 123
val a: String = z as String // This will throw a ClassCastException
```

The second type of cast operator is the safe cast operator, denoted by the keyword "as?". This operator will return `null` if the cast is not possible, instead of throwing an exception. This can be useful in situations where we are not sure if the variable is of the type we expect it to be.

```kotlin
val x: Any = "hello"
val y: String? = x as? String // This will return "hello"
val z: Any = 123
val a: String? = z as? String // This will return null
```

### Smart Cast in Kotlin

In addition to these explicit cast operators, Kotlin also has a feature called *smart casts*. Smart casts allow the compiler to automatically insert safe casts when necessary, without the need for explicit cast operators. The compiler can track the type of a variable and insert a cast when it is used in a context where a different type is expected.

For example, consider the following code:

```kotlin
if (x is String) {
    print(x.length) // x is automatically cast to String
}
```

Here, the compiler is smart enough to know that x will be a String in the body of if block, hence, it automatically casts the variable x to String.

However, it's important to note that smart casts work only when the compiler can guarantee that the variable won't change between the check and the usage. More specifically, smart casts can be used under the following conditions:

* `val` local variables - always, except local delegated properties
    
* `val` properties - if the property is private or internal or if the check is performed in the same module where the property is declared. Smart casts cannot be used on open properties or properties that have custom getters.
    
* `var` local variables - if the variable is not modified between the check and the usage, is not captured in a lambda that modifies it, and is not a local delegated property.
    
* `var` properties - never, because the variable can be modified at any time by other code.
    

## Conclusion

In conclusion, the Kotlin-type system is an important aspect of the language that provides many features to ensure safety and reliability in your code. We have discussed the basic types available in Kotlin, the concept of null safety, and the various type casts and checks that can be used to ensure that your code is working as expected.

It is important to understand how to use these features to prevent common errors such as null pointer exceptions and type mismatches. By making use of nullable types, the safe call operator, and the Elvis operator, you can write code that is safe from null references and is more readable.

We have also discussed how to use the `is` and `!is` operators and the safe and unsafe cast operators to perform runtime type checks and casts. Smart casts, which are automatically inserted by the compiler when necessary, provide an even more convenient way to handle types.

In the next article in this series, we will delve into data classes. Data classes are a special type of class in Kotlin that are designed to hold data. They are typically used to represent the data model in an app, such as a user or a product.

Keep Coding!
