Scala实战专栏 (三) ——— 控制结构

scala-basic 基础篇

@author 鲁伟林
Scala基础知识介绍,主要包括《Scala编程实战》的基础章节

GitHub地址: https://github.com/thinkingfioa/scala-in-action
本人博客地址: http://blog.csdn.net/thinking_fioa/article/details/78265745

3. 控制结构

Scala语言和Java语言在控制结构部分很像,但是又存在有趣的差别。 第3章项目源码阅读

3.1 for和foreach循环

循环语句主要用于处理三个问题: 1. 遍历一个集合中的所有元素; 2. 对集合中的每个元素进行某个操作; 3. 利用现有的集合创建新的集合

3.1.1 for循环常用的遍历

个人比较推荐如下for循环语法遍历集合

def forF(array : Array[String]): Unit = {
    for(entry <- array) {
      println(entry.toUpperCase())
    }
  }

3.1.2 从for循环中返回值

使用for/ield组合,输入一个集合,返回一个新的集合。在for循环添加yield实际上将每次循环的结果放入一个临时存放区。等循环结束后,以集合的形式返回。

def forYield(array : Array[String]) : Array[String] = {
    for(entry <- array) yield {
        entry.toUpperCase()
    }
}

3.1.3 for循环计数器

访问循环内的计数器,即通过一个计数器访问数组元素,使用array.indices

def forCount(array : Array[String]): Unit = {
  for(i <- array.indices) {
    println(s"$i is ${array(i)}")
  }
}

3.1.4 遍历一个Map

遍历Map中的键值对,下面的Map循环方法最简洁和可读

def forMap(namesMap : Map[String, Int]) : Unit = {
  for((k,v) <- namesMap) {
    println(s"key is $k, value is $v")
  }
}

3.1.5 另一种遍历集合方式

使用foreach, map, flatmap, collect, reduce等方法

  1. array.foreach(println) ----- 遍历array数组,可以使用自定义方法替换println方法
  2. array.foreach( e => println(e.toUpperCase)) ----- 使用匿名函数的语法
  3. array.foreach{ e => | val s = e.toUpperCase | println(s)|} ----- 实现多行函数

3.1.6 for循环式是如何被解释的

提供简化版的规则,帮助理解for循环执行过程

  1. 遍历集合的一个简单的for循环被解释为foreach方法调用
  2. 带有卫语句的for循环(3.3节)被解释为一个foreach调用后在集合上调用withFilter方法的序列
  3. 带有yield表达式的for循环被解释为集合上调用map方法
  4. 带有yield表达式和卫语句被解释为在集合上调用withFilter方法,紧接着一个map方法

3.2 在for循环中使用多个计数器

创建有多个计数器的循环,如遍历多维数组的情况。推荐使用大括号的代码风格,如下:

代码

def forMoreCount() : Unit = {
  for {
    i <- 1 to 3
    j <- 1 to 5
    k <- 1 to 10
  } {
    println("next: ")
    println(s"i : $i, j : $j, k : $k")
  }
}

3.3 在for循环中嵌入if语句(卫语句)

for循环中添加一个或者多个条件语句,典型的应用场景就是将一个元素从集合中过滤掉。 推荐使用大括号编码风格

代码

def forIf() : Unit = {
  for {
    i <- 1 to 10
    if i> 3
    if i<= 8
    if i % 2 == 0
  } println(i)
}

3.4 创建for表达式( for/yield组合 )

对一个已有的集合中的每个元素应用某个算法,从而生成新的集合。在for循环中使用yield语句的方式通常被叫作for推导

代码

def forYieldMoreLine(array : Array[String]) : Array[Int] = {
  for( e <- array) yield {
    val eUpper = e.toUpperCase()
    eUpper.length
  }
}

理解for/yield表达式

  1. 开始运行时,for/yield循环立刻创建一个新的空集合(Bucket),类型与输入的集合相同
  2. for循环每次遍历,都会在输入集合中的每个元素基础上创建新的元素,加入到Bucket中
  3. 循环结束后,Bucket中的所有内容都被返回

3.5 实现break和continue

遗憾的是Scala没有break或者continue关键字,但是scala.util.control.Breaks提供了类似的功能。相对于Java来说,Scala的break和continue较为复杂化。个人不喜欢这种风格

代码

import util.control.Breaks._

object Ctrl3P5 {

  def forBreak(array : Array[Int]) : Unit = {
    breakable {
      for(i <- array) {
        if(i== 2) {
          break()
        } else {
          print(i+", ")
        }
      }
    }
  }

  def forContinue(array : Array[Int]) : Unit = {
    for(i <- array) {
      breakable {
        if(i== 2) {
          break()
        } else {
          print(i +", ")
        }
      }
    }
  }
}

3.6 像三元运算符一样使用if

Java提供条件运算符 ? : 称为三元运算符,但是Scala没有提供。在scala中只能使用if/else语句。eg x >=0 ? "yes": "no" 等价于 if(x>=0) "yes" else "no"

3.7 像swtich语句一样使用匹配表达式

Java中基于整数的switch语句,Scala也支持使用@switch注解来满足switch语句,同时@switch注解性能会更优越

代码

def switchTypo(i : Int) : String = {
  (i : @switch) match {
    case 1 => "One"
    case 2 => "Two"
    case _ => "Other"
  }
}

匹配表达式不局限于整数,它是非常灵活的,如下类型的匹配也是支持的

代码

def switchType(x : Any) : String = {
  (x : @switch) match {
    case s : String => "One"
    case i : Int => "Two"
    case _ => "Other"
  }
}

3.7.1 处理缺省情况

处理缺省情况的两种情况:

  • 不关心缺省匹配的值,使用通配符去捕获 ----- case _ => println("default")
  • 关系缺省匹配的值,指定一个变量,然后在表达式右侧使用该变量 ----- case default => println(default)
  • 如果不处理缺省情况,可能会产生MatchError错误。所以建议要处理default case

3.8 一条case语句匹配多个条件

多个匹配条件需要执行相同的业务逻辑时,使用一条case语句匹配多个条件

代码

def moreCase(x : Int) : Unit = {
  (x : @switch) match {
    case 1 | 3 | 5 | 7 | 9 => println("odd")
    case 2 | 4 | 6 | 8 | 10 => println("even")
  }
}

3.9 将匹配表达式的结果赋值给变量

将一个匹配表达式返回值赋值给一个变量,或将匹配表达式作为方法的主体

def isTrue(a : Any) = a match{
  case 0 | "" => false
  case _ => true
}

3.12 在匹配表达式中使用Case类

在一个匹配表达式中匹配不同的case类(或者case对象)。如下面的代码,Dog和Cat case类以及Woodpecker case对象都是Animal trait的子类型

代码

trait Animal

case class Dog(name : String) extends Animal

case class Cat(name : String) extends Animal

case object Woodpecker extends Animal

object Ctrl3P11 {

  def main(args: Array[String]): Unit = {
    println(determineType(Dog("ppp")))
    println(determineType(Cat("fioa")))
    println(determineType(Woodpecker))
  }

  def determineType(x : Animal) : String = x match {
    case Dog(moniker) => "Got a Dog, name = "+moniker
    case _ : Cat => "Got a Cat"
    case Woodpecker => "That was a Wood"
    case _ => "default"
  }

}

3.13 给Case语句添加if表达式(卫语句)

给匹配的表达式内的case语句添加合适的逻辑,帮助增强case语句的约束条件

object Ctrl3P13 {

  def main(args: Array[String]): Unit = {
    caseIfNum(1)
    caseIfNum(2)
  }

  def caseIfNum(x : Int) : Unit = {
    (x : @switch) match {
      case m if m==1 => println("one, a lonely number")
      case n if n==2 || n==3 => println(n)
      case _ => println("default")
    }
  }
}

3.14 使用匹配表达式替换isInstanceOf

判断对象是否匹配一个类型,可以通过使用isInstanceOf来判断,eg: if(x isInstanceOf[Class]) 。但当需求负责情况写,写一个代码块去匹配一种类型或者多个不同的类型的可读性更好。

代码

trait SentientBeing
trait Animal2 extends SentientBeing
case class Pig(name : String) extends Animal2
case class Person(name:String, age : Int) extends SentientBeing

object Ctrl3P14 {

  def main(args: Array[String]): Unit = {
    casePrintInfo(Person("luweilin", 24))
    casePrintInfo(Pig("ppp"))

    if(Pig("ppp").isInstanceOf[SentientBeing]) {
      println("true")
    }
  }


  def casePrintInfo(x : SentientBeing) : Unit = x match {
    case Person(name, age) => println(s"$name is $name, age is $age")
    case Pig(name) => println(s"pig name is $name")
    case _ => println("default")
  }
}

3.15 在匹配的表达式中使用List

List数据结构和其他的集合数据结构略有不同。列表由单元开始,Nil元素结尾。如果下递归打印内容

object Ctrl3P15 {

  def main(args: Array[String]): Unit = {
    val x = List(1,2,3,4)
    println(caseList(x))
  }

  def caseList(x : List[Int]) : String = x match {
    case s :: rest => s +", " + caseList(rest)
    case Nil => ""
  }
}

3.16 用try/catch匹配一个或多个异常

在try/catch块捕捉一个或者更多的异常

def moreException(fileName : String) : Unit = {
  try {
    // read config File
  } catch {
    case e : FileNotFoundException => println("Colud find that file.")
    case e : IOException => println("IOException trying to read that file")
  }
}

注意: Scala中没有受检异常,因此不需要指定抛出异常的方法。如果需要声明方法抛出的异常,或者需要和Java交互,在定义方法时添加@throws注解

@throws(classOf[NumberFormatException])
def throwException(fileName : String) : Unit = {
  try {
    // read config File
  } catch {
    case e : NumberFormatException => throw e
  }
}

3.17 在try/catch/finally块中使用变量前定义变量问题

在try代码中使用一个变量,并在finally代码块中访问该对象,如调用对象的close方法。一般情况下,在try/catch块前声明字段为Option,然后在try子句中创建一个Some对象,finally中执行关闭。在Scala中建议不要使用null

代码:

def tryCatchFinally(fileName : String) : Unit = {
  var in = None : Option[FileInputStream]

  try {
    // open file Name
  } catch {
    case cause: IOException => cause.printStackTrace()
  } finally {
    if(in.isDefined) {
      in.get.close()
    }
  }
}

3.18 创建自定义控制结构

Scala语言的创造者有意决定通过Scala类库去实现功能而不是去创建一些关键字。典型的例子就是: break和continue关键字。开发者可以自定义控制结构,创建可用的DSL去给他人所用

3.18.1 创建一个类似于while循环控制结构

object Ctrl3P18 {

  def main(args: Array[String]): Unit = {
    var i =0
    whilst(i<5) {
      println(s"index: $i")
      i += 1
    }
  }

  @tailrec
  def whilst(testCondition : => Boolean) (codeBlock : => Unit) {
    if(testCondition) {
      codeBlock
      whilst(testCondition)(codeBlock)
    }
  }
}

解释:

自定义函数名:whilst,接受两个参数列表,第一个是参数列表测试条件,一个表示用户想要运行的代码块

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值