Kotlinのdefinitely non-nullable typesを試す
Kotlinのdefinitely non-nullable typesを試す
Kotlinのdefinitely non-nullable typesを試してみたメモです。
definitely non-nullable types
Kotlin 1.7.0からdefinitely non-nullable typesがstableになりました。
What's new in Kotlin 1.7.0 | Kotlin
元々どのような問題があって、それをどう解決しているのかを調べつつ試してみます。
下記のissueとproposalを参考にしています。
https://youtrack.jetbrains.com/issue/KT-26245
https://github.com/Kotlin/KEEP/blob/c72601cf35c1e95a541bb4b230edb474a6d1d1a8/proposals/definitely-non-nullable-types.md
https://youtrack.jetbrains.com/issue/KT-36770
https://github.com/Kotlin/KEEP/issues/268
下記バージョンで試してみます。
- kotlin 1.7.0
Background
kotlinでは下記のように型変数としてT
を指定すると、デフォルトの上限(Upper bounds)がAny?
のため、T?
を指定したのと同じことになります。
fun <T> printT(t: T) {
println(t)
}
String
を指定するとabc
は指定できますが、null
はエラーになります。
String?
を指定すると当然null
も指定できます。
fun main() { printT<String>("abc") // error // printT<String>(null) printT<String?>(null) }
ジェネリクスの型変数でnullableかnon nullかを指定できるようにしたいです。
Problems
上記のprintT
のようにほとんどのケースでは型推論が働くので、明示的に型を指定する必要はありません。
fun main() { printT("abc") printT(123) printT(null) }
特に問題となるケースとしては、Javaでnon nullとしてアノテートされた下記のようなインターフェースを
kotlinで継承または実装した場合です。
public interface JBox { <T> void put(@NotNull T t); }
上記の例では@NotNull
でnon nullであることを明示していますが、
kotlinではT
はnullableなのでnullが許容されてしまいます。
Proposal
言語の初期設計時であれば、
- T
-> non nullの型
- T?
-> nullableの型
のように設計できたかもしれませんが、kotlinはすでにstable versionになっているので、
新しい方法を導入する必要があります。
下記のdiscussionを見ると、T!!
やT & Any
などが提案されてましたが最終的にT & Any
が採用されたようです。
https://github.com/Kotlin/KEEP/issues/268
https://youtrack.jetbrains.com/issue/KT-26245
これはintersection type(インターセクション型、交差型)というようです。
https://en.wikipedia.org/wiki/Intersection_type
Example
実際に試してみます。
下記のjavaのインターフェースを定義します。
put
の引数とget
の戻り値には@NotNull
をつけてます。
JavaInterface.java
import org.jetbrains.annotations.NotNull; public interface JavaInterface<T> { void put(@NotNull T value); @NotNull T get(int index); }
デフォルト
JavaInterfaceを素直にkotlinのクラスで実装してみます。
このT
はJavaInterfaceで@NotNull
でアノテートされているにもかかわらずnullableです。
JavaInterfaceImpl.kt
class JavaInterfaceImpl<T> : JavaInterface<T> { private val list = mutableListOf<T>() override fun put(value: T) { list.add(value) } override fun get(index: Int): T { return list[index] } }
下記のように型パラメータにString?
を指定してnullをputできます。
Main.kt
fun main(){ val implString = JavaInterfaceImpl<String>() implString.put("abc") implString.put("") // error // implString.put(null) val implStringNullable = JavaInterfaceImpl<String?>() implStringNullable.put("abc") implStringNullable.put("") implStringNullable.put(null) }
上限(Upper Bounds)
今度は上限にAny
を指定して実装してみます。
JavaInterfaceImplUsingUpperBounds.kt
class JavaInterfaceImplUsingUpperBounds<T : Any> : JavaInterface<T> { private val list = mutableListOf<T>() override fun put(value: T) { list.add(value) } override fun get(index :Int): T { return list[index] } }
この場合、上限がnon nullなので、String
は指定できますがString?
は指定できません。
しかし型変数はT
ではなく上限付きのT : Any
となってしまいます。(それで問題ない場合やその方が好ましい場合も多いと思います)
Main.kt
fun main() { val implString = JavaInterfaceImplUsingUpperBounds<String>() implString.put("abc") implString.put("") // error // implString.put(null) // error // val implStringNullable = JavaInterfaceImplUsingUpperBounds<String?>() }
definitely non-nullable types
1.7.0から導入されたdefinitely non-nullable typesを指定してみます。
put
とget
の引数と戻り値をT & Any
にします。
JavaInterfaceImplUsingIntersection.kt
class JavaInterfaceImplUsingIntersection<T> : JavaInterface<T> { private val list = mutableListOf<T>() override fun put(value: T & Any) { list.add(value) } override fun get(index: Int): T & Any { // error // return list[index] return list[index]!! } }
型変数はT
なので型パラメータはString
もString?
も指定可能ですが、メソッドではT & Any
として定義しているので、
nullをputすることはできません。
Main.kt
fun main() { val implString = JavaInterfaceImplUsingIntersection<String>() implString.put("abc") implString.put("") // error // implString.put(null) val implStringNullable = JavaInterfaceImplUsingIntersection<String?>() implStringNullable.put("abc") implStringNullable.put("") // error // implStringNullable.put(null) }
型変数がT
なので、下記のようにnullを引数で取るメソッドや、nullを返すメソッドも定義できます。
JavaInterfaceImplUsingIntersection.kt
fun main() { fun putNullable(value: T) { list.add(value) } fun getNullable(index: Int): T { return list[index] } }
nullを引数で取るメソッドや、nullを返すメソッドを利用してみます。
Main.kt
fun main() { val implStringNullable = JavaInterfaceImplUsingIntersection<String?>() implStringNullable.put("abc") implStringNullable.put("") // error // implStringNullable.put(null) println("0: " + implStringNullable.get(0)) println("1: " + implStringNullable.get(1)) implStringNullable.putNullable(null) // error: NullPointerException // println("2: " + implStringNullable.get(2)) println("2: " + implStringNullable.getNullable(2)) }
実行
0: abc 1: 2: null
サンプルコードは下記にあげました。
おわり。
【参考】
https://kotlinlang.org/docs/whatsnew17.html#stable-definitely-non-nullable-types
https://youtrack.jetbrains.com/issue/KT-26245
https://github.com/Kotlin/KEEP/blob/c72601cf35c1e95a541bb4b230edb474a6d1d1a8/proposals/definitely-non-nullable-types.md
https://youtrack.jetbrains.com/issue/KT-36770
https://github.com/Kotlin/KEEP/issues/268