Skip to content

JavaとKotlinのコレクション

_コレクション_とは、解決しようとしている問題にとって重要であり、一般的に操作される、可変個(ゼロ個の場合もある)の項目のグループです。 このガイドでは、JavaとKotlinにおけるコレクションの概念と操作について説明し、比較します。 JavaからKotlinへの移行を支援し、Kotlinらしい書き方でコードを作成できるようにします。

このガイドの最初の部分には、JavaとKotlinの同じコレクションに対する操作の簡単な用語集が含まれています。 これは、JavaとKotlinで同じ操作Javaの標準ライブラリには存在しない操作に分かれています。 ガイドの2番目の部分では、ミュータビリティから始まり、具体的なケースを見ていくつかの違いを説明します。

コレクションの概要については、コレクションの概要を参照するか、Kotlin開発者アドボケイトであるSebastian Aignerによるこのビデオをご覧ください。

以下のすべての例では、JavaとKotlinの標準ライブラリAPIのみを使用しています。

JavaとKotlinで同じ操作

Kotlinには、Javaでの同等の操作とまったく同じように見えるコレクション操作が多数あります。

リスト、セット、キュー、およびデキューの操作

説明共通の操作その他のKotlinの選択肢
要素または要素群を追加するadd(), addAll()plusAssign(+=) 演算子を使用します: collection += element, collection += anotherCollection
コレクションが要素または要素群を含むか確認するcontains(), containsAll()inキーワード](collection-elements.md#check-element-existence)を使用して、演算子形式でcontains()を呼び出します: element in collection
コレクションが空か確認するisEmpty()コレクションが空でないか確認するにはisNotEmpty()を使用します。
特定の条件で削除するremoveIf()
選択された要素のみを残すretainAll()
コレクションから全ての要素を削除するclear()
コレクションからストリームを取得するstream()Kotlinには、ストリームを処理するための独自の方法があります:シーケンスmap()filter()のようなメソッドです。
コレクションからイテレータを取得するiterator()

マップの操作

説明共通の操作その他のKotlinの選択肢
要素または要素群を追加するput(), putAll(), putIfAbsent()Kotlinでは、代入map[key] = valueput(key, value)と同じように動作します。また、plusAssign(+=) 演算子も使用できます: map += Pair(key, value)またはmap += anotherMap
要素または要素群を置換するput(), replace(), replaceAll()put()replace()の代わりにインデックス演算子map[key] = valueを使用します。
要素を取得するget()インデックス演算子を使用して要素を取得します: map[index]
マップが要素または要素群を含むか確認するcontainsKey(), containsValue()inキーワードを使用して、演算子形式でcontains()を呼び出します: element in map
マップが空か確認するisEmpty()マップが空でないか確認するにはisNotEmpty()を使用します。
要素を削除するremove(key), remove(key, value)minusAssign(-=) 演算子を使用します: map -= key
マップから全ての要素を削除するclear()
マップからストリームを取得するstream() on entries, keys, or values

リストにのみ存在する操作

説明共通の操作その他のKotlinの選択肢
要素のインデックスを取得するindexOf()
要素の最後のインデックスを取得するlastIndexOf()
要素を取得するget()インデックス演算子を使用して要素を取得します: list[index]
サブリストを取得するsubList()
要素または要素群を置換するset(), replaceAll()set()の代わりにインデックス演算子を使用します: list[index] = value

少し異なる操作

任意のコレクション型に対する操作

説明JavaKotlin
コレクションのサイズを取得するsize()count(), size
ネストされたコレクション要素へのフラットなアクセスを取得するcollectionOfCollections.forEach(flatCollection::addAll) または collectionOfCollections.stream().flatMap().collect()flatten() または flatMap()
各要素に指定された関数を適用するstream().map().collect()map()
提供された操作をコレクション要素に順次適用し、累積結果を返すstream().reduce()reduce(), fold()
分類子で要素をグループ化し、カウントするstream().collect(Collectors.groupingBy(classifier, counting()))eachCount()
条件でフィルタリングするstream().filter().collect()filter()
コレクション要素が条件を満たすか確認するstream().noneMatch(), stream().anyMatch(), stream().allMatch()none(), any(), all()
要素をソートするstream().sorted().collect()sorted()
最初のN個の要素を取得するstream().limit(N).collect()take(N)
述語を持つ要素を取得するstream().takeWhile().collect()takeWhile()
最初のN個の要素をスキップするstream().skip(N).collect()drop(N)
述語を持つ要素をスキップするstream().dropWhile().collect()dropWhile()
コレクション要素とそれに関連付けられた特定の値からマップを構築するstream().collect(toMap(keyMapper, valueMapper))associate()

上記にリストされているすべての操作をマップに対して実行するには、まずマップのentrySetを取得する必要があります。

リストの操作

説明JavaKotlin
リストを自然順にソートするsort(null)sort()
リストを降順にソートするsort(comparator)sortDescending()
リストから要素を削除するremove(index), remove(element)removeAt(index), remove(element) または collection -= element
リストの全ての要素を特定の値で埋めるCollections.fill()fill()
リストから一意の要素を取得するstream().distinct().toList()distinct()

Javaの標準ライブラリには存在しない操作

zip()chunked()windowed()、およびその他の操作について深く掘り下げたい場合は、Sebastian AignerによるKotlinの高度なコレクション操作に関するこのビデオをご覧ください。

ミュータビリティ

Javaには、可変(mutable)コレクションがあります。

java
// Java
// This list is mutable!
public List<Customer> getCustomers() { ... }

部分的に可変なものもあります。

java
// Java
List<String> numbers = Arrays.asList("one", "two", "three", "four");
numbers.add("five"); // Fails in runtime with `UnsupportedOperationException`

そして、不変(immutable)なものもあります。

java
// Java
List<String> numbers = new LinkedList<>();
// This list is immutable!
List<String> immutableCollection = Collections.unmodifiableList(numbers);
immutableCollection.add("five"); // Fails in runtime with `UnsupportedOperationException`

IntelliJ IDEAで最後の2つのコードを書くと、IDEは不変オブジェクトを変更しようとしていると警告します。 このコードはコンパイルされますが、実行時にUnsupportedOperationExceptionで失敗します。コレクションが可変かどうかは、その型を見ただけでは判別できません。

Javaとは異なり、Kotlinでは必要に応じて可変または読み取り専用のコレクションを明示的に宣言します。 読み取り専用コレクションを変更しようとすると、コードはコンパイルされません。

kotlin
// Kotlin
val numbers = mutableListOf("one", "two", "three", "four")
numbers.add("five")            // This is OK
val immutableNumbers = listOf("one", "two")
//immutableNumbers.add("five") // Compilation error - Unresolved reference: add

不変性に関する詳細については、Kotlinコーディング規約のページを参照してください。

共変性

Javaでは、子孫型(descendant type)のコレクションを、祖先型(ancestor type)のコレクションを受け取る関数に渡すことはできません。 例えば、RectangleShapeを継承している場合、Rectangle要素のコレクションをShape要素のコレクションを受け取る関数に渡すことはできません。 コードをコンパイル可能にするには、? extends Shape型を使用すると、その関数はShapeの任意の子孫型を持つコレクションを受け取ることができます。

java
// Java
class Shape {}

class Rectangle extends Shape {}

public void doSthWithShapes(List<? extends Shape> shapes) {
/* If using just List<Shape>, the code won't compile when calling
this function with the List<Rectangle> as the argument as below */
}

public void main() {
    var rectangles = List.of(new Rectangle(), new Rectangle());
    doSthWithShapes(rectangles);
}

Kotlinでは、読み取り専用のコレクション型は共変(covariant)です。これは、RectangleクラスがShapeクラスを継承している場合、List<Shape>型が要求される場所であればどこでもList<Rectangle>型を使用できることを意味します。 言い換えれば、コレクション型は要素型と同じサブタイピング関係を持ちます。マップは値型に対しては共変ですが、キー型に対しては共変ではありません。 可変コレクションは共変ではありません – これは実行時エラーにつながる可能性があります。

kotlin
// Kotlin
open class Shape(val name: String)

class Rectangle(private val rectangleName: String) : Shape(rectangleName)

fun doSthWithShapes(shapes: List<Shape>) {
    println("The shapes are: ${shapes.joinToString { it.name }}")
}

fun main() {
    val rectangles = listOf(Rectangle("rhombus"), Rectangle("parallelepiped"))
    doSthWithShapes(rectangles)
}

コレクションの型についてはこちらを参照してください。

レンジとプログレッション

Kotlinでは、レンジを使用して区間を作成できます。例えば、Version(1, 11)..Version(1, 30)1.11から1.30までのすべてのバージョンを含みます。 in演算子を使用して、バージョンがレンジ内にあるか確認できます: Version(0, 9) in versionRange

Javaでは、Versionが両方の境界に適合するかを手動で確認する必要があります。

java
// Java
class Version implements Comparable<Version> {

    int major;
    int minor;

    Version(int major, int minor) {
        this.major = major;
        this.minor = minor;
    }

    @Override
    public int compareTo(Version o) {
        if (this.major != o.major) {
            return this.major - o.major;
        }
        return this.minor - o.minor;
    }
}

public void compareVersions() {
    var minVersion = new Version(1, 11);
    var maxVersion = new Version(1, 31);

   System.out.println(
           versionIsInRange(new Version(0, 9), minVersion, maxVersion));
   System.out.println(
           versionIsInRange(new Version(1, 20), minVersion, maxVersion));
}

public Boolean versionIsInRange(Version versionToCheck, Version minVersion, 
                                Version maxVersion) {
    return versionToCheck.compareTo(minVersion) >= 0 
            && versionToCheck.compareTo(maxVersion) <= 0;
}

Kotlinでは、レンジをオブジェクト全体として操作します。2つの変数を作成してVersionと比較する必要はありません。

kotlin
// Kotlin
class Version(val major: Int, val minor: Int): Comparable<Version> {
    override fun compareTo(other: Version): Int {
        if (this.major != other.major) {
            return this.major - other.major
        }
        return this.minor - other.minor
    }
}

fun main() {
    val versionRange = Version(1, 11)..Version(1, 30)

    println(Version(0, 9) in versionRange)
    println(Version(1, 20) in versionRange)
}

いずれかの境界を除外する必要がある場合(例えば、バージョンが最小バージョン以上(>=)かつ最大バージョン未満(<)であるかを確認する場合)は、これらの包括的なレンジは役に立ちません。

複数の基準による比較

Javaでは、複数の基準でオブジェクトを比較するために、Comparatorインターフェースのcomparing()関数とthenComparingX()関数を使用できます。 例えば、名前と年齢で人を比較するには:

java
class Person implements Comparable<Person> {
    String name;
    int age;

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return this.name + " " + age;
    }
}

public void comparePersons() {
    var persons = List.of(new Person("Jack", 35), new Person("David", 30), 
            new Person("Jack", 25));
    System.out.println(persons.stream().sorted(Comparator
            .comparing(Person::getName)
            .thenComparingInt(Person::getAge)).collect(toList()));
}

Kotlinでは、比較したいフィールドを列挙するだけです。

kotlin
data class Person(
    val name: String,
    val age: Int
)

fun main() {
    val persons = listOf(Person("Jack", 35), Person("David", 30), 
        Person("Jack", 25))
    println(persons.sortedWith(compareBy(Person::name, Person::age)))
}

シーケンス

Javaでは、次のように数値のシーケンスを生成できます。

java
// Java
int sum = IntStream.iterate(1, e -> e + 3)
    .limit(10).sum();
System.out.println(sum); // Prints 145

Kotlinでは、_シーケンス_を使用します。シーケンスの多段階処理は、可能な限り遅延実行されます – 実際の計算は、処理チェーン全体の最終結果が要求されたときにのみ行われます。

kotlin
fun main() {
    // Kotlin
    val sum = generateSequence(1) {
        it + 3
    }.take(10).sum()
    println(sum) // Prints 145
}

シーケンスを使用すると、一部のフィルタリング操作を実行するために必要なステップ数を削減できる場合があります。 IterableSequenceの違いを示すシーケンス処理の例を参照してください。

リストからの要素の削除

Javaでは、remove()関数は削除する要素のインデックスを受け取ります。

整数要素を削除する場合、remove()関数の引数としてInteger.valueOf()関数を使用します。

java
// Java
public void remove() {
    var numbers = new ArrayList<>();
    numbers.add(1);
    numbers.add(2);
    numbers.add(3);
    numbers.add(1);
    numbers.remove(1); // This removes by index
    System.out.println(numbers); // [1, 3, 1]
    numbers.remove(Integer.valueOf(1));
    System.out.println(numbers); // [3, 1]
}

Kotlinでは、要素削除には2つのタイプがあります。 インデックスによる削除はremoveAt()、 値による削除はremove()を使用します。

kotlin
fun main() {
    // Kotlin
    val numbers = mutableListOf(1, 2, 3, 1)
    numbers.removeAt(0)
    println(numbers) // [2, 3, 1]
    numbers.remove(1)
    println(numbers) // [2, 3]
}

マップのトラバース

Javaでは、forEachを介してマップをトラバースできます。

java
// Java
numbers.forEach((k,v) -> System.out.println("Key = " + k + ", Value = " + v));

Kotlinでは、forループまたはJavaのforEachに似たforEachを使用してマップをトラバースします。

kotlin
// Kotlin
for ((k, v) in numbers) {
    println("Key = $k, Value = $v")
}
// Or
numbers.forEach { (k, v) -> println("Key = $k, Value = $v") }

空の可能性があるコレクションの最初と最後の項目を取得する

Javaでは、コレクションのサイズを確認し、インデックスを使用することで、最初と最後の項目を安全に取得できます。

java
// Java
var list = new ArrayList<>();
//...
if (list.size() > 0) {
    System.out.println(list.get(0));
    System.out.println(list.get(list.size() - 1));
}

また、Dequeとその継承型に対して、getFirst()およびgetLast()関数を使用することもできます。

java
// Java
var deque = new ArrayDeque<>();
//...
if (deque.size() > 0) {
    System.out.println(deque.getFirst());
    System.out.println(deque.getLast());
}

Kotlinには、特別な関数firstOrNull()lastOrNull()があります。 Elvis演算子を使用すると、関数の結果に応じてすぐに追加のアクションを実行できます。例えば、firstOrNull()の場合:

kotlin
// Kotlin
val emails = listOf<String>() // Might be empty
val theOldestEmail = emails.firstOrNull() ?: ""
val theFreshestEmail = emails.lastOrNull() ?: ""

リストからセットを作成する

Javaでは、ListからSetを作成するには、Set.copyOf関数を使用できます。

java
// Java
public void listToSet() {
    var sourceList = List.of(1, 2, 3, 1);
    var copySet = Set.copyOf(sourceList);
    System.out.println(copySet);
}

Kotlinでは、toSet()関数を使用します。

kotlin
fun main() {
    // Kotlin
    val sourceList = listOf(1, 2, 3, 1)
    val copySet = sourceList.toSet()
    println(copySet)
}

要素のグループ化

Javaでは、Collectors関数groupingBy()を使用して要素をグループ化できます。

java
// Java
public void analyzeLogs() {
    var requests = List.of(
        new Request("https://kotlinlang.org/docs/home.html", 200),
        new Request("https://kotlinlang.org/docs/home.html", 400),
        new Request("https://kotlinlang.org/docs/comparison-to-java.html", 200)
    );
    var urlsAndRequests = requests.stream().collect(
            Collectors.groupingBy(Request::getUrl));
    System.out.println(urlsAndRequests);
}

Kotlinでは、groupBy()関数を使用します。

kotlin
data class Request(
    val url: String,
    val responseCode: Int
)

fun main() {
    // Kotlin
    val requests = listOf(
        Request("https://kotlinlang.org/docs/home.html", 200),
        Request("https://kotlinlang.org/docs/home.html", 400),
        Request("https://kotlinlang.org/docs/comparison-to-java.html", 200)
    )
    println(requests.groupBy(Request::url))
}

要素のフィルタリング

Javaでは、コレクションから要素をフィルタリングするために、Stream APIを使用する必要があります。 Stream APIにはintermediate操作とterminal操作があります。filter()intermediate操作であり、ストリームを返します。 コレクションを出力として受け取るには、collect()のようなterminal操作を使用する必要があります。 例えば、キーが1で終わり、値が10より大きいペアのみを残す場合:

java
// Java
public void filterEndsWith() {
    var numbers = Map.of("key1", 1, "key2", 2, "key3", 3, "key11", 11);
    var filteredNumbers = numbers.entrySet().stream()
        .filter(entry -> entry.getKey().endsWith("1") && entry.getValue() > 10)
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
    System.out.println(filteredNumbers);
}

Kotlinでは、フィルタリングはコレクションに組み込まれており、filter()はフィルタリングされたものと同じコレクション型を返します。 そのため、filter()とその述語(predicate)を書くだけで済みます。

kotlin
fun main() {
    // Kotlin
    val numbers = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key11" to 11)
    val filteredNumbers = numbers.filter { (key, value) -> key.endsWith("1") && value > 10 }
    println(filteredNumbers)
}

マップのフィルタリングに関する詳細はこちらをご覧ください。

型による要素のフィルタリング

Javaでは、型によって要素をフィルタリングしてそれらに対してアクションを実行するために、instanceof演算子で型をチェックし、その後型キャストを行う必要があります。

java
// Java
public void objectIsInstance() {
    var numbers = new ArrayList<>();
    numbers.add(null);
    numbers.add(1);
    numbers.add("two");
    numbers.add(3.0);
    numbers.add("four");
    System.out.println("All String elements in upper case:");
    numbers.stream().filter(it -> it instanceof String)
        .forEach( it -> System.out.println(((String) it).toUpperCase()));
}

Kotlinでは、コレクションでfilterIsInstance<NEEDED_TYPE>()を呼び出すだけで、型キャストはスマートキャストによって行われます。

kotlin
// Kotlin
fun main() {
    // Kotlin
    val numbers = listOf(null, 1, "two", 3.0, "four")
    println("All String elements in upper case:")
    numbers.filterIsInstance<String>().forEach {
        println(it.uppercase())
    }
}

述語のテスト

一部のタスクでは、すべての要素、どの要素も、または任意の要素が条件を満たすかをチェックする必要があります。 Javaでは、これらのチェックはすべてStream APIの関数allMatch()noneMatch()、およびanyMatch()を介して行うことができます。

java
// Java
public void testPredicates() {
    var numbers = List.of("one", "two", "three", "four");
    System.out.println(numbers.stream().noneMatch(it -> it.endsWith("e"))); // false
    System.out.println(numbers.stream().anyMatch(it -> it.endsWith("e"))); // true
    System.out.println(numbers.stream().allMatch(it -> it.endsWith("e"))); // false
}

Kotlinでは、none()any()all()という拡張関数がすべてのIterableオブジェクトで利用可能です。

kotlin
fun main() {
// Kotlin
    val numbers = listOf("one", "two", "three", "four")
    println(numbers.none { it.endsWith("e") })
    println(numbers.any { it.endsWith("e") })
    println(numbers.all { it.endsWith("e") })
}

述語のテストに関する詳細はこちらをご覧ください。

コレクション変換操作

要素をジップする

Javaでは、2つのコレクションを同時に反復処理することで、同じ位置にある要素からペアを作成できます。

java
// Java
public void zip() {
    var colors = List.of("red", "brown");
    var animals = List.of("fox", "bear", "wolf");

    for (int i = 0; i < Math.min(colors.size(), animals.size()); i++) {
        String animal = animals.get(i);
        System.out.println("The " + animal.substring(0, 1).toUpperCase()
               + animal.substring(1) + " is " + colors.get(i));
   }
}

単に要素のペアを出力するよりも複雑なことをしたい場合は、Recordsを使用できます。 上記の例では、レコードはrecord AnimalDescription(String animal, String color) {}となります。

Kotlinでは、zip()関数を使用して同じことを行います。

kotlin
fun main() {
    // Kotlin
    val colors = listOf("red", "brown")
    val animals = listOf("fox", "bear", "wolf")

    println(colors.zip(animals) { color, animal -> 
        "The ${animal.replaceFirstChar { it.uppercase() }} is $color" })
}

zip()Pairオブジェクトのリストを返します。

コレクションのサイズが異なる場合、zip()の結果はより小さい方のサイズになります。大きい方のコレクションの最後の要素は結果に含まれません。

要素を関連付ける

Javaでは、Stream APIを使用して要素を特性と関連付けることができます。

java
// Java
public void associate() {
    var numbers = List.of("one", "two", "three", "four");
    var wordAndLength = numbers.stream()
        .collect(toMap(number -> number, String::length));
    System.out.println(wordAndLength);
}

Kotlinでは、associate()関数を使用します。

kotlin
fun main() {
    // Kotlin
    val numbers = listOf("one", "two", "three", "four")
    println(numbers.associateWith { it.length })
}

次のステップ

  • Kotlin Koansにアクセスして、Kotlin構文を学ぶための演習を完了しましょう。各演習は失敗するユニットテストとして作成されており、それをパスさせることがあなたの仕事です。
  • 他のKotlinイディオムを確認してください。
  • Java to Kotlinコンバーターを使用して、既存のJavaコードをKotlinに変換する方法を学びましょう。
  • Kotlinのコレクションを発見してください。

お気に入りのイディオムがあれば、プルリクエストを送ってぜひ共有してください。