読者です 読者をやめる 読者になる 読者になる

算譜王におれはなる!!!!

偏りはあると思うけど情報技術全般についてマイペースに書くよ。

Kotlinの総称型(ジェネリクス) 前編

Kotlin

※下記URLのサイトを参考にしました。英語、または技術的な知識が至らず、内容に誤りが含まれるおそれがありますので、ご了承ください。
※本エントリは参考サイトの翻訳をベースに、加筆・変更を施した構成となっています。

参考サイト http://confluence.jetbrains.net/display/Kotlin/Generics

後編 Kotlinの総称型(ジェネリクス) 後編 - 算譜王におれはなる!!!!

総称型クラス

JavaのようにKotlinのクラスは型パラメータを持つことができます。一般に、そのようなクラスのインスタンスを生成するには、型パラメータを提供する必要があります。しかし、コンストラクタの引数やその他の手段によりパラメータが推論できる場合は、型パラメータを省略することができます。

class Foo<T>(val t : T)

fun main(args : Array<String>) {
  val foo1 : Foo<String> = Foo<String>("abc")
  val foo2 = Foo("ABC")
}

総称型は実行時に保持される

Javaとは異なり、クラスの実際の型引数についての情報は実行時に保持されます。これにより実行時にオブジェクトの完全な型をチェックすることが可能になります。

if(hoge is Hoge<Int>) { // 'is'はJavaで言う'instanceof'
  // hogeは単なるHogeではなく、Hoge<Int>であることが保証されている
}
// Java
//if(hoge instanceof Hoge<Int>) { // この形はJavaではコンパイルエラーとなる
if(hoge instanceof Hoge) { // 精々、この形
}

isチェックには、型変数も使用できます。

if (foo is T) { // this is a non-trivial check
  // ...
}

http://confluence.jetbrains.net/display/Kotlin/Generics より引用

Kotlinにはraw型(未加工型)と型消去(type erasure)はありません。つまり、総称型クラスは型パラメータなしでは存在できません。これはEffective Javaの項目23「Don't use raw types in new code*1」と項目24「Eliminate unchecked warnings*2」に対応しています。

分散

Javaの型システムの中で最もトリッキーなパーツのひとつは、ワイルドカード*3ですが、Kotlinにはありません。

はじめにJavaでは何故、ミステリアスなワイルドカードが必要なのか考えましょう。

と、参考サイトにはありますが、長くなるので割愛…^^;
詳しくは、Effective Javaの項目28「Use bounded wildcards to increase API flexibility*4」を参照してください。また、項目28 APIの柔軟性向上のために境界ワイルドカードを使用する - プログラマ的京都生活では非常にわかりやすい解説がされていますので、併せてご覧ください。

マイクロソフトのC#を解説するページに倣って、ここではvarianceという言葉を「分散」と翻訳しています。

宣言箇所分散

要するにJavaの総称型は不変(invariance)であることが、ここでは問題となっています。これに対しKotlinではC#のような仕組みが用意されています。つまり型パラメータに共変性(covariance)または反変性(contravariance)と言った性質を持たせることができます。

共変性を指定するには、型パラメータにout修飾子を付けます。

abstract class Source<out T> {
  fun nextT() : T
}

fun demo(strs : Source<String>) {
  val objects : Source<Any> = strs // This is OK, since T is an out-parameter
  // ...
}

http://confluence.jetbrains.net/display/Kotlin/Generics より引用

out修飾子は分散アノテーション(variance annotation)と言い、それは型パラメータの宣言箇所で提供されているので、この機能を宣言箇所分散と呼んでいます。
そして型パラメータに反変性を指定するinアノテーションもあります。

abstract class Comparable<in T> {
  fun compareTo(other : T) : Int
}

fun demo(x : Comparable<Number>) {
  x.compareTo(1.0) // 1.0 has type Double, which is a subtype of Number
  // Thus, we can assign x to a variable of type Comparable<Double>
  val y : Comparable<Double> = x // OK!
}

http://confluence.jetbrains.net/display/Kotlin/Generics より引用

使用箇所分散:型投影

宣言箇所分散では、分散アノテーションをクラス宣言のときに使用しましたが、使用箇所分散(use-site variance)では、分散アノテーションをオブジェクトの使用箇所で宣言します。つまり、メソッド等の仮引数の型に out や in を付加して、型の硬直性を解消します。そして、この使用箇所分散のことを型投影(type projection)と呼びます。

ざっくりしすぎな説明ですみません。詳細はGenerics - Kotlin - Confluenceをご覧ください。

スター・プロジェクション

型パラメータについては何も知らないけど安全な方法で使いたい、と言いたくなるでしょう。ここでの安全な方法とは、outプロジェクションを扱ったり、このプロジェクションが、対応するパラメータの上限であること(つまり、ほとんどのケースではout Any?)を言います。Kotlinではスター・プロジェクションと呼ばれる速記構文が提供されています。Foo<*>は、Foo(BarはFooの型パラメータの上限)を意味します。

注意:スター・プロジェクションはJavaのraw型に非常に似ていますが、安全です。

Kotlinのジェネリクスのお話の続きはまた今度〜!
後編→Kotlinの総称型(ジェネリクス) 後編 - 算譜王におれはなる!!!!

*1:邦訳:新たなコードで原型を使用しない

*2:邦訳:無検査警告を取り除く

*3:http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html参照

*4:邦訳:APIの柔軟性向上のために境界ワイルドカードを使用する