Help us understand the problem. What is going on with this article?

Java開発者に送る、Kotlinのジェネリック

KotlinのジェネリックはJavaで表現可能な仕様と、そうでないものがあります。
まずは、Javaで表現可能なものから見ていきます。

ジェネリック関数

Kotlinでジェネリック関数を宣言してみます。

// Kotlin
fun <T> convertToString(arg: T) : String {
    return arg.toString()
}

通常の関数宣言のfunキーワードと関数名の間に型パラメータを宣言します。
同等の関数をJavaで表現することが出来ます。

// Java
<T> String convertToString(T arg) {
    return arg.toString();
}

型制約

型パラメータに制約を持たせることが出来ます。

fun <T: Exception> convertToString(arg: T) : String {
    return arg.localizedMessage
}

型パラメータTはExceptionもしくはそのサブクラスとする制約を付加しました。
こちらもJavaで表現可能です。

// Java
<T extends Exception> String convertToString(T arg) {
    return arg.getLocalizedMessage();
}

ジェネリッククラス

Kotlinでジェネリッククラスを定義してみます。

class Box<T>(t: T) {
    var value = t
}

クラス名とプライマリコンストラクタの間に型パラメータを宣言します。
同等のクラスをJavaで表現することが出来ます。

class Box<T> {
    T value;
    Box(T t) {
        this.value = t;
    }
}

型制約

クラスについても型パラメータに制約を持たせることが出来ます。

class Box<T: Number> (t: T) {
    var value = t
}

型パラメータTについて、Numberもしくはそのサブクラスとする制約をつけました。
こちらもJavaで表現可能です。

static class Box<T extends Number> {
    T value;

    Box(T t) {
        this.value = t;
    }
}

where

関数やクラスで型制約を複数指定する場合はwhere句が便利です。

fun <T> doSomething(t: T) where T: Runnable, T: Cancellable {

}

変数宣言と変位指定

Javaの境界ワイルドカード型と同じく、変数宣言時に変位を指定する記法がKotlinにもあります。

Java Kotlin
上限境界制約 <? extends XXX> <out XXX>
下限境界制約 <? super XXX> <in XXX>

共変

DogはAnimalのサブタイプです。
そこで、List<Dog>はList<Animal>のサブタイプとなるように共変の関係を作ります。

open class Animal
class Dog: Animal()

var animals: List<out Animal> = emptyList()
val dogs: List<Dog> = emptyList()

animals = dogs

反変

DogはAnimalのサブタイプです。
List<Animal>はList<Dog>のサブタイプとなるように反変の関係を作ります。

open class Animal
class Cat: Animal()

val animals: MutableList<Animal> = mutableListOf()
var cats: MutableList<in Cat> = mutableListOf()

cats = animals

Kotlinでは、宣言時の変位指定を型投影 (type projection)と呼びます。

クラス宣言と変位指定

Javaでは共変と反変の関係を、クラスそのものに指定することは出来ません。そのため、変数宣言などはその都度変位指定する必要があります。

一方、Kotlinにはクラスの宣言時に変位を指定する仕組みがあります。
クラスの設計者、実装者の意図に反して変位が利用されるリスクがなくなります。

//             ↓型パラメータにoutポジションを指定
class Supplier<out T> {
    fun get(): T {
        //
    }
}

Supplierクラスは型パラメータTについて共変となります。

var animalSupplier: Supplier<Animal> = Supplier()
var dogSupplier: Supplier<Dog> = Supplier()
animalSupplier = dogSupplier

変数の宣言時に変位指定が不要となります。
同様にinポジションを指定することで反変とすることも可能です。

非null許容型とジェネリック

下記のジェネリック関数、引数にはnullを渡すことが出来ます。

fun <T> doSomething(t: T){
}

doSomething(null) // OK

JavaのルートクラスObjectに相当するKotlinの型はAnyです。
が、制約のない型パラメータの上限制約はAnyではなくAny?です。

もし、非null許容型のジェネリック関数を宣言したければ
型制約にAnyを指定します。

fun <T: Any> doSomething(t: T){
}

doSomething(null) // コンパイルエラー
doSomething(1) // OK
doSomething("hoge") // OK

スター投影

型パラメータにアスタリスク*を指定するスター投影という仕組みがあります。

var animals: List<*> = //

上記のList<*>List<Any?>と等値ではありません。List<out Any?>として振る舞います。
スター投影は型パラメータに関心がない場合に使う構文です。
要素を取得しAny?として扱う分には安全ですが、リストに要素を追加することは安全ではありません。

下記のように、想定外の型を受け取るリスクがあるためoutポジションとして振る舞います。

val animals: MutableList<*> = mutableListOf<Animal>()
val animal = animals.first() // OK
animals.add(1) // コンパイルエラー
animals.add(Dog()) // コンパイルエラー
Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away