Kotlinコレクション入門

この記事は最終更新日から3年以上が経過しています。

Kotlinコレクション入門

by opengl-8080
1 / 45

自己紹介

  • 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 のコレクションではどうやるの?
  • 型引数で <? 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)
  • ArrayListHashMap など、 Java のコレクションのインスタンスを作る関数も用意されている
  • linkedMapOf(), sortedSetOf() などもある

Java のコレクションとの相互変換はどうする?


Java コレクションとの相互変換

  • Kotlin には、 Java の型と相互に自動変換する仕組みが用意されている
    • Java の int ⇔ Kotlin の kotlin.Int
    • Java の java.lang.String ⇔ Kotlin の kotlin.String

Java コレクションとの相互変換

  • コレクションも自動変換の対象
    • java.util.Iterablekotlin.collections.Iterable
    • java.util.Collection
      kotlin.collections.Collection
    • java.util.Listkotlin.collections.List
    • java.util.Setkotlin.collections.Set
    • java.util.Mapkotlin.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>
みたいなのはどうする?


共変

  • BA のサブタイプのときに、 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 のような内部イテレーション用のメソッドが用意されている
    • なければ拡張関数で自由に追加できる
  • 読み取り専用のコレクションは共変性を持つ

より詳細な情報は Qiita の記事を参照してください

ユーザー登録して、Qiitaをもっと便利に使ってみませんか。
  1. あなたにマッチした記事をお届けします
    ユーザーやタグをフォローすることで、あなたが興味を持つ技術分野の情報をまとめてキャッチアップできます
  2. 便利な情報をあとで効率的に読み返せます
    気に入った記事を「ストック」することで、あとからすぐに検索できます
コメント
この記事にコメントはありません。
あなたもコメントしてみませんか :)
すでにアカウントを持っている方は
ユーザーは見つかりませんでした