无参数方法

定义无参数方法可以省略括号。一旦定义无参数方法时省略了括号,那么调用这些方法时必须省略括号。假如在定义无参数方法时添加了空括号,那么调用方可以选择省略或保留括号。

Scala社区已经养成了这样一个习惯:定义那些无副作用的无参方法时省略括号。而定义具有副作用的方法时则添加括号,这样便能提醒读者某些对象可能会发送变化,需要额外小心。

加入运行scala或scalac时添加了-Xlint选项,那么在定义那些会产生副作用(例如方法中有IO操作)的无参方法时,省略括号将会出现一条警告信息。


In [4]:
// 合理考虑是否使用括号有助于构建更具有表现力的方法调用链
def isEven(n: Int) = (n % 2) == 0

List(1, 2, 3, 4).filter((i: Int) => isEven(i)).foreach((i: Int) => print(i))
println
List(1, 2, 3, 4).filter(i => isEven(i)).foreach(i => print(i))
println
List(1, 2, 3, 4).filter(isEven).foreach(print)
println
List(1, 2, 3, 4) filter isEven foreach print


24
24
24
24
defined function isEven

Scala的for推导式

推导式起源于函数式编程,它表示:遍历一个或多个集合,对集合中的元素进行“推导”,并从中计算出新的事物,新推导出的事物往往是另一个集合。


In [5]:
val dogBreeds = List("Doberman", "Yorkshire Terrier", "Dachshund",
                    "Scottish Terrier", "Great Dane", "Portuguese Water Dog")

// 使用if守护式(guard)
for(breed <- dogBreeds 
   if breed.contains("Terrier"))
    println(breed)


Yorkshire Terrier
Scottish Terrier
dogBreeds: List[String] = List(
  "Doberman",
  "Yorkshire Terrier",
  "Dachshund",
  "Scottish Terrier",
  "Great Dane",
  "Portuguese Water Dog"
)

In [6]:
for(breed <- dogBreeds
   if breed.contains("Terrier")
   if !breed.startsWith("Yorkshire"))
    println(breed)


Scottish Terrier


In [7]:
// 使用yield关键字能在for表达式中生成新的集合
val filteredBreeds = for {
    breed <- dogBreeds 
    if breed.contains("Terrier") && !breed.startsWith("Yorkshire")
} yield breed

println(filteredBreeds)


List(Scottish Terrier)
filteredBreeds: List[String] = List("Scottish Terrier")

In [8]:
val dogBreeds2 = List(Some("Doberman"), None, Some("Yorkshire Terrier"), Some("Dachshund"), None,
                    Some("Scottish Terrier"), Some("Great Dane"), None, Some("Portuguese Water Dog"))


dogBreeds2: List[Option[String]] = List(
  Some(Doberman),
  None,
  Some(Yorkshire Terrier),
  Some(Dachshund),
  None,
  Some(Scottish Terrier),
  Some(Great Dane),
  None,
  Some(Portuguese Water Dog)
)

In [9]:
for {
    Some(breed) <- dogBreeds2 
    upcasedBreed = breed.toUpperCase()
} println(upcasedBreed)


DOBERMAN
YORKSHIRE TERRIER
DACHSHUND
SCOTTISH TERRIER
GREAT DANE
PORTUGUESE WATER DOG

上式中,在for表达式中定义upcasedBreed,然后在后面的表达式中使用该值。

由于dogBreeds2列表中包含都是Option对象,在for推导式中使用模式匹配提取出Some类型,而不处理None元素。

在大部分语言中,可以使用break或continue跳出循环。而Scala并未提供该功能。编写地道的Scala代码,可以使用条件表达式或者使用递归判断循环是否应该继续。在一开始对集合进行过滤以消除循环中的复杂条件就更好了,不过为了满足break的需求,Scala提供scala.util.control.Breaks对象来实现break功能。


In [10]:
// break/continue的例子
import util.control.Breaks._


import util.control.Breaks._

In [11]:
println("==== BREAK EXAMPLE ====")
breakable {
    for (i <- 1 to 10) {
        println(i)
        if (i > 4) break // break out of the for loop
    }
}

println("==== CONTINUE EXAMPLE ====")
val searchMe = "peter piper picked a peck of pickled peppers"
var numPs = 0
for (i <- 0 until searchMe.length) {
    breakable {
        if (searchMe.charAt(i) != 'p') {
            break // break out of the 'breakable', continue the outside loop
        } else {
            numPs += 1
        }
    }
}
println("Found " + numPs + " p's in the string.")


==== BREAK EXAMPLE ====
1
2
3
4
5
==== CONTINUE EXAMPLE ====
Found 9 p's in the string.
searchMe: String = "peter piper picked a peck of pickled peppers"
numPs: Int = 9

异常处理

Scala推崇通过使用函数式结构和强类型以减少对异常和异常处理的依赖的编码范式。

Scala将异常处理作为另一类模式匹配来处理。


In [12]:
// 资源管理场景中处理异常
// 统计文件行数
import scala.io.Source
import scala.util.control.NonFatal

def countLines(fileName: String) = {
    var source: Option[Source] = None
    try {
        source = Some(Source.fromFile(fileName))
        val size = source.get.getLines.size
        println(s"file $fileName has $size lines")
    } catch {
        case NonFatal(ex) => println(s"Non fatal exception! $ex")
    } finally {
        for (s <- source) {
            println(s"Closing $fileName ...")
            s.close
        }
    }
}


import scala.io.Source
import scala.util.control.NonFatal
defined function countLines

In [13]:
val files = Array("fileBulkReaderTestFile.txt", "nonexistentFile.txt")
files foreach (fileName => countLines(fileName))


file fileBulkReaderTestFile.txt has 3 lines
Closing fileBulkReaderTestFile.txt ...
Non fatal exception! java.io.FileNotFoundException: nonexistentFile.txt (系统找不到指定的文件。)
files: Array[String] = Array("fileBulkReaderTestFile.txt", "nonexistentFile.txt")
  • 将变量source声明为Option类型,在finally子句中能够分辨出source对象是否是真正实例,通过for推导式从Some类型中提取Source实例
  • 假如文件不存在,source.fromFile方法将抛出java.io.FileNotFoundException类型的异常,否则将方法返回值封装在Some对象中,然后调用source变量的get方法得到Source实例
  • catch子句中,使用模式匹配来捕捉所希望捕获的异常

惰性求值

当希望以延迟的方式初始化某值,并且表达式不会被重复计算,则需要使用惰性求值。

常见场景:

  • 表达式执行代价昂贵(比如打开数据库连接),因此我们希望能推迟该操作,知道我们需要表达式结果值时才使用它
  • 为了缩短模块启动的时间,可以将当前不需要的某些工作推迟执行
  • 为了确保对象中其他的字段的初始化过程能优先执行,需要将某些字段惰性化

In [14]:
object ExpensiveResource {
    lazy val resource: Int = init()
    def init(): Int = {
        Thread.sleep(3000)
        0
    }
}


defined object ExpensiveResource

In [15]:
ExpensiveResource.resource


res14: Int = 0

惰性求值与方法调用的区别:

  • 方法调用每次都会执行
  • 惰性求值首次使用该值时,初始化代码体,才会执行一次
  • lazy关键字只修饰val量

枚举

Scala在标准库中专门定义Enumeration类来实现枚举,并未提供任何特殊语法支持枚举。

值得一提的是,Scala常常使用case class来代替枚举值,其有两点好处:

  • case类允许添加方法和字段,我们能够对枚举值应用模式匹配,为用户提供更好的灵活性
  • case类适用于包含未知枚举值的场景。只要有需要,用户代码可以将更多的case类添加到基本集合中

In [16]:
object Breed extends Enumeration {
    type Breed = Value
    val doberman = Value("Doberman Pinscher")
    val yorkie = Value("Yorkshire Terrier")
    val scottie = Value("Scottish Terrier")
    val dane = Value("Great Dane")
    val portie = Value("Portuguese Water Dog")
}


defined object Breed

In [17]:
import Breed._

println("ID\tBreed")
for (breed <- Breed.values) println(s"${breed.id}\t$breed")

println("\nJust Terriers:")
Breed.values filter (_.toString.endsWith("Terrier")) foreach println

def isTerrier(b: Breed) = b.toString.endsWith("Terrier")

println("\nTerriers Again:")
Breed.values filter isTerrier foreach println


ID	Breed
0	Doberman Pinscher
1	Yorkshire Terrier
2	Scottish Terrier
3	Great Dane
4	Portuguese Water Dog

Just Terriers:
Yorkshire Terrier
Scottish Terrier

Terriers Again:
Yorkshire Terrier
Scottish Terrier
import Breed._
defined function isTerrier
  • 枚举类型中包含了许多Value类型值,每一个枚举值都调用了接收单一字符串参数的Value方法,调用Value.toString方法将返回该字符串。
  • Breed类型是一个别名(Value类型的别名)

Scala提供了很多个Value的重载方法:

  • 接受单一字符串的Value方法
  • 无参Value方法,将对象名作为输入字符串
  • 输入参数是一个ID值,该方法在使用默认字符串(变量名)的同时会将我们显式指定的整数值作为ID
  • 同时接收整数和字符串输入的Value方法

In [18]:
object WeekDay extends Enumeration {
    type WeekDay = Value
    val Mon, Tue, Wed, Thu, Fri, Sat, Sun = Value
}


defined object WeekDay

In [19]:
import WeekDay._

def isWorkingDay(d: WeekDay) = ! (d == Sat || d == Sun)

WeekDay.values filter isWorkingDay foreach println


Mon
Tue
Wed
Thu
Fri
import WeekDay._
defined function isWorkingDay

Trait:接口和混入

Scala从函数式编程思想中汲取trait,使用它来代替Java的接口。

在trait中可以声明或定义类型,并且trait允许真正意义上的组合行为(“混入”模式)。


In [20]:
// 在应用中混入日志
class ServiceImportance(name: String) {
    def work(i: Int): Int = {
        println(s"ServiceImportance: Doing important work! $i")
        i + 1
    }
}

val service1 = new ServiceImportance("uno")
(1 to 3) foreach (i => println(s"Result: ${service1.work(i)}"))


ServiceImportance: Doing important work! 1
Result: 2
ServiceImportance: Doing important work! 2
Result: 3
ServiceImportance: Doing important work! 3
Result: 4
defined class ServiceImportance
service1: $user.ServiceImportance = cmd19$$user$ServiceImportance@19e3ffe

In [21]:
// 在服务中混入标准日志库,使用println输出日志
// 定义日志抽象
trait Logging {
    def info(message: String): Unit
    def warning(message: String): Unit
    def error(message: String): Unit
}

// 定义日志信息输出到标准输出
trait StdoutLogging extends Logging {
    def info(message: String) =    println(s"INFO:    $message")
    def warning(message: String) = println(s"WARNING: $message")
    def error(message: String) =   println(s"ERROR:   $message")
}


defined trait Logging
defined trait StdoutLogging

In [22]:
val service2 = new ServiceImportance("dos") with StdoutLogging {
    override def work(i: Int): Int = {
        info(s"Starting work: i = $i")
        val result = super.work(i)
        info(s"Ending work: i = $i, result = $result")
        result
    }
}


service2: ServiceImportance with StdoutLogging = cmd21$$user$$anonfun$1$$anon$1@110bafd

In [23]:
(1 to 3) foreach (i => println(s"Result: ${service2.work(i)}"))


INFO:    Starting work: i = 1
ServiceImportance: Doing important work! 1
INFO:    Ending work: i = 1, result = 2
Result: 2
INFO:    Starting work: i = 2
ServiceImportance: Doing important work! 2
INFO:    Ending work: i = 2, result = 3
Result: 3
INFO:    Starting work: i = 3
ServiceImportance: Doing important work! 3
INFO:    Ending work: i = 3, result = 4
Result: 4