Kotlin 语言参考文档 中文版 Help

Java 和 Kotlin 中的集合(Collection)

集合 是一组可变数量(可以为 0)的元素, 解决问题时起到重要作用, 而且经常被用到. 本文解释并比较 Java 和 Kotlin 中集合的概念以及操作方式. 本文将帮助你从 Java 迁移到 Kotlin, 并以真正 Kotlin 的方式编写你的代码.

本文第 1 部分包括在 Java 和 Kotlin 中对同一个集合进行操作的快速介绍. 分为 共同的操作只存在于 Kotlin 中的操作. 本文第 2 部分, 从 可变性(Mutability) 开始, 通过例子来解释一些区别.

关于集合的介绍, 请参见 集合概述, 或观看 Sebastian Aigner 讲解的这个 视频, 他是 Kotlin 开发者 Advocate.

在 Java 和 Kotlin 中相同的操作

在 Kotlin 中, 有很多集合操作与在 Java 中的对应操作完全相同.

对 List, Set, Queue, 和 Deque 的操作

描述

共通操作

Kotlin 中的更多选择

添加一个或多个元素

add(), addAll()

使用 加然后赋值(plusAssign) (+=) 操作符: collection += element, collection += anotherCollection.

检查集合是否包含一个或多个元素

contains(), containsAll()

使用 in 关键字 以操作符的形式调用 contains() 函数: element in collection.

检查集合是否为空

isEmpty()

使用 isNotEmpty() 检查集合是否为非空.

指定条件删除

removeIf()

只保留指定的元素

retainAll()

从集合删除所有元素

clear()

从集合得到一个 Stream

stream()

Kotlin 有自己的方式来处理 Stream: 序列(Sequence), 以及方法, 比如 map()filter().

从集合得到一个 Iterator

iterator()

对 Map 的操作

描述

共通操作

Kotlin 中的更多选择

添加一个或多个元素

put(), putAll(), putIfAbsent()

在 Kotlin 中, 赋值操作 map[key] = value 的效果与 put(key, value) 相同. 你还可以使用 加然后赋值(plusAssign) (+=) 操作符: map += Pair(key, value)map += anotherMap.

替换一个或多个元素

put(), replace(), replaceAll()

使用下标访问操作符 map[key] = value, 而不是 put()replace().

得到元素

get()

使用下标访问操作符得到元素: map[index].

检查 Map 是否包含一个或多个元素

containsKey(), containsValue()

使用 in 关键字 以操作符形式调用 contains() 函数: element in map.

检查 Map 是否为空

isEmpty()

使用 isNotEmpty() 检查 Map 是否为非空.

删除元素

remove(key), remove(key, value)

使用 减然后赋值(minusAssign) (-=) 操作符: map -= key.

从 Map 删除所有元素

clear()

从 Map 得到一个 Stream

entries, keys, 或 values 的 stream() 函数

只对 List 有效的操作

描述

共通操作

Kotlin 中的更多选择

得到元素下标

indexOf()

得到元素的最后下标

lastIndexOf()

得到元素

get()

使用下标访问操作符得到元素: list[index].

获取一个子 List

subList()

替换一个或多个元素

set(), replaceAll()

使用下标访问操作符, 而不是 set(): list[index] = value.

略有不同的操作

对任何集合类型都有效的操作

描述

Java

Kotlin

得到集合的大小

size()

count(), size

平展访问(Flat Access) 嵌套的集合元素

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()

构建从集合元素到关联值的 Map

stream().collect(toMap(keyMapper, valueMapper))

associate()

要对 Map 执行上述所有操作, 你首先需要得到 Map 的 entrySet.

对 List 的操作

描述

Java

Kotlin

按照自然顺序排序 List

sort(null)

sort()

按照逆序排序 List

sort(comparator)

sortDescending()

从 List 删除元素

remove(index), remove(element)

removeAt(index), remove(element)collection -= element

将 List 的所有元素填充为指定的值

Collections.fill()

fill()

从 List 得到不重复的元素

stream().distinct().toList()

distinct()

在 Java 标准库中不存在的操作

如果你想要深入了解 zip(), chunked(), windowed(), 以及其他一些操作, 请观看 Sebastian Aigner 讲解的这个视频, 关于Kotlin 中集合的高级操作:

可变性

在 Java 中, 有可变的集合:

// Java // 这个 List 是可变的! public List<Customer> getCustomers() { ... }

也有部分可变的集合:

// Java List<String> numbers = Arrays.asList("one", "two", "three", "four"); numbers.add("five"); // 在运行时刻会发生 `UnsupportedOperationException` 错误

还有不可变的集合:

// Java List<String> numbers = new LinkedList<>(); // 这个 List 是 不可变的! List<String> immutableCollection = Collections.unmodifiableList(numbers); immutableCollection.add("five"); // 在运行时刻会发生 `UnsupportedOperationException` 错误

如果你在 IntelliJ IDEA 中编写后面两段代码, IDE 会提出警告, 告诉你正在修改不可变的对象. 这段代码能够编译, 并在运行时刻发生 UnsupportedOperationException 错误. 你不能通过集合的类型判断它是否可变.

与 Java 不同, 在 Kotlin 中, 你会根据需要明确声明可变的或只读的集合. 如果你试图修改只读集合, 代码将会无法编译:

// Kotlin val numbers = mutableListOf("one", "two", "three", "four") numbers.add("five") // 这是正确的 val immutableNumbers = listOf("one", "two") //immutableNumbers.add("five") // 编译错误 - 无法解析的引用: add

关于可变性, 详情请参见 Kotlin 编码规约.

协变(Covariance)

在 Java 中, 如果函数的参数是祖先类型元素的集合, 那么你不能传递一个后代类型元素的集合. 比如, 如果 Rectangle 继承 Shape, 对于参数是 Shape 元素集合的函数, 你不能传递 Rectangle 元素类型的集合. 要让代码能够编译, 需要使用 ? extends Shape 类型, 才能让函数接受从 Shape 继承的后代类型元素的集合:

// Java class Shape {} class Rectangle extends Shape {} public void doSthWithShapes(List<? extends Shape> shapes) { /* 如果只使用 List<Shape>, 那么如下面的例子那样, 使用 List<Rectangle> 作为参数调用 这个函数时, 代码将无法编译 */ } public void main() { var rectangles = List.of(new Rectangle(), new Rectangle()); doSthWithShapes(rectangles); }

在 Kotlin 中, 只读集合类型是 协变的(Covariant). 因此, 如果 Rectangle 类继承自 Shape 类, 那么在要求 List<Shape> 类型的地方, 你可以使用 List<Rectangle> 类型. 也就是说, 集合类型之间的子类型关系与元素类型之间相同. Map 根据 value 类型协变, 而不是根据 key 类型. 可变的集合不是协变的 – 否则会导致运行时错误.

// 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) }

详情请参见 集合类型.

值范围(Range)与数列(Progression)

在 Kotlin 中, 你可以使用 值范围(Range) 创建数值范围. 比如, Version(1, 11)..Version(1, 30) 包括从 1.111.30 的所有版本. 你可以使用 in 操作符检查你的版本是否在范围中: Version(0, 9) in versionRange.

在 Java 中, 你需要手动检查一个 Version 是否在边界条件之内:

// 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 中, 你可以将值范围当作整个对象来操作. 你不需要创建两个变量, 并分别与 Version 进行比较:

// 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() 函数 . 比如, 要按照姓名和年龄比较人:

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 中, 你只需要列举你希望比较的属性:

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))) }

序列(Sequence)

在 Java 中, 你可以这样生成一个数值序列(Sequence):

// Java int sum = IntStream.iterate(1, e -> e + 3) .limit(10).sum(); System.out.println(sum); // 输出结果为 145

在 Kotlin 中, 请使用 序列(Sequence). 对序列的多个步骤处理会尽可能延迟执行 – 只有在需要整个处理串的结果时, 实际的计算才会发生.

fun main() { //sampleStart // Kotlin val sum = generateSequence(1) { it + 3 }.take(10).sum() println(sum) // 输出结果为 145 //sampleEnd }

对于一些过滤操作, 序列可能会减少需要执行的步骤. 详情请参见 序列的处理示例, 这篇文档会演示 IterableSequence 的区别.

从 List 删除元素

在 Java 中, remove() 函数接受需要删除的元素下标.

要删除整数元素时, 使用 Integer.valueOf() 函数作为 remove() 函数的参数:

// Java public void remove() { var numbers = new ArrayList<>(); numbers.add(1); numbers.add(2); numbers.add(3); numbers.add(1); numbers.remove(1); // 这里根据下标删除元素 System.out.println(numbers); // [1, 3, 1] numbers.remove(Integer.valueOf(1)); System.out.println(numbers); // [3, 1] }

在 Kotlin 中, 有 2 种类型的元素删除函数: 根据下标删除 removeAt(), 以及根据值删除 remove().

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

遍历 Map

在 Java 中, 你可以通过 forEach 来遍历 Map:

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

在 Kotlin 中, 请使用 forforEach 循环, 类似于 Java 的 forEach, 来遍历 Map:

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

从可能为空的集合得到第 1 个和最后 1 个元素

在 Java 中, 你可以检查集合大小, 并使用下标安全的得到第 1 个和最后 1 个 元素:

// 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 var deque = new ArrayDeque<>(); //... if (deque.size() > 0) { System.out.println(deque.getFirst()); System.out.println(deque.getLast()); }

在 Kotlin 中, 有专门的函数 firstOrNull()lastOrNull(). 使用 Elvis 操作符, 你可以根据函数结果执行更多操作. 比如, firstOrNull():

// Kotlin val emails = listOf<String>() // 可能为空 val theOldestEmail = emails.firstOrNull() ?: "" val theFreshestEmail = emails.lastOrNull() ?: ""

从 List 创建 Set

在 Java 中, 要从 List 创建 Set, 你可以使用 Set.copyOf 函数:

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

在 Kotlin 中, 使用函数 toSet():

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

对元素分组

在 Java 中, 你可以使用 Collectors 函数 groupingBy(), 对元素分组:

// 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():

class Request( val url: String, val responseCode: Int ) fun main() { //sampleStart // 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)) //sampleEnd }

过滤元素

在 Java 中, 要过滤集合的元素, 你需要使用 Stream API. Stream API 包括 中间(Intermediate)终止(Terminal) 操作. filter() 是一个中间操作, 返回一个 Stream. 要得到输出的集合, 你需要使用终止操作, 比如 collect(). 比如, 要只保留 key 以 1 结尾并且 value 大于 10 的对:

// 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() 以及它的过滤条件:

fun main() { //sampleStart // 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) //sampleEnd }

详情请参见 过滤 Map.

根据类型过滤元素

在 Java 中, 要根据类型过滤元素, 并对其执行操作, 你需要使用 instanceof 操作符检查元素类型, 然后进行类型转换:

// 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 fun main() { //sampleStart // Kotlin val numbers = listOf(null, 1, "two", 3.0, "four") println("All String elements in upper case:") numbers.filterIsInstance<String>().forEach { println(it.uppercase()) } //sampleEnd }

验证判定条件

一些任务要求你检查是否所有元素, 不存在元素, 或存在某些元素符合某个条件. 在 Java 中, 你可以通过 Stream API 函数 allMatch(), noneMatch(), 和 anyMatch() 执行所有这些检查:

// 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 中, 对所有的 Iterable 对象, 可以使用 扩展函数 none(), any(), and all():

fun main() { //sampleStart // 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") }) //sampleEnd }

详情请参见 验证判定条件.

集合变换操作

合并(Zip)元素

在 Java 中, 你可以同时遍历两个集合, 将同一位置的两个元素变换为 pair :

// 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)); } }

如果你希望做某些更加复杂的操作, 而不仅仅是将元素 pair 打印输出, 你可以使用 Record. 在上面的示例中, Record 是 record AnimalDescription(String animal, String color) {}.

在 Kotlin 中, 使用 zip() 函数可以完成相同的功能:

fun main() { //sampleStart // 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" }) //sampleEnd }

zip() 返回 Pair 对象组成的 List.

关联(Associate)元素

在 Java 中, 你可以使用 Stream API 将元素与某个特性关联在一起:

// 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() 函数:

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

下一步做什么?

如果你有喜欢的惯用法, 欢迎你发送一个 pull request, 分享给我们.

最终更新: 2024/10/17