scala 程序设计 第3章

目录

3.1 操作符重载

  • 在java中特殊的基本类型:Int、Short、Long、Double、Float、Byte、Boolean 等类型在scala中都变成正规的对象,这意味着它们都可以拥有成员方法
  • 由于使用中缀表示法表示单参数方法时,点号和括号可以省略
scala> 1 + 2
res0: Int = 3

scala> 1.+(2)
res1: Int = 3

scala> 1 + 2 * 3
res2: Int = 7

// . 比 * 优先级高,所以 (1 + 2) * 3
scala> 1.+(2)*3
res3: Int = 9
  • 调用无参数的方法也可以省略”.” 这种写法称为后缀表示法,只是这种表示法有时候会产生歧义,所以scala 2.10 将这种特性改为可选特性,需要执行import语句,如果不执行就会产生警告
scala> 1 toString
warning: there was one feature warning; re-run with -feature for details
res4: String = 1

scala> :quit
// 添加 -feature 这个参数,可以看到scala命令启动的过程
[root@master ~]# scala -feature
Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_151).
Type in expressions for evaluation. Or try :help.

scala> 1.toString
res0: String = 1

scala> 1 toString
<console>:12: warning: postfix operator toString should be enabled
by making the implicit value scala.language.postfixOps visible.
This can be achieved by adding the import clause 'import scala.language.postfixOps'
or by setting the compiler option -language:postfixOps.
See the Scaladoc for value scala.language.postfixOps for a discussion
why the feature should be explicitly enabled.
       1 toString
         ^
// 这段提示大致翻译:toString 的后缀表示法需要开启,将scala.language.postfixOps 这个值由隐式转换成显示,需要使用:import scala.language.post.fixOps
res1: String = 1

scala> import scala.language.postfixOps
import scala.language.postfixOps

scala> 1 toString
res2: String = 1

// 还有另一种方法删除这里的警告:给编译器传入另一个表示-language:postfixOps,通过这一种方法在全局范围内开启该特性。只是这种方法还不知道如何实现??

标志符(方法名,变量名,类型名等)遵循的规则

  • 可用的字符:除了了括号,分隔符之外的,其他任何可打印的字ASCII字符,如字母、数字、下划线”_”和美元符号”$”
  • 插入字符:(,)、[,]、{,and}
  • 分隔符:`、’、’、”、.、; 以及,
  • 可以使用编码在\u0020 到 \u007F 之间的字符,如数学符号、像”\”和”<” 这样的操作符字符以及其他的一些符号
  • 不能使用保留字
  • 普通标志符:字母、数字、$、_ 和操作符的组合。由字母或者开头。Scala 允许使用Unicode格式的字符。$ 美元符在Scala有特殊的作用,所以不能将$ 单独做为字符。下划线后可以输入字母、数字,也可以输入一些操作符,编译器会将下划线之后空格之前的所有字符视为标志的一部分。如 val xyz++= = 1 会将变量xyz_++ 赋值为1
scala> val xyz_++ = 1
xyz_++: Int = 1

// xyz++= 可以解释为xyz ++= 这样看上去要为xyz赋值
scala> val xyz++= = 1
<console>:1: error: illegal start of simple pattern
val xyz++= = 1
           ^

scala> val xyz++ = 1
<console>:1: error: illegal start of simple pattern
val xyz++ = 1
          ^

scala> val xyz+ = 1
<console>:1: error: illegal start of simple pattern
val xyz+ = 1

// 下划线后输入了操作符,那么不允许在操作符后输入字母或者数字
scala> val abc_-123 = 1
<console>:1: error: '=' expected but integer literal found.
val abc_-123 = 1
         ^

scala> val abc_123 = 1
abc_123: Int = 1
  • 操作符:某一标志付以操作符开头,那么后面的字符也必须是操作字符
  • 反引号操作符
// assert() 后面的表达式只能是true/false
// 反引号定义测试名称的方法,这种方式使用了一种"如果不满足某些条件,便存在问题"的命名技巧。可以无论后面的表达式是true 还是 false 所得到的结果都是一样的,很奇怪?
scala> def `test that addition works` = assert(4 == 4)
test$u0020that$u0020addition$u0020works: Unit

scala> def `test that addition works` = assert(1 == 8)
test$u0020that$u0020addition$u0020works: Unit

// 访问java类方法或者变量名与scala类的保留字相同,也需要使用反引号

import java.net.Proxy.Type
// 这个报错
import java.net.Proxy.`type`()
val `type` = "abc"

3.2 无参数方法

  • 定义无参数方法的时候可以省略(),如List(1,2,3).szie 这样的方法定义的没有括号,所以调用的时候不能带()。假如在定义无参数方法时添加了空括号,那么调用可以代括号或者省略括号
scala> val l1: List[String] = List("123",123)
<console>:11: error: type mismatch;
 found   : Int(123)
 required: String
       val l1: List[String] = List("123",123)
                                         ^

scala> val l1: List[String] = List("123","123")
l1: List[String] = List(123, 123)

scala> val a = 5
a: Int = 5

scala> val b = 10
b: Int = 10
scala> println(addFun)
15

scala> println(addFun())
15
scala> "hello".length
res12: Int = 5

scala> "hello".length()
res13: Int = 5
  • Scala 社区已经养成这样一个习惯:定义无副作用的无参数方法时省略括号,定义有副作用的方法时不省略括号。scala 或 scalac 带上-Xlint 选项,那么在定义那些产生副作用(例如,方法中会有I/O操作)的无参数方法时,省略括号就会出现一条警告
scala> val s = 3
s: Int = 3

scala> def isEven(n:Int) = (n % s) == 0
isEven: (n: Int)Boolean
scala> List(1,2,3,4) filter isEven foreach println
3

// 下面的命令都是等价
scala> List(1,2,3,4) filter isEven foreach(x => println(x))

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


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

scala> List(1,2,3,4).filter(isEven).foreach(println)

// 上面的每个方法都接受单一的参数,因此该表达式能正常运行。假如方发链中某一方法接受0个或者大于1个的参数,编译器会困惑。如果出现了这种情况,请为部分或全部方法补上点号

3.3 优先级规则

优先级从低到高列出优先级规则

  1. 所有字母
  2. |
  3. ^
  4. &
  5. < >
  6. = !
  7. :
    • -
    • / %
  8. 其他字符
  9. 同一行的字符具有相同的优先级,有个例外,当”=” 用于赋值操作时,该符号的优先级最低
scala> 2.0 * 4.0 / 3.0 * 5.0
res48: Double = 13.333333333333332

scala> ((2.0 * 4.0) / 3.0) * 5.0
res52: Double = 13.333333333333332

执行由左结合方法组成的方法序列时,只有简单地按照从左到又的顺序执行就行了。但不是所有的都是左邦定,如果通过:::方法将某医院是放置到列表的前面,这一操作成为cons操作,cons是constructor的缩写,这也是Lisp所引入的概念

scala> val list = List('b','c','d','a')
list: List[Char] = List(b, c, d, a)

// 以":"结尾的方法均与其右边的对象绑定,他们并不与左侧的对象绑定
scala> 'e' :: list
res53: List[Char] = List(e, b, c, d, a)

scala> list.::('e')
res54: List[Char] = List(e, b, c, d, a)

scala> list
res55: List[Char] = List(b, c, d, a)

3.4 领域特定语言

  1. DSL:领域特定语言,指专为某一问题领域编写的语言,引入DSL是为了方便用简洁直观的方式表达该领域的概念。例如SQL便可以被视为一门DSL,因为它是一门专用于解释关系模型的编程语言
  2. DSL通常只用于即席查询语言,要么嵌入某一宿主语言内,要么会专门有一个定制的解析器负责解析。嵌入式DSL,通常也被称为内部DSL,需要特制解析器的DSL则被称为外部DSL
  3. scala 为内部DSL和外部DSL 均提供了完美的支持。scala提供了灵活的标志符规则,如允许使用操作符命名,支持中缀和后缀方法调用语法,这位编写嵌入DSL提供了构建DSL所需要的组成元素
  4. 4.

3.5 Scala 中的 if 语句

if ( 2 + 2 == 5) {
    println("Hello from 1984.")
} else if ( 2 + 2 == 3) {
    println("Hello from Remedial Math class?")
} else {
    println("Hello from a non-Orwellian future.")
}
/*
输出:
Hello from a non-Orwellian future.
*/

if 语句和几乎所有的其他语句都是具有返回值的表达式。因此我们能想下面展示的代码那样,将if表达式的结果值赋给其他变量
scala 中的if语句是一类表达式,像predicate ? trueHandler() : falseHandler() 这种三元表达式对于scala是多余的,所有scala并不支持三元表达式

val configFile = new java.io.File("/tmp/test.txt")

val configFilePath = if (configFile.exists()) {

    println("confingFile's absolutepath",configFile.getAbsolutePath())
} else {
    configFile.createNewFile()
    println(configFile.getAbsolutePath())
}
/*
第一次运行/tmp/test.txt 这个文件不存在
/tmp/test.txt
第二次运行/tmp/test.txt 这个文件已经存在
(confingFile's absolutepath,/tmp/test.txt)
*/

3.6 Scala 中的for推导式

3.6.1 for循环

val dogBreeds = List("Doberman","Yokshire Terrier","Dachshund","Scottish Terrier","Great Dane","Portugurses Water Dog")
for (breed <- dogBreeds)
    println(breed)
/*
输出:
Doberman
Yokshire Terrier
Dachshund
Scottish Terrier
Great Dane
Portugurses Water Dog
*/
// 这类for 推导式,不返回任何值,因此它只会执行带来副作用的操作。这类for推导式有时候也被称为for循环,这与Java中的for循环较为类似

3.6.2生成器表达式

scala> for (i <- 1 to 10) print(i)
12345678910
scala> for (i <- 1 to 10) println(i)
1
2
3
4
5
6
7
8
9
10
/*
像breed <- dogBreeds 生成器表达式(generator expression),之所以将生成器,是因为该表达式会基于集合生成单独的数值。左箭头操作符(<-) 用于对像列表这样的集合
还可以使用生成器表达式对某些区间进行访问,以这种方式编写出的for循环更加自然
*/

3.6.3 保护试:筛选元素

val dogBreeds = List("Doberman","Yokshire Terrier","Dachshund","Scottish Terrier","Great Dane","Portugurses Water Dog")
for (breed <- dogBreeds
    if breed.contains("Terrier"))
    println(breed)
/*输出:
Yokshire Terrier
Scottish Terrier
*/
// def contains(elem: Any): Boolean
// 检测列表中是否包含指定的元素

val dogBreeds = List("Doberman","Yokshire Terrier","Dachshund","Scottish Terrier","Great Dane","Portugurses Water Dog")
for (breed <- dogBreeds {
    if breed.contains("Terrier")
    println(breed) }
// if 表达式,需要和x <- Range 属于同一个括号中,空格隔开

val dogBreeds = List("Doberman","Yorkshire Terrier","Dachshund","Scottish Terrier","Great Dane","Portugurses Water Dog")
for (breed <- dogBreeds 
    if breed.contains("Terrier")
    if ! breed.startsWith("Yorkshire"))
    println(breed)

for (breed <- dogBreeds
    if breed.contains("Terrier") && ! breed.startsWith("Yorkshire")
    ) println(breed)

/*
输出:
[root@spark1 scala]# scala double-guard-for.sc 
Scottish Terrier
Scottish Terrier
*/

3.6.4 Yielding

// 这里可以将for循环遍历列表,并过滤掉执行完成后所得的结果保存至另一个列表中
// filteredBreeds 源于dogBreeds 所以filteredBreeds 列表是List[String] 字符列表
var dogBreeds = List("Doberman","Yorkshire Terrier","Dachshund","Scottish Terrier","Great Dane","Portuguese Water Dog")
val filteredBreeds = for {
    breed <- dogBreeds
    if breed.contains("Terrier") && ! breed.startsWith("Yorkshire")
}yield breed
println("filteredBreeds",filteredBreeds)

val filteredBreeds_1 = for (
    breed <- dogBreeds
    if breed.contains("Terrier") && ! breed.startsWith("Yorkshire")
) yield breed
println("filteredBreeds",filteredBreeds_1)

/*
output:
(filteredBreeds,List(Scottish Terrier))
(filteredBreeds,List(Scottish Terrier))

*/
// 由上面的结果来看,{} 和 () 效果一样。只是for 推导式有个不成文的规定:仅包含单一表达式时使用(),当其包含多个表达式时使用{}

3.6.5 扩展作用域与值定义

val dogBreeds = List("Doberman","Yorkshire Terrier","Dachshund","Scottish Terrier","Great Dane","Portugrese Water Dog")
for {
    breed <- dogBreeds
    upcaseBreed = breed.toUpperCase()
    // 此处的val 这个关键字可以省略,如果带上在scala 2.11.8 的版本会警告,而在Scala 早期版本此处的val 这个关键字是可选的。
    val upcaseBreed1 = breed.toUpperCase()
} println("upcaseBreed: "+ upcaseBreed + " " + "upcaseBreed1: " + upcaseBreed1)

// 既然不能连续使用两条println语句
// println(upcaseBreed1)


// 使用yield 会报错,很奇怪的,不知道哪里的问题
//val upcaseBreed_2 = for {
//    breed <- dogBreeds
//    breed.toUpperCase()
//     // val upcaseBreed1 = breed.toUpperCase()
//} yield breed
//println("upcaseBreed: "+ upcaseBreed")

/* -deprecation 这个参数可以看到警告的明细信息
使用
[root@master scalar]# scala -deprecation scoped-for.sc 
/data/project/scalar/scoped-for.sc:5: warning: val keyword in for comprehension is deprecated
    val upcaseBreed1 = breed.toUpperCase()
                     ^
one warning found
upcaseBreed: DOBERMAN upcaseBreed1: DOBERMAN
upcaseBreed: YORKSHIRE TERRIER upcaseBreed1: YORKSHIRE TERRIER
upcaseBreed: DACHSHUND upcaseBreed1: DACHSHUND
upcaseBreed: SCOTTISH TERRIER upcaseBreed1: SCOTTISH TERRIER
upcaseBreed: GREAT DANE upcaseBreed1: GREAT DANE
upcaseBreed: PORTUGRESE WATER DOG upcaseBreed1: PORTUGRESE WATER DOG

*/

Option 用在for循环中

val dogBreeds = List(Some("Doberman"),None,Some("Yorkshire Terrier"),Some("Dachshund"),None,Some("Scottish Terrier"),None,
                     Some("Great Dance"),Some("Portugurese Water Dog"))

println("first pass:")
for {
    // breed <- breed  如果直接写成这样编译器会报错,需要转换下。
    // 这里相当于显示的增加 if breedOption != None
    breedOption <- dogBreeds
    breed <- breedOption
    upcasedBreed = breed.toUpperCase()
} println(upcasedBreed)

println("second pass:")
for {
    // 这里使用了模式匹配,只有BreedOption 是 Some 类型时,表达式Some(breed) <- dogBreeds 才会成功执行并提取出 breed,所有的操作一步完成。None 元素不再处理
    Some(breed) <- dogBreeds
    upcaseBreed = breed.toUpperCase()

} println(upcaseBreed)

/*
output
first pass:
DOBERMAN
YORKSHIRE TERRIER
DACHSHUND
SCOTTISH TERRIER
GREAT DANCE
PORTUGURESE WATER DOG
second pass:
DOBERMAN
YORKSHIRE TERRIER
DACHSHUND
SCOTTISH TERRIER
GREAT DANCE
PORTUGURESE WATER DOG

*/

for 推导式的第一句表达式必须是 <- 。当遍历一个集合或者其他想Option 这样的容器并试图提取值时,应该使用”<-“。当执行不需要迭代的赋值操作的时候,应该使用等号 “=”

注意:scala for 推导式中不使用break和continue,可以使用过滤的来替代
scala 提供了 scala.util.control.Breaks 这个对象可以实现break功能:x

3.7 其他循环结构

while 循环

import java.util.Calendar

def isFridayThirteen(cal: Calendar): Boolean = {
    // 一周的第几天,注意。第一天是星期天
    val dayOfWeek = cal.get(Calendar.DAY_OF_WEEK)
    // 一个月的第几天。
    val dayOFMonth = cal.get(Calendar.DAY_OF_MONTH)

    ((dayOfWeek == Calendar.FRIDAY) && (dayOFMonth == 13))
}

while (!isFridayThirteen(Calendar.getInstance())) {
    println("Today isn't Friday the 13th, Lame.")
    Thread.sleep(10000)
}

do-while 先执行循环体,然后在检测循环条件是否满足

var count = 0
do {
    count += 1
    println(count)
} while (count < 10)

/*
1
2
3
4
5
6
7
8
9
10
*/

3.8 条件操作符

操作符操作描述
&&和操作操作符左边和右边的值都为true,只有当操作符左边的值为真值才会评估右边的值是否为真
||或操作操作符左边和右边值至少有一个为true,只有当操作符左边的值为假的时候才评估右边的是否为真
>大于操作符左边的值大于右边的值
>=大于或等于操作符左边的值大于或等于右边的值
<小于操作符左边的值小于右边的值
<=小于或等于操作符左边的值小于或等于右边的值
==等于操作符左边的值等于右边的值
!=不等于操作符左边的值不等于右边的值

- scala 使用 “==” 执行逻辑意义上的相等检查,不过该操作符调用了equals 方法,假如你并不希望进行逻辑相等检查,而只想比较引用,你可以使用scala提供的新方法eq

scala> var a = 8
a: Int = 8

scala> var b = 6
b: Int = 6

scala> val c = 3
c: Int = 3

scala> val d = 8
d: Int = 8

scala> var e = 6
e: Int = 6

scala> var f = 3
f: Int = 3

scala> a < b
res8: Boolean = false

scala> a > b
res9: Boolean = true

// 比大小与变量是否可读写无关
scala> a > c
res10: Boolean = true

scala> a < c
res11: Boolean = false

scala> d >= a
res17: Boolean = true

scala> b >= e
res18: Boolean = true

scala> a != c
res12: Boolean = true

scala> c >= f
res21: Boolean = true

scala> d <= a
res22: Boolean = true

scala> b <= e
res28: Boolean = true

scala> f <= c
res29: Boolean = true

3.9 使用 try、catch 和 final 子句

  • scala 推崇使用函数式结构和强类型以减少对异常和异常处理的依赖的编码方式。但是异常还是无法避免,如scala需要与java代码交互时,异常就无法避免
  • scala 并不支持已被视为失败设计的检查异常(check exception)。scala将java中的检测型异常视为非检查型,而且方法声明中也不包含throw 子句。不过scala提供了有助于java互操作的@throws注解
  • scala将异常处理作为另一类模式匹配来进行处理,因此我们可以简洁地对各种不同类型的异常进行处理
object TryCatch {
    /** Usage: scala rounding.TryCatch filename1 filename2 ...*/
    def main(args: Array[String]) = {
        // foreach 返回 Unit 类型
        args foreach(arg => countLines(arg))
    }

    // 文件处理接口 scala.io.Source
    import scala.io.Source
    // 匹配nonfatal 异常 scala.util.control.NonFatal
    import scala.util.control.NonFatal
    def countLines(fileName: String) = {

        println() // Add a blank line for legibility
        // source 声明为Option 类型,因此我们在finally子句中能分辨出source对象是否是真正的实例
        var source: Option[Source] = None
        try {
            // 假如文件不存在,source.fromFile 方法将抛出java.io.FileNotFoundException 类型异常。否则的话,将该方法返回的值装载到Some对象中。
            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 { // 应用finally 子句会得到一个合理的清理。如果不是用finally,我们将不得不分别在try子句和catch子句重复清理逻辑,这样才能确保文件句柄会被关闭
            for (s <- source) {
                // 使用for推导式从Some类型的对象中提取Source实例,之后将关闭文件,假如source对象为None,不会执行for 循环体
                // 假如你需要对Option 对象进行检测,当它是Some 对象执行一些操作,而当它是None对象时则不进行任何操作,那么你可以使用for推导式,这也是Scala的一个广泛应用的常见用法
                println(s"Closing $fileName....")
                s.close
            }
        }
    }
}

/*
编译
[root@master scalar]# scalac TryCatch.scala
参数给个文件名
[root@master scalar]# scala TryCatch /tmp/sbt144959257350048385.log

捕获到异常
[root@master scalar]# scala TryCatch /tmp/test.txt

Non fatal exception! java.io.FileNotFoundException: /tmp/test.txt (No such file or directory)
*/

3.10 名字调用和值调用

import scala.language.reflectiveCalls
import scala.util.control.NonFatal

object manager {
    // R 表示需要管理的资源,"<:" 意味着R 属于某其他类型的子类。在本列中R的父类型是一个包含close():Unit 方法的结构类型
    // 结构化类型允许使用反射机制嵌入包含close():Unit方法的任意类型(如Source 类型)。反射机制会造成许多系统开销,而结构化类型代价也较为昂贵
    def apply[R <: { def close() : Unit },T](resource: => R)(f:R=> T) = { // T为匿名函数的返回值,这个函数用于处理资源。
    //resource 是一个传名函数,暂且当做是一个调用时应省略括号的函数。f也是一个匿名函数,输入为resource 返回值为T,这里匿名函数处理resource
        var res: Option[R] = None
        try {
            res = Some(resource)
            f(res.get)
        } catch {
            case NonFatal(ex) => println(s"Non fatal exception! $ex")
        } finally {
            println(s"Closing resource ...")
            res.get.close
        }
    }
}
object TryCatchARM {
    /** Usage: scala rounding. TryCatch filename1 filename2 ...*/
    def main(args: Array[String]) = {
        args foreach (arg => countLine(arg))
    }
    import scala.io.Source
    def countLine(fileName: String) = {
        println()
        manager(Source.fromFile(fileName)) { source =>
            val size = source.getLines.size
            println(s"file $fileName has $size lines")
            if (size > 20) throw new RuntimeException("Big file!")
        }
    }

}

/*
编译:
[root@master scalar]# scalac TryCatchArm.scala 

// 文件行数大于20行,捕获到自定义的异常
[root@master scalar]# scala TryCatchARM /tmp/sbt144959257350048385.log

file /tmp/sbt144959257350048385.log has 55 lines
Non fatal exception! java.lang.RuntimeException: Big file!
Closing resource ...

文件不存在的时候报错,不在是抛异常,也没有捕获到
[root@master scalar]# scala TryCatchARM /tmp/test.txt

Non fatal exception! java.io.FileNotFoundException: /tmp/test.txt (No such file or directory)
Closing resource ...
java.util.NoSuchElementException: None.get
    at scala.None$.get(Option.scala:347)
    at scala.None$.get(Option.scala:345)
    at manager$.apply(TryCatchArm.scala:14)
    at TryCatchARM$.countLine(TryCatchArm.scala:26)
    at TryCatchARM$$anonfun$main$1.apply(TryCatchArm.scala:21)
	at TryCatchARM$$anonfun$main$1.apply(TryCatchArm.scala:21)
    at scala.collection.IndexedSeqOptimized$class.foreach(IndexedSeqOptimized.scala:33)
    at scala.collection.mutable.ArrayOps$ofRef.foreach(ArrayOps.scala:186)
    at TryCatchARM$.main(TryCatchArm.scala:21)
    at TryCatchARM.main(TryCatchArm.scala)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at scala.reflect.internal.util.ScalaClassLoader$$anonfun$run$1.apply(ScalaClassLoader.scala:70)
    at scala.reflect.internal.util.ScalaClassLoader$class.asContext(ScalaClassLoader.scala:31)
    at scala.reflect.internal.util.ScalaClassLoader$URLClassLoader.asContext(ScalaClassLoader.scala:101)
    at scala.reflect.internal.util.ScalaClassLoader$class.run(ScalaClassLoader.scala:70)
    at scala.reflect.internal.util.ScalaClassLoader$URLClassLoader.run(ScalaClassLoader.scala:101)
    at scala.tools.nsc.CommonRunner$class.run(ObjectRunner.scala:22)
    at scala.tools.nsc.ObjectRunner$.run(ObjectRunner.scala:39)
    at scala.tools.nsc.CommonRunner$class.runAndCatch(ObjectRunner.scala:29)
    at scala.tools.nsc.ObjectRunner$.runAndCatch(ObjectRunner.scala:39)
    at scala.tools.nsc.MainGenericRunner.runTarget$1(MainGenericRunner.scala:65)
    at scala.tools.nsc.MainGenericRunner.run$1(MainGenericRunner.scala:87)
    at scala.tools.nsc.MainGenericRunner.process(MainGenericRunner.scala:98)
    at scala.tools.nsc.MainGenericRunner$.main(MainGenericRunner.scala:103)
    at scala.tools.nsc.MainGenericRunner.main(MainGenericRunner.scala)
[root@master scalar]# echo $?
1

*/

使用continue() 递归函数 替代while循环

@annotation.tailrec // 尾递归,代用递归函数是该函数最后的表达式,表达式返回的值就是所谓递归函数的返回值
def continue(conditional: => Boolean)(body: => Unit) {
    if (conditional) {
        body
        continue(conditional)(body)
    }
}

var count = 0
continue(count < 5) {
    // {...} 花括号体是body 参数列表,可以是参数也可以是表达式,注意2个及2个以上的表达式使用() 括起来,编译器会报错,需要使用{} 括起来
    println(s"at $count")
    count += 1

}

/*
output:
[root@master scalar]# scala call-by-name.sc 
at 0
at 1
at 2
at 3
at 4

*/

传名参数在每次被引用时估值。由于传名参数的求值会被推迟,并可能会一再地被重复调用,因此此类参数具有惰性

3.11 惰性赋值

  1. 由于表达式执行代价昂贵(例如:打开一个数据库连接),因此这类操作应推迟
  2. 为了缩短模块的启动时间,可以将当前不需要的某些工作推迟执行
  3. 为了确保对象中其他的字段初始化过程能优先执行,需要将某些字段惰性化处理
  4. 惰性赋值和方法调用的区别:方法调用,每调用一次,方法体都要执行一次,而惰性赋值,首次使用该值是,用于初始化的”代码体”才会被执行一次,这种只能执行一次的计算对于读写变量是没有意义的,所以关键字lazy 不能修饰var 变量
  5. 保护式(guard)实现惰性值,当客户代码引用惰性值时,保护式拦截引用并检查此时是否需要初始化惰性。由于保护式能确保惰性值在第一次访问之前已初始化,因此增加保护式检查只有当第一次引用惰性值的时候才是必要的。但不幸的是,很难解除之后的惰性值保护式检查。所以”立刻”值相比,惰性值具有额外的开销。因此只有当保护式代码的开销小于初始化带来的开销是,或者能简化系统初始化过程并确保执行顺序满足
object ExpensiveResource {
    lazy val resource: Int = init()
    def init() : Int = {
        // 执行某些高昂的操作
        0
    }
}

3.12 枚举

object Breed extends Enumeration {
    // Value 无参数,将对象名作为输入字符串
    type Breed = Value
    // 枚举类型包含了许多Value 类型
    // Value([String]) 带1个参数
    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")
}

import Breed._
println("ID\tBreed")
// id从0开始。Value 输入参数是一个整型ID值,该方法使用默认字符串(即变量名)的同时会显示指定的数值作为ID值
for (breed <- Breed.values) println(s"${breed.id}\t$breed")

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

// boolean endsWith(String suffix) 测试字符串是否以指定的字符串结尾
def isTerrier(b:Breed) = b.toString.endsWith("Terrier")

println("\nTerriers Again??")
Breed.values filter isTerrier foreach println
// Breed values filter isTerrier foreach println
/*
output:
[root@spark1 scala]# scala enumeration.sc 
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
*/
object WeekDay extends Enumeration {
    type WeekDay = Value
    // 不需要对枚举值命名
    val Mon,Tue,Wed,Thu,Fri,Sat,Sun = Value
}
// 导入每个枚举值,否则需要自行编写像WeekDay.Mon、WeekDay.Tus 这样的代码
import WeekDay._
def isWorkingDay(d: WeekDay) = ! (d == Sat || d == Sun)

WeekDay.values filter isWorkingDay foreach println
/*
output:
Mon
Tue
Wed
Thu
Fri
*/
  • 尽管Scala 中的枚举使用便利,但是需要预先知道集合中应包含哪些枚举值,而客户端是无法增加其他的枚举值的
  • 使用case类 替代枚举类型,虽然case 类更重量级些,但有两大优势:
    • case 类允许添加方法和字段,而且能够对枚举值应用模式匹配,这便为用户提供了更好的灵活性。
    • case 类能适用于包含位置枚举值的场景,只要有需要,用户代码便可以将更多的case类添加到基本的集合中

3.13 可插入字符串


scala> val name = "Buck Trends"
name: String = Buck Trends

scala> println(s"Hello,$name")
Hello,Buck Trends

scala> val gross = 100000F
gross: Float = 100000.0

scala> val net = 64000F
net: Float = 64000.0

scala> val percent = (net / gross) * 100
percent: Float = 64.0

// $ 需要转义,前面在加1$,两个$在一起输出的是1$,${gross} 可插入字符串,所以有3个$
// 最后两个% 第1%是转义,输出1个%
scala> println(f"$$${gross}%.2f vs. $$${net}%.2f or ${percent}%.1f%%")
$100000.00 vs. $64000.00 or 64.0%
scala> val i = 200
i: Int = 200
scala> val d = 100.22
d: Double = 100.22

scala> f"${d}%2d"
<console>:13: error: type mismatch;
 found   : Double
 required: Int
       f"${d}%2d"
           ^

scala> val s = "%02d: name = %s".format(5,"Dean Wampler")
s: String = 05: name = Dean Wampler

scala> val name = "Dean Wampler"
name: String = Dean Wampler

scala> s"123\n$name\n456"
res11: String =
123
Dean Wampler
456

// 原生(raw)  插入器,该插入器不会对控制字符进行扩展
scala> raw"123\n$name\n456"
res12: String = 123\nDean Wampler\n456
  • java 静态方法 String.format 将按照printf 的格式对字符串格式化。该方法的输入参数保护了格式字符串以及一组用于替代格式字符串的变量列表
  • String.format 方法还有另外一个版本,该版本中的第一个参数代表字符串的区域设置(该参数为Locate类型)

3.14 Trait: Scala语言的接口和”混入”

  • trait:允许将声明方法实现的接口,可以声明示例字段,在trait中并非只局限于声明或定义类型
  • trait 提供的这些被证实确实可以打破java 对象模型的一些局限性
class ServiceImportante(name: String) {
    def work(i:Int): Int = {
        println(s"ServiceImportante: Doing important work! $i")
        i + 1
    }
}
val service1 = new ServiceImportante("uno")
(1 to 3) foreach (i => println(s"Result:${service1.work(i)}"))

trait Logging {
    def info (message:String) :Unit
    def warning(message: String) : Unit
    def error (message: String): Unit
}

trait StdoutLoggin 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")
}

val service2 = new ServiceImportante("dos") with StdoutLoggin {
    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
    }
}

(1 to 3) foreach( i => println(s"Result:${service2.work(i)}"))
/*
output:
ServiceImportante: Doing important work! 1
Result:2
ServiceImportante: Doing important work! 2
Result:3
ServiceImportante: Doing important work! 3
Result:4
INFO: Starting work: i = 1
ServiceImportante: Doing important work! 1
INFO: Ending work: i = 1,result = 2
Result:2
INFO: Starting work: i = 2
ServiceImportante: Doing important work! 2
INFO: Ending work: i = 2,result = 3
Result:3
INFO: Starting work: i = 3
ServiceImportante: Doing important work! 3
INFO: Ending work: i = 3,result = 4
Result:4
*/
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值