在 Kotlin 集合(列表、数组和映射(中,要获得大小,我们已经有size
.count()
有什么用?
val list = listOf(1, 2, 3)
list.size
list.count()
val map = mapOf(1 to 1, 2 to 2, 3 to 3)
map.size
map.count()
val array = arrayOf(1, 2, 3)
array.size
array.count()
从本质上讲,如果我们在引擎盖下检查它们,它们只会返回size
.
size
和count()
来自不同的接口。size
定义在kotlin.collections.Collection
上,List
和Set
都实现了:
public interface Collection<out E> : Iterable<E> {
// Query Operations
/**
* Returns the size of the collection.
*/
public val size: Int
...
而count()
是在kotlin.collections.Iterable
上定义的扩展方法(Collection
继承自(:
/**
* Returns the number of elements in this collection.
*/
public fun <T> Iterable<T>.count(): Int {
if (this is Collection) return size
var count = 0
for (element in this) checkCountOverflow(++count)
return count
}
因此,仅从Iterable
继承就会自动Collection
(包括List
s和Set
s(提供count()
方法。我们绝对希望Collection
从Iterable
继承,以便我们可以将Collection
传递给将接受任何Iterable
的方法。然后,最后,Map
(不是Collection
(为了与List
和Set
保持一致,也同时获得了size
属性和count()
方法。
从技术上讲,我想这已经回答了为什么集合具有count()
方法的问题,但这还不是一个令人满意的解释,因为它引发了一个后续问题:那么,为什么Collection
具有size
属性?为什么标准库开发人员不费心添加size
,而是让我们在Collection
上调用count()
?
上面引用的count()
的实施向我们展示了这个问题的答案:size
是一种性能优化。当调用 n 个元素的非Collection
Iterable
时,count()
具有O(n(时间复杂度,因为它循环遍历可迭代对象中的所有元素。当调用Collection
时,它应该具有O(1( 时间复杂度,因为它只是顺从size
属性(希望是O(1(来评估!
接下来我们可能会问:这种优化可以通过其他方式实现吗?标准库开发人员不是用额外的属性使 API 复杂化,难道不能简单地让他们实现的所有Collection
都提供自己的count()
成员函数,时间复杂度为 O(1(吗?
不。因为扩展是静态解析的 - 所以如果你没有size
,而是在List
、Map
、Set
等上提供了 O(1(count()
的实现,那么当你将List
或Map
或Set
传递给将任意Iterable
作为参数并调用其count()
方法的函数时,这些O(1(实现将不会被使用;相反,调用将静态解析为O(n(扩展方法。
因此,size
和count()
都存在是能够在所有Iterable
上提供count()
的唯一方法,同时也使其在Collection
秒内为O(1(。
最后,如果您想知道为什么count()
是一个方法而size
是一个属性,编码约定提供了一个线索:
函数与属性 在某些情况下,没有参数的函数可能与只读属性互换。尽管语义相似,但对于何时首选一个而不是另一个有一些风格约定。
当基础算法:
不抛
计算成本低(或在第一次运行时缓存(
如果对象状态未更改,则在调用时返回相同的结果
(粗体我的(。由于count()
计算成本(可能(昂贵,但size
便宜,因此count()
应该成为一种方法并size
属性 - 这确实是标准库开发人员所做的。