この記事でわかること
- ジェネリックをどのような場面で使用するか(型の安全性について)
- out(共変) について
- in(反変) について
ジェネリックをどのような場面で使用するか(型の安全性について)
Kotlinにおいて、ジェネリックを利用することで、様々な型に対応可能な汎用的なクラスやメソッドを簡単に作成できます。ここでは、ジェネリックを使ったシンプルなBoxクラスを例に取り上げ、その基本的な使い方を説明します。
class Box<T>(t: T) {
var value = t
fun updateValue(v: T) {
value = v
}
fun returnValue(): T {
return value
}
}
val box = Box("xxx")
box.updateValue("yyy")
box.returnValue // return "yyy"
val box = Box(111)
box.updateValue(222)
box.returnValue // return 222
BoxクラスはInt、String、カスタムクラスなど、どんな型のデータも格納できます。この柔軟性により、コードの再利用性と型安全を確保しつつ、様々なデータ操作を一つのクラスで行えます。
例えば、上記のコードでジェネリックの代わりにAnyを使用すると型の不一致が起きる可能性があります。
ここからは色々調べながら書いたので間違いがあればコメントで指摘お願いします。
out(共変) について
outキーワードを使用することで、ジェネリック型の共変を安全に実現できます。これにより、サブタイプのインスタンスをスーパータイプの変数に代入することが可能になり、より柔軟なコードが書けるようになります。
// outを使用する場合
class Box<out T>(val value: T)
fun main() {
val stringBox: Box<String> = Box("Hello")
val anyBox: Box<Any> = stringBox // StringはAnyのサブタイプなので、これは許可されます。
val intBox: Box<Int> = Box(123)
val anyBox: Box<Any> = intBox
}
// outを使用しない場合
class Box<T>(val value: T)
fun main() {
val stringBox: Box<String> = Box("Hello")
val anyBox: Box<Any> = stringBox // コンパイルエラー
val stringBox2️: Box<String> = stringBox // エラーは起きない
}
上記の例では、IntとStringはAnyのサブタイプであるため、Box< Any >にBox< Int >,Box< String >を代入することができます。
Any, StringではStringのスーパータイプにAnyがあるのと同じように、
Box< Any >とBox< String >ではBox< String >のスーパータイプがBox< Any >であり、
この方向が同じであるから共変と言います。
in(反変) について
Kotlinのinキーワードは、ジェネリック型がメソッド引数としてのみ使用される場合に使います。これにより、ジェネリック型が反変になり、より具体的な型のインスタンスをより一般的な型のパラメータに代入できるようになります。
// inを使用する場合
class LoggerConsumer<in T> {
fun log(item: T) {
println("Logging: $item")
}
}
fun main() {
val anyLogger: LoggerConsumer<Any> = LoggerConsumer<Any>()
anyLogger.log(123)
anyLogger.log(true)
// Any型のConsumerをString型のConsumerとして扱う
val stringLogger: LoggerConsumer<String> = anyLogger
stringLogger.log("This is a string")
val intLogger: LoggerConsumer<Int> = anyLogger
intLogger.log(111)
}
// inを使用しない場合
class LoggerConsumer<T> {
fun log(item: T) {
println("Logging: $item")
}
}
fun main() {
val stringLogger: LoggerConsumer<String> = LoggerConsumer<String>()
stringLogger.log("This is a string")
val intLogger: LoggerConsumer<Int> = LoggerConsumer<Int>()
intLogger.log(111)
}
上記の例では、inを使用することで異なる型毎にインスタンスを作成する必要がなくなり、インスタンスを再利用できるようになります。
Any, Stringでは、StringのスーパータイプがAnyであり、
LoggerConsumer< Any >, LoggerConsumer< String >では、LoggerConsumer< Any >のスーパータイプがLoggerConsumer< String >であり、
この方向が反対であるため反変と言います。