自己紹介
- opengl-8080
- 主に Qiita で技術メモを書いたり
- 趣味で Kotlin, Java 8, Java EE をさわり
仕事で Excel VBA, Java 6, Struts1 を使う関西の SE - 関西(たまに東京)の勉強会に出没
Kotlin といえば?
便利な機能の話はよく聞くが...
- Nullable
- Non-Null Type
- 関数リテラル
- 拡張関数
- 演算子オーバーロード
- スマートキャスト
- クラス委譲
- プロパティ委譲
- データクラス
- etc...
コレクションの話を
あまり聞かない気がする
自分の調べ方が甘いだけかも...
いざコレクションを
使おうとすると...
- Java の
ArrayList
を使えばいいの?- Kotlin のコレクションがあるらしい
- どういうクラスがあるの?
- クラス間の関係は?
- Java のコレクションとの変換は?
- どうやってインスタンス作るの?
- ファクトリ関数みたいなのがあるらしい
- 何種類あるの?
- どういうメソッドが用意されているの?
- Stream API でやってたアレ、
Kotlin のコレクションではどうやるの?
- Stream API でやってたアレ、
- 型引数で
<? extends Hoge>
とかしてたの、
どうすればいいの?
(;´・ω・)
結構分からないことが多い
言語を覚えるために勉強すること
- 開発環境の構築方法
- 文法(変数宣言、関数宣言、クラス宣言、制御文、etc...)
- コレクションの使い方
コレクションは、
言語を学ぶうえで文法と
同じくらい基本的なもの
Kotlinコレクション入門
対象者のイメージ
- Kotlin はじめたばかり
- Kotlin 使ってるけど、そういえばコレクションについて詳しく調べたことない
Kotlin のコレクションには
どんなクラスがある?
Kotlin のコレクション
Kotlin のコレクション
-
List
,Set
,Map
の3つ-
List
:順序を持つコレクション。重複可 -
Set
:順序を持たないコレクション。重複不可 -
Map
:キーバリューで値を保持するコレクション
-
Java との違い
読み取り専用とミュータブル
-
List
,Set
,Map
は読み取り専用-
add()
などの更新系メソッドを持っていない
-
- 更新系のメソッドは、
Mutable
が頭についた
インターフェースに定義されている
読み取り専用 ≠ イミュータブル
val mutableList = mutableListOf(1, 2, 3)
val readonlyList: List<Int> = mutableList
mutableList.add(9)
println(readonlyList)
実行結果
[1, 2, 3, 9]
どうやってインスタンスを作る?
インスタンスの作り方
val list = listOf(1, 2, 3)
val set = setOf(1, 2, 3)
val map = mapOf(1 to 10, 2 to 20, 3 to 30)
-
~Of()
という関数が用意されている - それぞれ引数が可変長になっている
インスタンスの作り方
val mutableList = mutableListOf(1, 2, 3)
val mutableSet = mutableSetOf(1, 2, 3)
val mutableMap = mutableMapOf(1 to 10, 2 to 20, 3 to 30)
- ミュータブルなコレクションをつくる場合は、
mutable~Of()
関数を使用する
Java コレクションのインスタンスを作る
val arrayList = arrayListOf(1, 2, 3)
val hashSet = hashSetOf(1, 2, 3)
val hashMap = hashMapOf(1 to 10, 2 to 20, 3 to 30)
-
ArrayList
やHashMap
など、 Java のコレクションのインスタンスを作る関数も用意されている -
linkedMapOf()
,sortedSetOf()
などもある
Java のコレクションとの相互変換はどうする?
Java コレクションとの相互変換
- Kotlin には、 Java の型と相互に自動変換する仕組みが用意されている
- Java の
int
⇔ Kotlin のkotlin.Int
- Java の
java.lang.String
⇔ Kotlin のkotlin.String
- Java の
Java コレクションとの相互変換
- コレクションも自動変換の対象
-
java.util.Iterable
⇔kotlin.collections.Iterable
-
java.util.Collection
⇔kotlin.collections.Collection
-
java.util.List
⇔kotlin.collections.List
-
java.util.Set
⇔kotlin.collections.Set
-
java.util.Map
⇔kotlin.collections.Map
-
変換のための特別な実装は不要
Javaのコード
public class Foo {
public static java.util.List<java.lang.String>
getList() {...}
}
Kotlinのコード
val list: kotlin.collections.List<kotlin.String>
= Foo.getList();
Kotlin のコレクションでは、
どんなメソッドが利用できる?
コレクションで使えるメソッド
- 演算子オーバーロードを利用した簡易な記法
- Stream API のような内部イテレーション用のメソッド
演算子オーバーロード
// +, -
val from = listOf(1, 2, 3)
val to = from + 9 - 2
println("from=${from}, to=${to}")
// +=, -=
val mutableList = mutableListOf(1, 2, 3)
mutableList += 7
mutableList -= 1
println("mutableList=${mutableList}")
// [n]
println("mutableList[1] = ${mutableList[1]}")
実行結果
from=[1, 2, 3], to=[1, 3, 9]
mutableList=[2, 3, 7]
mutableList[1] = 3
-
+
,-
は、内部の状態を変更せずに、新しいコレクションを返す。 -
+=
,-=
は、内部の状態を変更する。 -
[]
で各要素にアクセス可能(set も可能)。
内部イテレーション用のメソッド
val list = listOf(1, 2, 3)
list.forEach { println("forEach : " + it) }
println("all : " + list.all { it < 4 })
println("any : " + list.any { it == 2 })
println("filter : " + list.filter { it%2 == 1 })
println("map : " + list.map { it * 10 })
println("joinToString : " + list.joinToString(" > ") { "($it)" })
実行結果
forEach : 1
forEach : 2
forEach : 3
all : true
any : true
filter : [1, 3]
map : [10, 20, 30]
joinToString : (1) > (2) > (3)
- 多くの内部イテレーション用のメソッドが用意されている。
- Stream API でできたことは、だいたい標準で可能。
欲しいメソッドが無い場合は?
- 例えば
peek()
に該当するメソッドは無かったぽい。 - Kotlin の拡張関数を使えば、欲しいメソッドを追加できる。
peek() メソッド追加してみる
fun <T> Iterable<T>.peek(iterator: (T) -> Unit) : Iterable<T> {
this.forEach(iterator)
return this
}
fun main(args: Array<String>) {
(0..10)
.filter { it%2 == 0 }
.peek { println("peek : $it") }
.filter { it < 5 }
.forEach { println("forEach : $it") }
}
実行結果
peek : 0
peek : 2
peek : 4
peek : 6
peek : 8
peek : 10
forEach : 0
forEach : 2
forEach : 4
List extends Hoge>
みたいなのはどうする?
共変
-
B
がA
のサブタイプのときに、Foo<B>
がFoo<A>
のサブタイプとなる場合は共変 - 要は、
List<Number>
の変数にList<Int>
の変数を代入できたら共変
Java は共変ではない
List<Integer> intList = new ArrayList<>();
List<Number> numberList = intList;
// コンパイルエラー!!
以下のように記述すれば代入できる
List<Integer> intList = new ArrayList<>();
List<? extends Number> numberList = intList; // OK
※ただし、配列は共変
Java は共変ではない
ただし、 add()
などの内容を書き換えるメソッドを実行しようとするとコンパイルエラーになる
List<Integer> intList = new ArrayList<>();
List<? extends Number> numberList = intList;
numberList.add(10); // コンパイルエラー!!
なぜ書き込みできない?
List<Integer> intList = new ArrayList<>();
List<? extends Number> numberList = intList;
Number number = new BigDecimal(1.1);
numberList.add(number);
// add() を許可すると、
// numberList の実体は List<Integer> なのに、
// BigDecimal が追加できてしまう!
Integer i = intList.get(0); // BigDecimal が返ってくる!?
Kotlin は共変?
val intList: List<Int> = listOf(1, 2, 3)
val numberList: List<Number> = intList
// コンパイルエラーにならない!
- Kotlin の
List<Int>
は、List<Number>
に代入できる - ということは、 Kotlin は共変?
Kotlin の List は読み取り専用
- Kotlin の
List
は読み取り専用なのでadd()
などのメソッドを持っていない -
List<Number>
にList<Int>
を代入しても問題ない
Mutableなコレクションは共変ではない
val intList: MutableList<Int> = mutableListOf(1, 2, 3)
val numberList: MutableList<Number> = intList
// コンパイルエラー!!
-
MutableList<Number>
にMutableList<Int>
を代入しようとしたらコンパイルエラーになる
コンパイラは、
その型が読み取り専用かどうかを
どうやって判断している?
Kotlin のソースを見てみる
public interface List<out E> : kotlin.collections.Collection<E> {
- 型引数が
<out E>
となっている。
読み取り専用であることを宣言する
class Foo<out T>(val t: T) {
fun get() = this.t
fun set(t: T) {}
// Type Parameter T is declared as 'out'
// but occurs in 'in' position in type T.
}
-
<out T>
とすると、T
はメソッドの引数など「入力」となる場所で使用できなくなる。
まとめ
- Kotlin のコレクションには
List
,Set
,Map
がある - コレクションは読み取り専用とミュータブルの2種類に分かれている
- ファクトリ関数を使ってインスタンスを生成する
- Java のコレクションとは相互に自動変換される
- Stream API のような内部イテレーション用のメソッドが用意されている
- なければ拡張関数で自由に追加できる
- 読み取り専用のコレクションは共変性を持つ