heihei blog

Write once, recall anytime. 自分のために書く 📝

Notes - Kotlin Types: Exposed by Svetlana Isakova (KotlinConf 2017)

はじめに

Notes記事では、英語のセッション動画やポッドキャストの内容を(雑に)英語でメモに書き残すことを行っています。 本記事は、あくまで動画を見ながら、参考程度に読んでいただくことを想定しています。これから動画を見る方にとって本記事の内容が少しでもお役に立てば幸いです。(内容において不備、誤字脱字等ありましたらご連絡いただければと思います。)

本記事では、KotlinConf 2017 - Kotlin Types: Exposed by Svetlana Isakova - YouTubeの内容について雑にメモを書き残しました。

本記事で取り上げているセッションの動画はKotlin Types: Exposed by Svetlana Isakova (KotlinConf 2017)です:

www.youtube.com

Agenda

  • Basic Types
  • Nullable types
  • Collection types

How those types interact with Java and what happens when we mix types of Kotlin and Java

fun foo(): Int = 1

fun bar(): Int? = 1
public static final int foo() {
  return 1;
}

public static final Integer bar() {
  return Integer.of(1);
}

If you want to know what is going on under the food, you can "show kotlin bytecode" (and also decompile the byte code) in Android Studio

Correspondence between Kotlin and Java types

  • Int in Kotlin => int in Java
  • Int? in Kotlin => java.lang.Integer in Java

Kotlin does not have Primitive types in the language, yet Kotlin still has Primitive types in the byte code level by making use of wrapper types and nullable types

Generic arguments

  • List<Int> in Kotlin => List<Integer> in Java
  • Array<Int> in Kotlin => Integer[] in Java
Arrays of primiteve types

If you want to use primitive types of array, then use IntArray in Kotlin: - IntArray in Kotlin => int[] in Java

String

kotlin.String in Kotlin => java.lang.String in Java

kotlin.String hides some confusing methods:

e.g. replaceAll method

In Java:

"one.two.".replaceAll(".", "*") => *******

In Kotlin:

"one.two.".replace(".", "*") => one*two

"one.two.".replace(".".toRegex(), "*") => one*two

Any

  • Any in Kotlin => java.lang.Object in Java
  • Any type is the super type for ALL types, including Int or anything
Boxing under the hood
log(2017) // the value is autoboxed

fun log(any: Any) {
  println("Value: $any")
}

fun log(i: Int) {
  println("Value: $i")
}

Unit (Kotlin) vs Nothing (Kotlin) vs void (Java)?

The concept comes from functional programming; from the Type system

  • Unit: "a type that allows only one value and thus can hold no information" => The function completes
  • Nothing: "a type that has no values" => The function never completes
Unit instead of void

Whenever you use void in Java, you use Unit in Kotlin

Unit

  • No meaningful value is returned
  • No explicit returned type implies you return Unit by default
  • Two equivalent syntactic forms:
fun f() { /*…*/ }

fun f(): Unit { /*…*/ }

Under the hood, Unit is still void in the byte code

Nothing is different to Unit/void

Unit and Nothing are two very different types even though in the byte code level both mean void type

  • Nothing means "this function never returns"
Any & Nothing types
  • Any is a super type for all the other types
  • Nothing is a subtype for all the other types

e.g. fail function below returns Unit

val answer = if (timeHasPassed()) {
  42 
} else {
  fail("Not ready")
}

fun fail(message: String) {
  throw IllegalStateException(message)
}
  • If 42 is returned then the returned type of the expression function is Int
  • If fail function is called then the returned type of the expression function is Unit

=> Kotlin compiler thinks that the expression function returns Any type because the super type of both Int and Unit is Any

Returning Any means the variable answer will be initialized even if it fails, which is not what we expect; we expect it not to be initialized when it fails. => We can use Nothing in this case

Nothing is a subtype for all the other types, so that means the returned type of the expression function is now Int, which is what we expect

Type of null

var user = null // => var user: Nothing? = null
user = User("svtk") // Error: Type mismatch

val users = mutableListOf(null) // => var users: List<Nothing?> = mutableListOf(null)
users.add(User("svtk")) // Error: Type mismatch

Nullable types & Java

Nullable Types Under the Hood => Just annotations

If your returned type is annotated with @Nullable in Java, then in Kotlin, the type will become nullable

How to still prevent NPEs?

  • Annotate your Java types
    • @Nullable type
    • @NotNull type

Non-null by default (JSR-305):

@javax.annotation.Nonnull
@TypeQualifierDefault(ElementType.PARAMETER, …)
annotation class MyNonnullByDefault

package-info.java:

  @MyNonnullByDefault
  package mypackage;

Then, Kotlin will give you warning when assigning null to values:

@MyNonnullByDefault
public class Session {
  public void setDescription(String description) {
    this.description = description;
  }
}

Calling Java code from Kotlin:

val session = Session()
session.setDescription(null) // => Warning: Expected type doesn't accept nulls in Java …

If you prefer errors in compile time rather than warnings:

build.gradle

compileKotlin {
  kotlinOptions {
    freeCompilerArgs += "-Xjsr305=strict"
  }
}

Then, Kotlin will give you errors when assigning null to values:

@MyNonnullByDefault
public class Session {
  public void setDescription(String description) {
    this.description = description;
  }
}

Calling Java code from Kotlin:

val session = Session()
session.setDescription(null) // => Error: Null can not be a value of a non-null type …

build.gradle

  • Specify types explicitly
public class Session {
  public void setDescription(String description) {
    this.description = description;
  }
}
val session = Session()
val description: String? = session.description ✅

Or, the code below gives you IllegalStateException in runtime which notifies you very early what is wrong with your code:

val session = Session()
val description: String = session.description // IllegalStateException: session.description must not be null

How it works? - Intrinsic checks

The code below will be generated by compiler:

Intrinsics.checkExpressionValueisNotNull()

Collections

Read-only interfaces improve API

  • How read-only collections and mutable collections work in Kotlin

List & MutableList

  • Two interfaces declared in kotlin package
  • MutableList extends List

Read-only DOES NOT mean immutable - Read-only interface just lacks mutating methods

The actual list can be changed by another reference:

val mutableList = mutableListOf(1, 2, 3)
val list: List<Int> = mutableList

println(list) // [1, 2, 3]

mutableList.add(4)
println(list) // [1, 2, 3, 4]

In Kotlin, there is a plan to provide the immutable data structure in the standard library, but it is not ready yet

  • String! = notation, not syntax; platform type, which is a type that comes from Java

Platform types: summary

Good compromise between safety and convenience

Summary

  • primitives under the hood, and boxing is possible
  • Use Nothing whenever possible
  • Preventing NPE for Java interop: annotations, explicit types
  • Read-only does not mean immutable

以上です!