An introduction to Kotlin type system

An introduction to Kotlin type system

and more

Kotlin is a ..., statically typed,

..., programming language.

-wikipedia

Kotlin is a ..., statically typed,

..., programming language.

What is a type?

What is a type?

What is a type?

What is a class?

What is a type?

What is a class?

What is an object?

Object vs Class

Object vs Class

Class is a blueprint or template from which objects are created.
Object is an instance of a class.

Object vs Class

Class is a blueprint or template from which objects are created.
Object is an instance of a class.

class A

Object vs Class

Class is a blueprint or template from which objects are created.
Object is an instance of a class.

class A
val x = A()

Class vs Type

A type summarizes the common features of a set of objects with the same characteristics. We may say that a type is an abstract interface that specifies how an object can be used.

Class vs Type

A type summarizes the common features of a set of objects with the same characteristics. We may say that a type is an abstract interface that specifies how an object can be used.

A class represents an implementation of the type. It is a concrete data structure and collection of methods.

A data type (or simply type) is a collection or grouping of data values, usually specified by a set of possible values, a set of allowed operations on these values, ... A data type specification in a program constrains the possible values that an expression, such as a variable or a function call, might take.

-wikipedia

We can define our types by defining classes, interfaces, Enums, ...

Every classifier, i.e. classes and interfaces, generates a set of types.

Every classifier, i.e. classes and interfaces, generates a set of types. In most cases, it generates a single type, but an example where a single class generates multiple types is a generic class:

Every classifier, i.e. classes and interfaces, generates a set of types. In most cases, it generates a single type, but an example where a single class generates multiple types is a generic class:

class Box<T>

We can say that in the above snippet we see definition of class A, but the type of x is A. Therefore A is both a name of the class and of the type. The class is a template for an object but concrete object have a type. Actually, every expression has a type!

class A
val x = A()

We can say that in the above snippet we see definition of class A, but the type of x is A. Therefore A is both a name of the class and of the type. The class is a template for an object but concrete object have a type. Actually, every expression has a type!

class A
val x = A()

Statement vs Expression

Statement vs Expression

An expression in a programming language is a combination of one or more explicit values, constants, variables, operators and functions that the programming language interprets and computes to produce another value.

An expression has a value, which can be used as part of another expression.

Statement vs Expression

In computer programming, a statement is the smallest standalone element of an imperative programming language that expresses some action to be carried out.

Java type system

Java type system

Object

Java type system

Object

Number

MyClass

String

Java type system

Object

Number

MyClass

String

Integer

MySubClass

Kotlin type system

Any

Kotlin type system

Any

Number

MyClass

String

Kotlin type system

Any

Number

MyClass

String

Int

MySubClass

Kotlin type system

Any

Number

MyClass

String

Int

MySubClass

Nothing

Kotlin type system

Any

Number

MyClass

String

Int

MySubClass

Nothing

Object

=

Kotlin type system

Any

Number

MyClass

String

Int

MySubClass

Nothing

Object

=

?

Kotlin type system

Any

Number

MyClass

String

Int

MySubClass

Nothing

Object

=

?

Object test = null

Kotlin type system

Any

Number

MyClass

String

Int

MySubClass

Nothing

Object

=

?

Object test = null
val test: Any = null

Kotlin type system

Any

Number

MyClass

String

Int

MySubClass

Nothing

Any?

subtyping by

Nullablitiy

Kotlin type system

Any

Number

MyClass

String

Int

MySubClass

Nothing

Any?

Number?

MyClass?

String?

Int?

MySubClass?

Nothing?

Kotlin type system

Kotlin type system

When we define a class

class MyClass

We automatically have 2 types available

Kotlin type system

When we define a class

class MyClass

We automatically have 2 types available

val a: MyClass

val b: MyClass?

Kotlin type system

A type without a question mark denotes that variables of this type can’t store null references. This means all regular types are non-nullable by default, unless explicitly marked as nullable.

Kotlin type system

Once you have a value of nullable type, the set of operations you can perform on it is restricted. For example, you can no longer call methods on it. The compiler will now complain about the call to length in the function body:

fun main() {
 val x: String? = null
 var y: String = x
 // ERROR: Type mismatch:
 // inferred type is String? but String was expected
}

Kotlin type system

fun strLen(s: String?) = s.length

Kotlin type system

fun strLen(s: String?) = s.length

Error: Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String?

Kotlin type system

fun strLenSafe(s: String?): Int =
 if (s != null) s.length else 0

Kotlin type system

fun strLenSafe(s: String?): Int =
 if (s != null) s.length else 0

Once you perform the comparison, the compiler remembers that and treats the value as non-nullable in the scope where the check has been performed.

Kotlin type system

Note: At runtime, objects of nullable types and objects of non-nullable types are treated the same: A nullable type isn't a wrapper for a non-nullable type. All checks are performed at compile time. That means there's almost no runtime overhead for working with nullable types in Kotlin.

Kotlin type system

safe-call operator

Kotlin type system

Elvis operator

val x =

Kotlin type system

 safe-cast operator

Kotlin type system

non-null assertion operator

Kotlin type system

Kotlin type system

Kotlin type system

kotlin.Any

Kotlin type system

Besides being the unified supertype of all non-nullable types, kotlin.Any⁡ must also provide the following methods.

kotlin.Any

Kotlin type system

Besides being the unified supertype of all non-nullable types, kotlin.Any⁡ must also provide the following methods.

public open operator fun equals(other: Any?): Boolean
public open fun hashCode(): Int
public open fun toString(): String

kotlin.Any

Kotlin type system

kotlin.Nothing

Kotlin type system

kotlin.Nothing is an uninhabited type, which means the evaluation of an expression with kotlin.Nothing⁡ type can never complete normally. Therefore, it is used to mark special situations, such as

  • non-terminating expressions
  • exceptional control flow
  • control flow transfer

kotlin.Nothing

Kotlin type system

kotlin.Nothing

fun fail(message: String): Nothing {
	throw IllegalStateException(message)
}

val address = company.address ?: fail("No address")

println(address.city)

Kotlin type system

kotlin.Unit is a unit type, i.e., a type with only one value; all values of type kotlin.Unit should reference the same underlying kotlin.Unit⁡ object. It is somewhat similar in purpose to void return type in other programming languages in that it signifies an absence of a value (i.e. the returned type for a function returning nothing), but is different in that there is, in fact, a single value of this type.

kotlin.Unit

Kotlin type system

kotlin.Unit

public object Unit {
    override fun toString() = "kotlin.Unit"
}

Kotlin type system

Where is null on the hierarchy?

When we declare a property without explicitly indicate its type and assigning null as its value, its type will be determined as Nothing? that’s because this is the data type of null. Null is the only possible instance of Nothing?, there is not an object which type is specifically Nothing?

Kotlin type system

Where is null on the hierarchy?

At code level we can be totally sure that when we see a property which type is Nothing? the only possible value it can keep is null. Also, since Nothing is the subtype of all types, null can be used as the nullable version of any type we declare.

Kotlin type system

Where is null on the hierarchy?

fun print(value: Any?) = println(
    when (value) {
        is String -> "value is string"
        is Int -> "value is int"
        else -> "value unknown"
    }
)

Kotlin type system

Where is null on the hierarchy?

fun print(value: Any?) = println(
    when (value) {
        is String -> "value is string"
        is Int -> "value is int"
        is null -> "value is null"
        else -> "value unknown"
    }
)

Kotlin type system

Where is null on the hierarchy?

fun print(value: Any?) = println(
    when (value) {
        is String -> "value is string"
        is Int -> "value is int"
        is null -> "value is null"
        else -> "value unknown"
    }
)

error: type expected

Kotlin type system

Where is null on the hierarchy?

fun print(value: Any?) = println(
    when (value) {
        is String -> "value is string"
        is Int -> "value is int"
        is Nothing? -> "value is null"
        else -> "value unknown"
    }
)

Kotlin type system

Generic types are, by default, nullable

fun <T> printer(value: T) {
   print("Printing the value: "+ value)
}

printer("Hola")
printer(1)
printer(1.1)
printer(null)

Kotlin type system

Generic types are, by default, nullable

If we don’t want to allow null values we need to modify our example as below:

fun <T: Any> printer(value: T) {
   print("Printing the value: "+ value)
}
data class User (
    val firstName: String?,
    val lastName: String?
)

fun hasValidData(user: User): Boolean{
    val firstName = user.firstName ?: return false
    val lastName = user.lastName ?: return false
    return firstName.isNotBlank() && lastName.isNotBlank()
}

Kotlin type system

data class User (
    val firstName: String?,
    val lastName: String?
)

fun hasValidData(user: User): Boolean{
    val firstName = user.firstName ?: return false
    val lastName = user.lastName ?: return false
    return firstName.isNotBlank() && lastName.isNotBlank()
}

What is the inferred type?

Kotlin type system

data class User (
    val firstName: String?,
    val lastName: String?
)

fun hasValidData(user: User): Boolean{
    val firstName: String = user.firstName ?: return false
    val lastName = user.lastName ?: return false
    return firstName.isNotBlank() && lastName.isNotBlank()
}

What is the inferred type?

Kotlin type system

data class User (
    val firstName: String?,
    val lastName: String?
)

fun hasValidData(user: User): Boolean{
    val firstName: String = user.firstName ?: return false
    val lastName = user.lastName ?: return false
    return firstName.isNotBlank() && lastName.isNotBlank()
}

Why is it possible to assign return to a varialbe?

Kotlin type system

In Kotlin almost everything is an expression.

val name = when (number) {
        1 -> "one"
        2 -> "two"
    }

Kotlin type system

In Kotlin almost everything is an expression.

val name = when (number) {
        1 -> "one"
        2 -> "two"
    }
val message =  if (number % 2) "even" else "odd"

Kotlin type system

In Kotlin almost everything is an expression.

val function = fun (param: String){
 //do stuff
}
val name = when (number) {
        1 -> "one"
        2 -> "two"
    }
val message =  if (number % 2) "even" else "odd"

Kotlin type system

data class User (
    val firstName: String?,
    val lastName: String?
)

fun hasValidData(user: User): Boolean{
    val firstName: String = user.firstName ?: return false
    val lastName = user.lastName ?: return false
    return firstName.isNotBlank() && lastName.isNotBlank()
}

Why is it possible to assign return to a varialbe?

Kotlin type system

data class User (
    val firstName: String?,
    val lastName: String?
)

fun hasValidData(user: User): Boolean{
    val firstName: String = user.firstName ?: return false
    val lastName = user.lastName ?: return false
    return firstName.isNotBlank() && lastName.isNotBlank()
}

Kotlin type system

returns Nothing

Why is it possible to assign return to a varialbe?

data class User (
    val firstName: String?,
    val lastName: String?
)

fun hasValidData(user: User): Boolean{
    val firstName: String = user.firstName ?: return false
    val lastName = user.lastName ?: return false
    return firstName.isNotBlank() && lastName.isNotBlank()
}

Because Nothing is subtype of everything

Kotlin type system

Liskov Substitution Principle

If S is subtype of T, then objects of type T may be replaced with objects of type S without altering any of the desirable properties of that program.

data class User (
    val firstName: String?,
    val lastName: String?
)

fun hasValidData(user: User): Boolean{
    val firstName: String = return false
    val lastName = user.lastName ?: return false
    return firstName.isNotBlank() && lastName.isNotBlank()
}

Kotlin type system

data class User (
    val firstName: String?,
    val lastName: String?
)

fun hasValidData(user: User): Boolean{
    val firstName: String = return false
    val lastName = user.lastName ?: return false
    return firstName.isNotBlank() && lastName.isNotBlank()
}

possible but useless

Kotlin type system

data class User (
    val firstName: String?,
    val lastName: String?
)

fun hasValidData(user: User): Boolean{
    val firstName: String = return false
    val lastName = user.lastName ?: return false
    return firstName.isNotBlank() && lastName.isNotBlank()
}

possible but useless

Kotlin type system

Warning: Unreachable code

data class User (
    val firstName: String?,
    val lastName: String?
)

fun hasValidData(user: User): Boolean{
    val firstName = user.firstName ?: throw RunTimeException()
    val lastName = user.lastName ?: return false
    return firstName.isNotBlank() && lastName.isNotBlank()
}

Throw returns Nothing too.

Kotlin type system

fun test(): String?{
    TODO()
    return null
}

Warning: Unreachable code

public inline fun TODO(): Nothing = throw NotImplementedError()

Kotlin type system

fun test(): String?{
    TODO()
    return null
}

Warning: Unreachable code

The compiler knows after Nothing is returned, the execution flow stops.

public inline fun TODO(): Nothing = throw NotImplementedError()

Kotlin type system

So this will actually never be assigned,

because the function ends with the return statement.

data class User (
    val firstName: String?,
    val lastName: String?
)

fun hasValidData(user: User): Boolean{
    val firstName: String = user.firstName ?: return false
    val lastName = user.lastName ?: return false
    return firstName.isNotBlank() && lastName.isNotBlank()
}

Kotlin type system

All Kotlin functions calls are expressions, because they return at least Unit. Calls of Java functions that do not defined any return type are not expressions.

Kotlin vs Java

Kotlin value assignment (a = 1) is not an expression in Kotlin, while it is in Java because over there it returns assigned value (in Java you can do

a = b = 2 

or

a = 2 * (b = 3))

Kotlin vs Java

This helps avoid confusion between comparisons and assignments, which is a common source of mistakes in languages that treat them as expressions, such as Java or C/C++.

Kotlin vs Java

All usages of control structures (if, switch) in Java are not expressions, while Kotlin allowed if, when and try to return values:

val bigger = if(a > b) a else b

val color = when {
    relax -> GREEN
    studyTime -> YELLOW
    else -> BLUE
}

val object = try {
    gson.fromJson(json)
} catch (e: Throwable) {
    null
}

Kotlin vs Java

Kotlin vs Java

In java primitive types aren’t part of the hierarchy.

Kotlin vs Java

In java primitive types aren’t part of the hierarchy.

That means you have to use wrapper types such as java.lang.Integer to represent a primitive type value when Object is required. In Kotlin, Any is a supertype of all types, including the primitive types such as Int.

Kotlin vs Java

All Kotlin classes have the following three methods: toString, equals, and hashCode. These methods are inherited from Any. Other methods defined on java.lang.Object (such as wait and notify) aren’t available on Any, but you can call them if you manually cast the value to java.lang.Object.

Kotlin vs Java

What distinguishes Kotlin’s Unit from Java’s void? Unit is a full-fledged type, and, unlike void, it can be used as a type argument. Only one value of this type exists; it’s also called Unit and is returned implicitly.

Kotlin vs Java

This is useful when you override a function that returns a generic parameter and make it return a value of the Unit type.

Kotlin vs Java

This is useful when you override a function that returns a generic parameter and make it return a value of the Unit type.

interface Processor{
	fun process(): T
}

class NoResultProcessor: Processor{
	override fun process(){
    	// do stuff
    }
}

Kotlin vs Java

Kotlin doesn’t automatically convert numbers from one type to another, even when the type you’re assigning your value to is larger and could comfortably hold the value you’re trying to assign.

Kotlin vs Java

Kotlin doesn’t automatically convert numbers from one type to another, even when the type you’re assigning your value to is larger and could comfortably hold the value you’re trying to assign.

byte b = 4;
short s = b;
int x = s;
long l = x;
float f = l;
double y = f;

Kotlin vs Java

Kotlin doesn’t automatically convert numbers from one type to another, even when the type you’re assigning your value to is larger and could comfortably hold the value you’re trying to assign.

byte b = 4;
short s = b;
int x = s;
long l = x;
float f = l;
double y = f;
val i = 1
val l: Long = i // Error: type mismatch

Integer type widening

Integer type widening

fun foo(value: Int) = 1
fun foo(value: Short) = 2

Integer type widening

fun foo(value: Int) = 1
fun foo(value: Short) = 2
foo(2)

Integer type widening

fun foo(value: Long) = 1
fun foo(value: Short) = 2
foo(2)

Integer type widening

fun foo(value: Long) = 1
fun foo(value: Short) = 2
foo(2)

Overload resolution ambiguity. All these functions match ...

Integer type widening

fun foo(value: Long) = 1
foo(2)

What I did not cover:
- Platform types

- Covariance and contravariance

- Unsigned number types

What I did not cover:
- Platform types

- Covariance and contravariance

- Unsigned number types

- Union types with errors

https://www.linkedin.com/in/ali-ahmadabadiha-59690923b/

resources:

https://www.linkslist.app/Xhs6khK

Video of this presentation is available at:
https://www.youtube.com/watch?v=5NoPe1__OcA

Code

By Ali Ahmadabadiha