こんにちは!リンコですピヨ!この記事では自分の勉強の復習もかねて、Kotlinでのジェネリクスを解説するピヨ!
この第35章では、プログラミング言語であるKotlinのジェネリクスについて、一緒に勉強していきましょう。
この記事を読めばプログラミング未経験の方も、Kotlinのジェネリクスについて1つ1つ理解しながら勉強できると思うので、是非最後まで読んで頂ければと思います。
※この記事で出てくる「サンプルコード」は、記述が長く画面からはみ出ている場合がありますが、横にスライドすると表示されるのでご安心ください。
【Kotlin】ジェネリクスとは?
まずは、ジェネリクスがどういうものかを勉強していきましょう。
<型名>のことを型パラメータという
ジェネリクスは日本語で言うと「総称型」と言い、型をパラメータにすることを可能としたものになります。
型名を<>で囲う
// リストの書式 val リスト名: List<型名> = listOf(値1,値2,値3...)
例えば上記のような第13章で用いたリストのコードがあります。
リストの型を示す「List<型名>」という部分がありますが、このように変数の型やクラス名を「<>」で囲む記述方法を使うことを「ジェネリクス」と言います。
val a: List<Int> = listOf(1, 2, 3)
このように<>でIntが囲われている場合は、このリストはInt型でしたよね。
リスト以外のセットやマップなどのコレクションでも、同様に<>というジェネリクスの記述方法を使っていて、ジェネリクスはKotlinのコードのいろいろなところで出てきます。
<型名>のことを型パラメータという
この記述方法を使うことをジェネリクスと言いますが、<型名>という記述のことを「型パラメータ」と言います。
ジェネリクスはコレクションでも自然に使っていましたが、クラスや関数の中でも使うことができます。
この記事では、主にクラス内や関数内でジェネリクスを使う方法を勉強していきます。
型をパラメータにすることを可能にし、宣言時に型を限定せず呼び出し時に決めることがジェネリクスピヨ!
【Kotlin】ジェネリクスを使ったクラスを定義してみよう
ジェネリクスについてなんとなく理解したところで、続いてジェネリクスを使ったクラスを定義して勉強していきましょう。
クラスでのジェネリクスの使い方
クラスでジェネリクスを使うにはどうすればいいか、書式を見ていきます。
// 呼び出し側 val インスタンス名 = クラス名<型名>() // クラスの定義 class クラス名<型パラメータ> { }
ジェネリクスを使ったクラスの書式は、呼び出し側ではクラス名の後ろに「<型名>」を記述します。
クラスの定義側ではクラス名の後ろに「型パラメータ」を記述します。
実際にジェネリクスを使ったクラスを定義
では実際にジェネリクスを使ったコードを記述していきます。
サンプルコード
以下のサンプルコードでは入れ物という意味を持つcontainerクラスを生成し、1つ目のインスタンスにはString型のクラスを、2つ目のインスタンスにはInt型のクラスを代入しています。
fun main() { val container1 = Container<String>() val container2 = Container<Int>() container1.value("文字列", "りんこ") container2.value("整数", 12345) } class Container<T> { fun value(a: String, b: T) { println("aの値は「${a}」で、bの値は「${b}」です。") } }
出力
サンプルコードを実行すると、以下のように出力されます。
aの値は「文字列」で、bの値は「りんこ」です。 aの値は「整数」で、bの値は「12345」です。
このコードでは、クラスの定義の際に「<T>」という記述があります。
class Container<T> {
これがジェネリクスの型パラメータの記述になるのですが、ここの「T」は「何らかの特定の型」を意味しています。
この「T」が何の型になるかは、containerクラスを定義している段階では決まっておらず、呼び出し側の記述で決まります。
val container1 = Container<String>() val container2 = Container<Int>() container1.value("文字列", "りんこ") container2.value("整数", 12345)
ここでは、インスタンスcontainer1を生成する際には「Container<String>」と型パラメータを記述しているので、String型のContainerクラスを呼び出していることになります。
そのおかげで、container1のvalue関数を呼び出す際にパラメータとして「りんこ」という文字列を引き渡すことができています。
同様にインスタンスcontainer2を生成する際に「Container<Int>」と型パラメータを記述しているので、Int型のContainerクラスを呼び出していることになり、container2のvalue関数には「12345」という整数を引き渡せています。
fun value(a: String, b: T) {
またvalue関数のパラメータの記述で「(a: String, b: T)」と、変数bの型も「T」となっていますが、ここも同様で呼び出し側で「Container<String>」と記述すれば「T」はString型となりますし、「Container<Int>」と記述すればInt型になります。
このように「汎用性(ジェネリック)」のある型のクラスを定義できるのが、ジェネリクスの大きな特徴になります。
型パラメータを記述する位置をしっかり覚えるピヨ!
【Kotlin】ジェネリクスを使った関数を定義してみよう
続いて、ジェネリクスを使った関数について勉強していきましょう。
関数でのジェネリクスの使い方
関数でジェネリクスを使うにはどうすればいいか、書式を見ていきます。
// 呼び出し側 val インスタンス名 = クラス名() インスタンス名.関数名<型名>(値,値) // 関数の定義 fun <型パラメータ> 関数名 { }
ジェネリクスを使った関数の定義の場合は、関数名の前に型パラメータを記述します。呼び出し側では、関数名を後ろに型名を記述しましょう。
実際にジェネリクスを使った関数を定義
では実際にジェネリクスを使ったコードを記述していきます。
サンプルコード
以下のサンプルコードでも上記と同じようなコードですが、クラスではなく関数の方でジェネリクスを使っています。
fun main() { val container1 = Container() val container2 = Container() container1.value<String>("文字列", "りんこ") container2.value<Int>("整数", 12345) } class Container { fun <T> value(a: String, b: T) { println("aの値は「${a}」で、bの値は「${b}」です。") } }
出力
サンプルコードを実行すると、以下のように出力されます。
aの値は「文字列」で、bの値は「りんこ」です。 aの値は「整数」で、bの値は「12345」です。
このコードでは、関数の定義の際の関数名の前に「<T>」という記述があります。
fun <T> value(a: String, b: T) {
そして、インスタンスcontainer1のvalue関数を呼び出す際の、関数名の後ろに「<String>」と型名が記述されていますね。
これで関数内のTはString型ということになっています。
val container1 = Container() val container2 = Container() container1.value<String>("文字列", "りんこ") container2.value<Int>("整数", 12345)
このようにクラスでも関数でもジェネリクスは使えて、記述する位置によって変わってきますので、記述方法をしっかりと覚えておきましょう。
クラスと関数では型パラメータを記述する位置が若干違うピヨ!
ジェネリクスを使った関数定義では型名を省略できる
ジェネリクスを使った関数定義では上記のように記述しますが、この場合呼び出し側の型名に関しては省略することができます。
サンプルコード
以下のサンプルコードも同じコードですが、呼び出し側の型名に関しては省略しています。
fun main() { val container1 = Container() val container2 = Container() container1.value("文字列", "りんこ") container2.value("整数", 12345) } class Container { fun <T> value(a: String, b: T) { println("aの値は「${a}」で、bの値は「${b}」です。") } }
出力
サンプルコードを実行すると、以下のように出力されます。
aの値は「文字列」で、bの値は「りんこ」です。 aの値は「整数」で、bの値は「12345」です。
このように<String>や<Int>という型名の記述を省略しましたが、問題なく出力されていることがわかります。
container1.value("文字列", "りんこ") container2.value("整数", 12345)
この省略も覚えておくと楽なので、ジェネリクスを使って関数を記述する際は省略して記述するピヨ!
【Kotlin】ジェネリクスを使わなかったらどうなる?
このように便利なジェネリクスですが、もし上記のような出力をさせたくて、ジェネリクスを使わなかったらどうなるのでしょうか?
ジェネリクスを使わなかったらどれだけコードが長くなってしまうのかを見ていきましょう。
サンプルコード
以下のサンプルコードも上記と基本的に同じですが、ジェネリクスを使っていません。
fun main() { val container1 = stringContainer() val container2 = intContainer() container1.value("文字列", "りんこ") container2.value("整数", 12345) } class stringContainer { fun value(a: String, b: String) { println("aの値は「${a}」で、bの値は「${b}」です。") } } class intContainer { fun value(a: String, b: Int) { println("aの値は「${a}」で、bの値は「${b}」です。") } }
出力
サンプルコードを実行すると、以下のように出力されます。
aの値は「文字列」で、bの値は「りんこ」です。 aの値は「整数」で、bの値は「12345」です。
どうでしょうか?ほぼほぼ同じことをしているんですが、コードの長さが全然違います。
このコードではジェネリクスを使っていないので、String型のstringContainerクラスと、Int型のintContainerクラスという風に、2つのクラスを定義しないといけなくなりました。
このようにプロパティ変数の型が違うだけなのに、ほとんど同じコードを2回記述するのは馬鹿らしいですよね。
こういった場合の時は、是非ジェネリクスを使うようにしましょう。
ジェネリクスを使うのと使わないのでは、コードの長さが全然違うピヨ!
【Kotlin】ジェネリクスの型パラメータの表記について
最後に、ジェネリクスの型パラメータの表記について勉強します。
ジェネリクスの型パラメータを表す文字は基本的に自由に名前を付けることができるのですが、一般的に大文字1つで記述することが習慣になっています。
その中でも、使われていることが多い文字がいくつかあるので紹介していきます。
表記 | 意味 | 詳細 |
---|---|---|
<T> | 型(Type) | 型パラメータの名前として、型を意味するTypeのTが使われることが一番多い。 |
<E> | 要素(Element) | リストやセットなど複数の要素がある時に、要素を意味するElementのEが使われる。 |
<K> | キー(Key) | マップで使用する時に、キーを意味するKeyのKが使われる。 |
<V> | バリュー(Value) | 同じくマップで使用する時に、値を意味するValueを意味するVが使われる。 |
<N> | ナンバー(Number) | 数を使う時に、数を意味するNumberのNが使われる。 |
この5つの表記は使うことが多いので、覚えておいて使う場面が出てきたらしっかり使えるようにするピヨ!
まとめ
この記事では、プログラミング言語であるKotlinのジェネリクスについて勉強していきましたが、いかがでしたでしょうか?今回の記事をまとめると以下のようになります。
- ジェネリクスは型をパラメータにすること
- <型名>という記述を「型パラメータ」という
- クラスでも関数でもジェネリクスを使える
- ジェネリクスクラスの書式は、呼び出し側ではクラス名の後ろに「<型名>」を記述、クラスの定義側ではクラス名の後ろに「型パラメータ」を記述
- ジェネリクス関数の書式は、呼び出し側では関数名の後ろに「<型名>」を記述、関数定義の場合は関数名の前に「型パラメータ」を記述
今回勉強した「ジェネリクス」は難しくややこしいと思うので、何回も読み直してしっかり覚えておきましょう。
次回の記事では「オブジェクト式とオブジェクト宣言」について勉強していくピヨ!
プログラミング未経験の方や入門レベルの方、Kotlinについて詳しくなりたい方は、また一緒に勉強するピヨ!
コメント