Kotlin For循环详解

5,596 阅读8分钟

前言

从今年的4月开始入手Kotlin到现在也有几个月时间了,在Kotlin当中循环算是这个语言当中的一个特点,最近Android上用Canvas在做游戏开发,循环也是写的非常多,发现在编写多变量的For循环无从下手,最后要不就是用Java写Kotlin调用,要不就是用While循环写,终于在今天我是解决了这个痛点,所以就打算写篇文章记录一下。

基本使用

在正式学习之前咱们先从最简单的For循环开始

从0循环到100

until关键字

until关键字在使用时左侧和右侧都需要一个数值,until循环是一个左闭右开区间

for (i in 0 until 100) {
    println(i) // 输出: 0 ~ 99
}

最终的输出结果包含0不包含100

Xnip2021-08-25_19-17-02.jpg

使用..

..在Kotlin中也是有含义的,它称为区间表达式,它表示一个左闭右闭区间,和until关键字一样在使用的时候左侧和右侧都需要一个数值

for (i in 0..100) {
    println(i) // 输出: 0 ~ 100
}

zeroPointOneHundred.jpg 最终的输出结果包含0也包含100

补充

其实..关键字也可以在直接声明成一个变量,这个变量就表示一个区间。当循环的时候 循环这个变量就会循环存储在当中的区间

val test = 0..100

注意: 使用..创建的区间只能是一个升序区间,也就是从小到大,不能从大到小,如果想从大到小需要使用另外一个关键字downTo

从100循环到0

downTo关键字

Kotlin当中降序循环使用downTo关键字,一样它也是左右侧都需要一个数值

for (i in 100 downTo 0) {
    println(i) // 输出: 100 ~ 0
}

100downto0.jpg

你会突然发现为啥是100 ~ 0而不是100 ~ 1,其实早在刚在介绍..关键字的末尾就说了。在降序循环方面downTo关键字生成的是一个左闭右闭区间

step关键字

Kotlin在循环的时候默认步长为1,想要改变步长可以使用step关键字

从0循环到10设置步长为2

for (i in 0..10 step 2) {
    print("${if (i == 10) i else "$i--"}") // 输出: 0--2--4--6--8--10
}

step.jpg

循环数组 or 列表

上述表示都是如何从一个数循环到另外一个数,实际很多情况下用到循环是拿到ArrayList这种容器里面的值。在Kotlin当中循环这些容器的操作都是很简单的

循环一个数组 or 列表

篇幅原因这里就不贴代码结果图了,专家们可以自己拿以下代码去试试

定义一个数组

val arrayList = arrayListOf(1, 2, 3, 4, 5, 6, 12, 31, 23, 123, 12, 3, 123, 2)

直接取出元素

for (i in arrayList) {
    println(i) // 输出: 1,2,3,4....2
}

同时循环取出索引 and 元素

同时取出元素的索引需要在循环时调用容器的withIndex()方法

for ((index, item) in arrayList.withIndex()) {
    println("index: $index, item: $item") // 输出: index: 0, item: 1....index: 13, item: 2
}

通过索引遍历一个数组 or 列表

有一些情况通过索引遍历,就可以这么写

for (i in 0 until arrayList.size) {
    println(arrayList[i]) // 输出: 1,2,3,4....2
}

这时候你会发现你的Idea会给你一个黄线警告,因为Kotlin这种情况已经给你提供好了一个扩展方法indices

for (i in arrayList.indices) {
    println(arrayList[i]) // 输出: 1,2,3,4....2
}

一个小说明

kotlin的for循环其实远不止去遍历数组或列表,基本日常开发用到的数据容器都可以循环遍历(只要这个容器实现了Iterable接口),篇幅原因就不继续了,专家们可以自己去试试

多变量循环

还记得前言里我说的痛点是啥吗,就是这个多变量循环,java中多变量的for循环转化成Kotlin会直接转化成while的语法,但是while会导致代码行数暴增,可读性也不太好,还创建了局部变量。

互相转化

来看一段Java多变量for循环的代码

// 转化前java
for (int i = 0, j = 100; i < 100 && j > 0; i++, j--) {
    System.out.println("i: " + i + ", j: " + j); // 输出: i: 0, j: 100 ~ i: 99, j: 1
}

这段代码如果让你改成Kotlin的for循环,怎么改?先不急,先转化成kotlin看看转化后是啥样的

// 转化后kotlin
var i = 0
var j = 100
while (i < 100 && j > 0) {
    println("i: $i, j: $j") // 输出: i: 0, j: 100 ~ i: 99, j: 1
    i++
    j--
}

这里Kotlin直接给我们转化成了while,弊端呢也说过了,就不赘述了。直接上Kotlin for的代码

for ((i, j) in (0 until 100).zip(100 downTo 0)) {
    println("i: $i, j: $j") // 输出: i: 0, j: 100 ~ i: 99, j: 1
}

执行结果你会发现这三个循环输出都是一样的,篇幅原因就不分别上图了,现在就介绍一下Kotlin多变量循环

定义多变量

在Kotlin的for当中呢 in关键字的前面通常用来定义循环的变量,或者用来表示被循环容器的索引or每一项元素,这里不考虑被循环容器,只考虑定义循环的变量。当需要定义多个变量时在 () 内部进行定义,每一位变量通过逗号分隔(英文逗号)。in关键字后面呢就是设置循环区间了

for((i,j)  in...){
}

设置循环区间

不知道你有没有发现,其实在kotlin的for循环定义循环变量的时候都没有给它赋予初始值,而是直接指定了它需要循环的区间以及是降序循环还是正序循环,所以上面描述的in循环前面是定义的变量而不是初始值

现在来给上述定义好的循环变量设置循环区间,多个循环区间使用 .zip() 函数进行设置

for ((i, j) in (0 until 10).zip(1 until 10)) {
    println("i: $i, j: $j")
}

上述代码含义 i的区间为0 ~ 10的升序j的区间为1 ~ 10的升序

我们来输出看看结果

result.jpg 没看错吧,i的难道不应该是0 ~ 9吗,为什么到8结束了?别急,咱们先看j的循环区间1 ~ 10,没问题吧 until左闭右开,这里的j循环次数要比i循环次数少一次,所以结束了。你可能会很懵,为什么结束了难道不应该i要输出到9吗,别急,我们同样的代码用java写

for (int i = 0, j = 1; i < 10 && j < 10; i++, j++) {
    System.out.println("i:" + i + ", j:" + j);
}

result_java.jpg 输出结果一致,配合java一起这块代码应该就理解起来没问题了

咱们继续

三个变量 and 四个变量

有时候循环的变量有三个或者四个,这时候你会发现又无从下手了,尝试在zip().zip(),会发现给你报个错误

for((i,j,k) in (0..100).zip(0..100).zip(0..100)){} // 错误代码

Kotlin: Destructuring declaration initializer of type Pair<Pair<Int, Int>, Int> must have a 'component3()' function

大意就是你不能这么写,三个变量或四个变量或者更多的时候你需要使用Pair类型的值作为循环变量,Pair是一个二分元组,可以把多个值同时赋给一个变量,或者同时给多个变量赋值,有二分就还有三分元组,三分元组Triple,不过在我的学习过程中没有用上三分,貌似现在的for循环最多也只支持也二分元组 (zip()返回二分元组),如果有不对的地方欢迎知道详情的大佬补充指正,我也会第一时间修改本文章

好了那我们就开始吧

三个变量

其实改变很少,in前面依旧是两个变量,不过其中一个存储的是元组,元组内部存储着值

先上代码
for ((i, jPair) in (1..100).zip((0..100).zip(0..100)) ) {
    println("i: $i,  $jPair")
}

你会发现调用两个zip函数,好像咱们还没有介绍zip函数的作用蛤,船到桥头自然直,先看输出结果

two_score_for.jpg 你会发现输出的后面jPair变成了一个对象,其实这个时候jPair已经成了元组对象,输出对象,对象里面有两个值,我们直接输出是这样的,如果我们想要拿到其中一个值呢?这么写

for ((i, jPair) in (1..100).zip((0..100).zip(0..100))) {
    println("i: $i |  jPair.firstValue: ${jPair.first} |  jPair.secondValue: ${jPair.second}")
}

输出结果: result_two_array.jpg 二分元组只能存储两个值,两个值都可以通过 对象.first and 对象.second来获取,现在我们再来介绍zip。

zip()介绍

Returns a list of pairs built from the elements of this collection and other collection with the same index. The returned list has length of the shortest collection.

上述的英文是在zip()函数的注释中截取下来的,大致意思就是返回索引最小的集合作为索引,两个集合取它们的交集。

是不是看完觉得很懵,没错我也一样,咱们不看注释了直接来看看源码接收的参数还有返回的参数吧

public infix fun <T, R> Iterable<T>.zip(other: Iterable<R>): List<Pair<T, R>> {
    return zip(other) { t1, t2 -> t1 to t2 }
}

接收的参数需要是一个实现了Iterable接口的类型,正好在Kotlin中常用的数据存储结构其实都已经实现了这个接口,同时咱们的区间表达式也是支持的。

在看返回值,返回值返回了一个包含元组对象的列表,接下来你就可以再看看三个变量的for循环,是不是一目了然了

四个变量

四个变量就这么写,细节我也不继续赘述了,和三个变量就只有一点点区别而已

for ((iPair, jPair) in ((1..100).zip(1..100)).zip((0..100).zip(0..100))) {
    println("iPair: $iPair |  jPair: $jPair")
}

执行结果:

four.jpg 要拿取元组对象里面的值上述也讲过了,这里就不在重复了。

小结

总体来说在多变量循环的时候Kotlin还是比较复杂的,但是如果熟悉语法其实看上去还是非常简洁的,不过日常开发中很少会涉及到多变量循环,这里也只是提一嘴其实kotlin只能做到的。如果没有明白就自己写写例子,会更加清楚zip()函数的用法以及for循环在多变量的时候执行流程

循环控制

有时候我们需要对循环进行一些控制,kotlin在这方面也有它的独特之处

结构跳转表达式

和大多数语言一样Kotlin的for循环也是两个结构跳转表达式:

  • break : 终止距离最近的循环
  • continue : 跳出最近的一次循环
for (i in 0 until 10) {
    if (i == 5) break // 输出: 0 ~ 4
}

for (i in 0 until 10) {
    if (i == 5) continue // 输出: 0 ~ 9
}

标签命名

标签命名算是Kotlin 循环当中很有特色的一个点了,我们可以给循环设立一个标签,然后通过结构跳转表达式操作标签对应的循环。

命名格式为: 标签名@

跳出指定标签可以这么写: break@标签名 or continue@标签名

all@ for (i in 0 until 100) {
    inner@ for (j in 100 downTo 0) {
        if (i == 60) break@inner
        if (j == 60) continue@all
        println("j: $j")
    }
    println("i: $i")
}

大家可以自己去试试,这里也就不贴执行结果了

总结

Kotlin的For循环我觉得总结的还算比较到位,从简单使用到多变量、遍历常用的容器,以及循环控制都总结到了。文章中如果你觉得哪里写的有问题欢迎指正,这也是我的第一篇文章,可能一些知识点描述的不是很好,不过我相信配合代码自己动手试试应该很快能理解我想表达的意思。最后谢谢您的阅读!