Kotlinの型を知る ~前編~

# はじめに

この記事は [Kotlin Advent Calendar](https://qiita.com/advent-calendar/2017/kotlin) 21日目の記事です。

Kotlin Advent Calendarは3年連続3回目の参加になります。
今年はGoogle I/OでKotlinがAndroidに正式採用されて、酉年の2017年はKotlin🐤にとっても飛躍の年になりました!
11月にはサンフランシスコにてKotlin Confが開催され、更なる盛り上がりを見せています！
また、レビュアーという形ですがKotlin In Actionの翻訳に携わりKotlinの盛り上げに少しばかりは貢献できたのではないかと思います。
本記事ではそのKotlin In Actionの著者の1人であるSvetlana Isakova氏のKotlin Confでのセッション 「[Kotlin Types: Exposed](https://www.youtube.com/watch?v=Uizh2WlJtnk)」の内容と、Kotlin In Actionの6章の内容を元に「Kotlinの型」についてまとめたものになります。
アジェンダは以下のとおりです。

- **KotlinとJavaの型**
- **Any, Unit, Nothing, void**
- Null許容型/Null非許容型とプラットフォーム型
- Collection型

「Null許容型/Null非許容型とプラットフォーム型」、「Collection型」については「[Kotlinの型を知る ~後編~](https://qiita.com/satorufujiwara/items/1399433529f1defa5b12)」をご覧ください。
本記事は前半の「KotlinとJavaの型」、「Any, Unit, Nothing, void」について解説していきます。

# KotlinとJavaの型

空前絶後のKotlinブームにより、新しくAndroidを始める方やRuby on Railsから静的型付けを求めてSpringに移行する方などが増え、これからはJavaを一度も書いたこと無い方がKotlinを書く事も増えていくのではないでしょうか？
Kotlinで書かれたコードはJVMで動作するJavaのバイトコードへコンパイルされるため、Javaの知識は避けて通れません。
この章では、JavaのBytecodeを見ながらKotlinとJavaの型の違いを解説していきます。

## Null非許容型, Null許容型

KotlinのNull非許容型、Null許容型について見ていきましょう。
以下の様な、Kotlinのコードがあります。
Javaでは、どのように表されるでしょうか？

```kotlin:Kotlin
fun foo(): Int = 1

fun bar(): Int? = 1
```

Bytecodeを見るには、IntelliJ IDEAの `Tools` > `Kotlin` > `Show Kotlin Bytecode` で確認することが可能です。
上記のコードは、このようになります

```Java:Bytecode
public static final int foo() {
   return 1;
}

@Nullable
public static final Integer bar() {
   return Integer.valueOf(1);
}
```

JavaにDecompileすると、KotlinのIntはプリミティブ型の`int`、KotlinのInt?はObject型の`java.lang.Integer`になります。

## KotlinとJavaの型比較

同様に、他の型もDecompileしてみるとこのようになります。

|Kotlin|Java|
|:-:|:-:|
|Int|int|
|Double|double|
|Boolean|boolean|
|Int?|java.lang.Integer|
|Double?|java.lang.Double|
|Boolean?|java.lang.Boolean|
|List<Int>|java.util.List<Integer>|
|Array<Int>|Integer[]||
|kotlin.String|java.lang.String|

String型だけはKotlinのStringクラスが存在します。
KotlinのStringは、Javaの分かりづらい処理をラップしています。

## KotlinのStringクラス

KotlinのStringクラスは、単純なJavaのStringクラスではなく、Kotlinの[拡張関数](https://dogwood008.github.io/kotlin-web-site-ja/docs/reference/extensions.html)の機能を利用してJavaには無いメソッドを幾つも提供しています。

例えば、Javaの`replaceAll`というメソッドがあります。

```Java:Java
"one.two.".replaceAll(".", "*")
```

実行結果はどうなるでしょうか？
結果は `********` となります。
意図した結果になりましたか？恐らく思った結果とは異なる結果が出力されたと思います。
Javaの`String.replaceAll`の第一引数は正規表現をとるため、注意が必要です。

```Kotlin:Kotlin
"one.two.".replace(".", "*")
```

一方、KotlinのStringクラスには `replaceAll` メソッドはありません。
代わりに`replace`メソッドが用意されています。
こちらの実行結果は、`one*two*`になります。
JavaのStringクラスと比較してメソッドが直感的になりました。
ちなみに、Javaの様に正規表現で変換するには、`kotlin.text.RegexExtensions.kt`クラスにある拡張関数 `toRegex()`メソッドを用いて以下のように書けます。

```Kotlin:Kotlin
"one.two.".replace(".".toRegex(), "*")
```

# Any, Unit, Nothing, void

次に、KotlinのAny, Unit, Nothing, Javaのvoidについてです。

## Any

クラスのコメントを見てみると、Anyはすべてのクラスのスーパクラスになると書いてあります。

> The root of the Kotlin class hierarchy.
> Every Kotlin class has [Any] as a superclass.

つまり、JavaのObject同様、引数にAnyを指定した場合でもInt型の変数を渡して実行することが可能です。
これはJavaのAuto boxingの機能が働いています。

```kotlin:Kotlin
log(2017)

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

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

前章のNull同様、Bytecodeを見てみましょう。

* log(Any)

```Java:Bytecode
public final static log(Ljava/lang/Object;)V
  @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
 L0
  ALOAD 0
  LDC "any"
  INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
 L1
  LINENUMBER 30 L1
  NEW java/lang/StringBuilder
  DUP
  INVOKESPECIAL java/lang/StringBuilder.<init> ()V
  LDC "Value: "
  INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
  ALOAD 0
  INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/Object;)Ljava/lang/StringBuilder;
  INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
  ASTORE 1
 L2
  ...略
```

* log(Int)

```Java:Bytecode

public final static log(I)V
  L0
   LINENUMBER 34 L0
   NEW java/lang/StringBuilder
   DUP
   INVOKESPECIAL java/lang/StringBuilder.<init> ()V
   LDC "Value: "
   INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
   ILOAD 0
   INVOKEVIRTUAL java/lang/StringBuilder.append (I)Ljava/lang/StringBuilder;
   INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
   ASTORE 1
  L1
   ...略
```

Anyの方はL0でAnyをNull非許容型か確認して内部ではObject型として扱ったまま、L1でStringBuilderに渡しています。
StringBuilderの内部では、`String.valueOf(Object)`が呼ばれて、変換されます。

Anyは、全てのクラスのスーパクラスになると言いましたが、厳密には違います。
以下のコードを見てみましょう。

```Kotlin:Kotlin
val hoge: Int? = 2017
log(hoge) // compile error

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

このコードは、コンパイルエラーになります。
つまり、Null許容型のスーパクラスはAnyではありません。

```Kotlin:Kotlin
val hoge: Int? = 2017
log(hoge)

fun log(any: Any?) { // <= Any?に変更
    println("Value: $any")
}
```

先程のコードを `Any?` に変更するとコンパイルする事が出来ます。
つまり厳密にクラスの階層構造を書くとこのようになります。

<img width="1312" alt="class_figure_1" src="https://qiita-image-store.s3.amazonaws.com/0/22527/a66b86dd-4117-ae32-53b9-58cb9e7ae4b5.png">

ちなみに、引数の型を`Any?`にするとBytecodeはこのようになります。

```Java:Bytecode
public final static log(Ljava/lang/Object;)V
    @Lorg/jetbrains/annotations/Nullable;() // invisible, parameter 0
   L0
    LINENUMBER 30 L0
    NEW java/lang/StringBuilder
    DUP
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    LDC "Value: "
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 0
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/Object;)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
    ASTORE 1
   L1
    ...略
```

Nullableアノテーションが付いただけで、あとは内部での処理はInt型と同じになります。(型の取り扱いはObject)

## Unit, void

Unitのクラスコメントには、このように書いてあります。

> The type with only one value: the Unit object. 
> This type corresponds to the `void` type in Java.

つまり、KotlinのUnitとJavaのvoidは同じ事を表していて、これら2つの値は意味のない値を返す事を示しています。

## Nothing

一方、Nothingのコメントには、このように書いてあります。

> Nothing has no instances. 
> You can use Nothing to represent "a value that never exists”:
for example, if a function has the return type of Nothing, it means that it never returns (always throws an exception).

Nothingはインスタンスがありません。またNothingの場合は、関数が返されないので常にExceptionがthrowされます。
まとめると、Nothingは値を返す事は無く、存在しない事を表すことが出来ます。
このことから、KotlinのNothingは、KotlinのUnit, Javaのvoidとは異なる事が分かります。

### Unit, Nothing, voidの具体例

具体例を見ていきましょう。
`bar`のBoolean値がTrueの時、`foo`にInt型の数字をいれて、Falseの時にExceptionをthrowする時を考えます。

```Kotlin:Kotlin
val foo = if (bar) {
   123
} else {
   fail()
}

fun fail() {
    throw IllegalStateException()
}
```

Kotlinでは、戻り値を指定していない関数の型は`Unit`になります。

```Kotlin:Kotlin
// コンパイラの解釈
fun fail(): Unit {
    throw IllegalStateException()
}
```

つまり、コンパイラは`bar`がTrueの時はInt型、Falseの時はUnit型として認識しています。
Int型とUnit型のスーパクラスはAny型になるので、`foo` の型はAny型になります。

```Kotlin:Kotlin
// コンパイラの解釈
val foo: Any = if (bar) {
   123
} else {
   fail()
}
```

恐らくこれは、みなさんの意図した結果では無いと思います。
`foo`はInt型としてコードを書いていると思います。

この問題を解決するにはどのようにしたら良いでしょうか。
そこで、先程のNothingの登場です。
最初は省略をしていた、`failメソッド`の戻り値に `Nothing` を指定します。

```Kotlin:Kotlin
fun fail(): Nothing {
    throw IllegalStateException()
}
```

`failメソッド`にNothingの戻り値を指定すると、コンパイラは`bar`がTrueの時はInt型、Falseの時はNothing型として認識します。
Nothingは全てのクラスのサブクラスになるので、`foo`はInt型としてコンパイラに認識され、意図したコードになります。

```Kotlin:Kotlin
val foo: Int = if (bar) { // Int型として定義
   123                    // Int
} else {
   fail()                 // Nothing
}

fun fail(): Nothing {     // 戻り値にNothing
    throw IllegalStateException()
}
```

### Nothing?

AnyにもNull許容型があったので、NothingにもNull許容型があるのか疑問になる方もいるでしょう。
もちろんNothingにもNull許容型が存在します。
`Nothing?`はNothingのスーパクラスではありますが、Null非許容型のスーパクラスではありません。
図にするとこのようになります。

<img width="1179" alt="class_figure_2" src="https://qiita-image-store.s3.amazonaws.com/0/22527/1b4fe07f-debd-575b-b85f-5dd1ec908d6a.png">

全てのスーパクラスが`Any?`になり、全てのサブクラスが`Nothing`になるのがわかると思います。
Nothingを頻繁に使う事は、あまりないかと思いますが型の階層図は覚えておきましょう。

# まとめ

* Kotlinを使う場合も、JavaのBytecodeを見る癖をつけましょう
* `Any`は全てのクラスのスーパータイプになるが厳密には`Any?`が最上位に位置する
* `Nothing`は全てのクラスのサブクラスになる。`Nothing?`は`Nothing`のスーパータイプに位置する

2018年はKotlin🐤にとって更なる飛躍の年になりますように！
Have a nice Kotlin!