集合(Collections)
Kotlin的集合类型和Java不一样,Kotlin的集合分为可变(读写)和不可变(只读)类型(lists, sets, maps, etc),可变类型是在不可变类型前面加Mutable
:
-
List<out E>
和MutableList<E>
-
Set<out E>
和MutableSet<E>
-
Map<K, out V>
和MutableMap<K, V>
举个栗子:
val numbers: MutableList<Int> = mutableListOf(1, 2, 3)
val readOnlyView: List<Int> = numbers
println(numbers) // prints "[1, 2, 3]"
numbers.add(4)
println(readOnlyView) // prints "[1, 2, 3, 4]"
// readOnlyView.clear() // 报错
val strings = hashSetOf("a", "b", "c", "c")
assert(strings.size == 3)
为什么加了Mutable
前缀就是可变类型?
我们先来看List,List实现了Collection接口,而MutableList实现的是List和MutableCollection接口
可以看出,MutableCollection接口实现了Collection接口,并且在里面添加了add
和remove
等操作方法,
所以加了Mutable
前缀就是可变类型,而没有的就是不可变类型。
创建集合
Kotlin没有用于创建列表或集合的专用语法结构,只能使用标准库中的方法:listOf()
, mutableListOf()
, setOf()
, mutableSetOf()
,mapOf()
等等。
// 等同于Java的List<String> list = new ArrayList();
val list: List<String> = arrayListOf()
val mutableList: MutableList<String> = mutableListOf()
// 也可以这样写
val list2 = mutableListOf<String>()
val mutableList2 = mutableListOf<String>()
// 等同于Java的Map<String, String> map = new HashMap();
val map: Map<String, String> = mapOf()
val mutableMap: MutableMap<String, String> = mutableMapOf()
// 也可以这样写
val map2 = mapOf<String, String>()
val mutableMap2 = mutableMapOf<String, String>()
其中mapOf()
可以简写成mapOf(a to b, c to d)
val map: Map<Int, Int> = mapOf(1 to 1, 2 to 2)
范围(Ranges)
范围表达式由rangeTo
函数形成,操作符形式为..
,由in
和!in
进行连接,可以用于判断,也可用于循环当中:
if (i in 1..10) { // i in 1..10 等同于1 <= i && i <= 10
println(i)
}
为什么..
能够替换rangeTo
函数,这就涉及到运算符的重载了。
运算符重载
Kotlin提供了一些运算符,这些运算符有固定的符号表示形式(如+
或-
),运算符重载要在函数加operator
关键字,在使用直接用运算符来替换函数,如..
替换rangeTo
函数:
if (i in 1..10) {
println(i)
}
// 等同与
if (i in 1.rangeTo(10)) {
println(i)
}
常见运算符
如+
、%
、in
、==
等等,都是运算符重载。
前缀运算符
递增和递减
算术运算符
in
和!in
运算符
索引访问操作符
调用运算符
赋值(Augmented assignments)
注意:赋值在 Kotlin 中不是表达式。
等式运算符
注意:===
和!==
没有被重载
比较运算符
针对Int
类型,是去调用compareTo
函数来比较
范围(Ranges)
范围表达式由rangeTo
函数形成,操作符形式为..
,由in
和!in
进行连接,可以用于判断,也可用于循环当中:
if (i in 1..10) { // i in 1..10 等同于1 <= i && i <= 10
println(i)
}
简写
(1..10).forEach { print(it) }
为什么..
能够替换rangeTo
函数,这就涉及到运算符的重载了。
运算符重载
运算符重载要在函数加operator
关键字,
Ranges是如何运行的
Ranges包含三种IntRange
,LongRange
,CharRange
,都是实现了通用接口ClosedRange<T>
ClosedRange<T>
表示在数学意义上的闭区间,定义为可比较类型,具有两个端点start
和endInclusive
,以及包含的范围,用in
和!in
来进行操作。
Ranges在实现ClosedRange<T>
的同时,也分别继承了相对应的运算进度类,如IntRange
继承了IntProgression
类,由first
元素,last
元素和不为零的step
来定义,第一个元素是first
元素,后面的元素是上一个元素加上step
的结果。
Progression
进度类是Iterable<T>
的子类,T
包括Int
、Long
和 Char
三种类型,这就是Ranges可以用于for
循环和map
/filter
等函数。
在Progression
进度类中,有个伴生对象里面的函数fromClosedRange
用来构建进度。
总结下,..
操作符创建一个实现了ClosedRange <T>
和继承了*Progression
的对象。例如IntRange
实现了ClosedRange <Int>
接口,并继承了IntProgression
类,因此为IntProgression
里面的所有操作都可用在IntRange
。
常用函数
Ranges的函数基本都是扩展函数。
rangeTo()
默认的rangeTo
是正序的,如果将1..10
改为10..1
的话,上面的循环是不会执行的:
for (i in 10..1) {
println(i) // prints nothing
}
浮点数(Float
和Double
)不能使用rangeTo
操作,而是使用提供的通用Comparable
类型的标准库:
public operator fun <T: Comparable<T>> T.rangeTo(that: T): ClosedRange<T>
该方法不能用于循环迭代(for
循环等等)。
downTo()
downTo()
在是扩展函数的同时,也是中缀函数,使用downTo()
函数控制倒序输出:
for (i in 10 downTo 1) {
println(i) // 打印10987654321
}
简写
(10 downTo 1).forEach { print(it) } // 打印10987654321
step()
step()
也是中缀函数,默认间隔长度是1,如果要自定义间隔长度的话,就要使用step()
函数:
for (i in 1..4 step 2) print(i) // 打印"13"
for (i in 4 downTo 1 step 2) print(i) // 打印"42"
简写
(1..4 step 2).forEach { print(it) } // 打印"13"
(4 downTo 1 step 2).forEach { print(it) } // 打印"42"
until()
until()
也是中缀函数,要创建不包含其终端元素的范围,可以使用until()
函数:
for (i in 1 until 10) { // i in [1, 10), 打印123456789
println(i)
}
简写
(1 until 10).forEach { print(it) } // 打印123456789
reversed()
reversed()
函数,功能同单词,反向,由于reversed()
并不是中缀函数,所以写法跟上面的那些不一样:
for (i in (1..4).reversed())
print(i) // print 4321
简写
(1..4).reversed().forEach { print(it) } // print 4321