本文来自艾叔编著的《零基础快速入门Scala》免费电子书,添加文末艾叔微信,获取完整版的PDF电子书
第4章 Scala基本控制结构
本章介绍Scala基本控制结构,正常情况下,Scala代码是一条条向下,顺序执行的,但很多情况下,需要根据条件,来决定程序的运行路径,这就需要控制结构。同基本数据类型一样,控制结构也是编程语言的重要组成部分。Scala的基本控制结构包括:if语句、case语句、for循环、while循环等,它们可以控制程序走不同的路径,或者完成不同的动作,具体如下。
4.1 if语句
if语句的使用例子如下:if判断条件表达式printHello的值,如果为true,则进入第3行所在的分支执行,否则,进入第5行所在的分支执行。
1 var printHello=true
2 if(printHello){
3 println("Hello!")
4 }else{
5 println("World!")
6 }
注意下面的用法,这是C/C++中的常用做法,对变量printHello赋值,然后判断printHello的值,来决定执行哪个分支。
1 var printHello=true
2 if(printHello=false){
3 println("Hello!")
4 }else{
5 println("World!")
6 }
但是,上述代码,在Scala中是有问题的,如果执行,会报下面的错误:
<console>:13: error: type mismatch;
found : Unit
required: Boolean
这是因为,printHello=false并不会像C/C++那样,返回printHello的值,然后由if再来判断printHello的值是否为真。Scala中,printHello=false是一个表达式,这个表达式的类型是Unit,它不是Boolean型的,因此报错。
& if、while中的条件表达式一定要注意其返回值。
4.2 match-case(模式匹配)
1. Match-case基本使用,值匹配
和if语句相比,match-case可以将各种判断列出来,程序代码在逻辑上更清晰,而且扩展更方便,不容易出错。case和match搭配一起用,示例代码如下.
1 inpuType match {
2 case 0 => println("input type is 0")
3 case 1 => println("input type is 1")
4 case 2 => println("input type is 2")
5 case _ => println("input type is other")
6 }
inputType是一个Int类型的变量,通过match-case来判断inputType的值,然后做出不同的动作,如果是0,则打印“input type is 0”,如果是1,则打印“input type is 1”,下划线_表示其它所有的情况。
case匹配后会直接跳出,例如,inputType的值为0时,Scala语言匹配到0,直接打印“input type is 0”,然后退出match-case。如果是C/C++/Java(使用switch-case),如果不在println语句后面加上break的话,则还会依次去匹配1、2等,这样就可能造成逻辑混乱。
如果要在匹配后,执行多条语句,只需在这些语句前后加上大括号即可,示例代码如下所示。
1 case 0 => {
2 println("input type is 0.1")
3 println("input type is 0.2")
4 }
2. class匹配与值抽取
match-case可以判断引用的类型,同时 还能从被match对象中,抽取值到变量中。示例代码如下。
1 var inputType:Any = null
2 //inputType = "Hello World!"
3 inputType = 10
4 inputType match {
5 case str:String => {
6 println("inputType is String")
7 println(str)
8 }
9
10 case num:Int => {
11 println("inputType is Int")
12 println(num)
13 }
14
15 case _ => println("other type")
16 }
上面的代码对inputType引用进行类型判断,inputType被声明为Any类型,Any是所有class的父类,因此,所有子类的值都可以赋给它,第2行是将String类型的引用赋值给inputType,而第3行则是将Int类型的引用赋值给inputType,因为String和Int都是Any的子类,因此,这样的赋值是没有问题的。第4~16行,判断inputType的类型,根据不同的类型做不同的动作,其中第5~8行匹配String类型,如果inputType是String类型,就会将inputType赋值给str,然后执行第5行起到第8行结束的大括号中的语句,在这段语句中,可以引用str,例如打印str等;第10~13行,则是匹配Int类型,一旦匹配,则将inputType赋值给num,然后执行第10行到第13中大括号内部的语句,同样可以使用num。
下面再来看一个更深入的例子,来体会一下Scala中match-case的强大。下面的代码对person的类型进行判断,分别判断person的类型是普通的person、teacher还是student,然后根据不同的类型,抽取不同的值到变量中,进行相应处理,示例代码说明如下。
第1行声明person为Any类型,这是为后续给person赋其它子类的值做准备;
第2~4行,是分别给person赋不同类型的值,第2行是普通person,它只有1个值,就是名字;第3行是teacher,它有2个值,名字和工作;第4行是student,它有3个值,名字、工作和id;
& Scala可以将多个类型的值组合在一起,构成一个新的对象,在scala中称之为元组(Tuple),例如(“rose”,”student”,”123456”),就是将String,String和Int三种类型的值组合在一起,构成一个新的对象,元组中的每个元素,可以使用下划线加数字来引用,person中第一个元素,就是person._1,第二个元素就是person._2,第三个元素就是person._3。
第5~10行,对person进行匹配,如果person中有2个元素,则执行第7行的=>后面的语句,在语句中,可以使用teacher._1来引用元组中的第一个元素,使用teacher._2来引用元组中的第二个元素;同样的,如果person中有3个元素,则执行第8行=>后面的语句。
1 var person:Any = null
2 //person = ("mike")
3 person = ("tom", "teacher")
4 //person = ("rose", "student", "123456")
5 person match {
6 case per:(String) => println("name " + per)
7 case teacher:(String, String) => println("name " + teacher._1 + " ocupation " + teacher._2)
8 case stu:(String, String, Int) => println("name " + stu._1 + " ocupation " + stu._2 + " id " + stu._3)
9 case _ => println("other type")
10 }
3. match的返回值
match-case语句是有返回值的,其返回值就是=>右边语句段中,最后一句的返回值。例子如下:
1 val input: Int = 1
2 val rs = input match {
3 case 0 => "num 0"
4 case 1 => "num 1"
5 case _ => "other value"
6 }
7 println("rs " + rs)
执行代码的话,会打印:
rs num 1
& 1. 如果=>后面返回的值有多种类型,例如,case 0后面的返回值是Int,而case 1后面的返回值是String,那么match返回值是Any类型;
& 2. match其实是一个方法,在IDEA中,输入input变量,后面跟一个点(.),就会出现match
& 3. 利用match的返回值,不需要在case语句中加入外部变量,就可以方便地将值带回来。
4.3 for
1. for基本使用
for的基本使用例子代码如下。
1 for(i <- 0 until 5){
2 print(i+" ")
3 }
上述代码执行后的输出如下。
0 1 2 3 4
上面的代码,首先执行0 until 5(其实是0.until(5)),生成0~4的集合(Range),然后从0开始,依次遍历,执行for后面大括号{}中的内容,在{}中,可以访问i,并做相应的动作。
& until生成的是减一的Range,如果要不减,则可以使用to
1 for(i <- 0 to 5){
2 print(i+" ")
3 }
上述代码执行后的输出如下。
0 1 2 3 4 5
2. for返回值
我们可以使用yield收集for循环中产生的值,并返回,例子代码如下。
第1行生成0~4的Range,然后遍历每个元素,乘以2,放入类型为IndexedSeq的集合,所有元素遍历完后,赋值给rs。
第2行遍历rs,每个元素打印一行输出。
1 val rs = for(i <- 0 until 5) yield i*2
2 rs.foreach(println)
& yield必须跟在for的小括号后面;
& Yield返回的类型和for中遍历对象的类型是一致的。
3. for当中可以加入条件判断(守卫)
for遍历时,可以加入条件判断,Scala称之为守卫。
下面的例子代码遍历数组ar,并找出所有是偶数的元素,返回给rs。第2行中,if((i%2)==0)判断i是否为偶数,这就是一个守卫,只有符合条件的(偶数),才会进入for后面的执行部分,即yield i。
1 val ar = Array(0, 1, 2, 2, 5, 4, 8, 9)
2 val rs = for(i <- ar if ((i%2)==0)) yield i
3 rs.foreach(x=>print(x+" "))
上述示例代码执行结果输出如下。
0 2 2 4 8
如果要加入多个判断,例子代码如下,第4行为第一个判断条件,第5行为第二个判断条件,yield返回大于2的所有偶数,因为ar是Array[Int],rs的类型也是Array[Int]。
1 val ar = Array(0, 1, 2, 2, 5, 4, 8, 9)
2 val rs = for{
3 i <- ar
4 if ((i%2)==0)
5 if(i>2)
6 } yield i
7 rs.foreach(x=>print(x+" "))
4. 双重for循环
有的时候需要双重(多重)循环,例如,打印9*9乘法表,代码如下。
1 for(i <- 1 to 9; j <- 1 to 9 if(i>=j) ){
2 print(j + "*" + i + "=" + i*j + " ")
3 if(i==j) println
4 }
第1行使用了i,j的双重循环,中间使用分号隔开,并且使用了if(i>=j)的守卫,过滤所有i<j的元素,避免重复;
第2~3行输出打印的数据和结果。
最终输出结果如下:
1*1=1
1*2=2 2*2=4
1*3=3 2*3=6 3*3=9
1*4=4 2*4=8 3*4=12 4*4=16
1*5=5 2*5=10 3*5=15 4*5=20 5*5=25
1*6=6 2*6=12 3*6=18 4*6=24 5*6=30 6*6=36
1*7=7 2*7=14 3*7=21 4*7=28 5*7=35 6*7=42 7*7=49
1*8=8 2*8=16 3*8=24 4*8=32 5*8=40 6*8=48 7*8=56 8*8=64
1*9=9 2*9=18 3*9=27 4*9=36 5*9=45 6*9=54 7*9=63 8*9=72 9*9=81
4.4 while
1. while基本使用
while基本使用的例子代码如下,功能是打印数字0~4。
第2行是while的控制部分,小括号内是一个条件表达式,如果表达式的值是真,则执行第3~4行,并返回到第2行,再次判断条件表达式的值,如果表达式的值为假,则直接从第2行跳转到第6行;
第2行开始的大括号{,到第5行}结束,大括号内部是while的执行代码,如果条件表达式的值为真,则会依次执行大括号{}内部的代码。
1 var i = 0
2 while(i<5){
3 print(i + " ")
4 i+=1
5 }
6 println
& 要特别注意while后面条件表达式:1. 表达式的类型必须是boolean型;2. 仔细考虑表达式为false的情况,即退出while循环的情况,防止死循环。
2. while和for的区别
(1)优化代码时,while循环和for循环之间的效率可能有10倍的差距;
(2)一般情况下,单层循环,while效率 > for;
(3)双层循环下,while效率可能 < for。
4.5 break和contine
1. break使用
Scala程序有时需要在循环过程中,直接跳出循环,在C/C++/Java中,使用break来实现,Scala也有break,但使用相对麻烦一点。
break例子代码如下,其功能是:当i=2时,使用break跳出while循环,关键代码说明如下。
第2行,引入break所需的package;
第4行,breakable是一个函数块,将循环(while或for)放入此函数块中;
第7���,当i等于2时,使用break函数,跳出while循环。
1 var i = 0
2 import scala.util.control.Breaks._
3
4 breakable {
5 while (i < 5) {
6 i += 1
7 if (i==2) break()
8 }
9 }
10 println(i)
& import可以出现在代码的任何位置,只要在使用此package的语句前就可以;
& break不可直接使用,要联合breakable语句块;
& breakable的使用会使得循环效率降低,在一些性能关键的循环处,要谨慎使用。
2. continue使用
Scala程序在循环过程中,有时需要停止向下执行代码,回到循环开始处,开始下一轮新的循环,C/C++/Java提供了continue来实现此功能。Scala中没有专门的continue关键字,但可以用break来实现类似的功能,
Scala例子代码如下。下面的代码遍历0~4,当i=2时,跳出,重新回到第3行while处,实现了类似continue的功能。
1 var i = 0
2 import scala.util.control.Breaks._
3 while (i < 5) {
4 i += 1
5 breakable {
6 if (i == 2) break()
7 println("in loop " + i)
8 }
9 }
要注意的是:
& breakable函数块提到了while内部;
& break()只是跳出breakable函数块,如果breakable函数块外部还有代码,也就是第8行开始,往下还有代码的话,会继续执行,这是和continue不一样的地方,continue会直接跳转到while,因此,如果要实现类似continue的功能,就要确保breakable函数块后面没有代码。
加艾叔微信,加入Linux(Shell+Zabbix)、大数据(Spark+Hadoop)、云原生(Docker+Kubernetes)技术交流群
关注艾叔公众号,获取更多一手信息