第七章 内建控制结构
使用scala的语法糖和特性 ,改善代码结构 ,更少的var变量 /更简单的循环 /更多的嵌套筛选函数 .
//可以自定义包结构 ,还可以在同一文件中定义多个包
package SecondWithProgrammingInScala.Chapter7 {
class ControlStructure(x: Int, y: Int) {
var temp = x
//if-else结构具有返回值 ,会将最后一行的变量返回
println(if (x > y) x else y)
//赋值语句不会再返回右边的值 ,不能使用 (x=2) < 3 这样的判断
//println((temp = 2) < y) //error
//for表达式进阶
val args = Array("Hello", ",", "Scala", "!")
//过滤(循环条件内使用if)
for (arg <- args
if arg != (",")
if arg != ("!")
) print(arg)
println()
//嵌套
for (arg <- args;
c <- arg.toCharArray
) print(c)
println()
//制造新集合
val newArgs =
for (arg <- args
if arg != (",")
if arg != ("!")
) yield arg
println(newArgs.toString)
//try处理异常 throw抛出 catch捕捉 finally
def tryCatch: String = {
try {
val a = x / y
a.toString
} catch {
case ex: Exception => throw ex
} finally {
println("tryCatch方法结束")
}
}
//match表达式 类似switch
args.foreach(arg => {
arg match {
case "Hello" => print("欢迎")
case _ => print(arg)
}
})
println()
//不使用break和continue ,用if-var(if true if false)去跳过和弹出
//或者使用递归 (有一定风险) 需要优化
}
/**
* 使用函数式风格打印一个乘法表
* 和Java的区别主要在于语法和val的使用
* 语法上 : 类的定义/隐藏的构造函数/if-else替代三元表达式/函数自带返回值
* 风格上 : 减少了var的使用 ,将变量隐藏在函数参数中 ,所有值在进入函数体后都是不可改变的
* 需要的每一个值都通过其他val值得出 ,层次比较清晰
* 而var(Java中重复赋值) ,到最后可能都忘记之前是什么值做什么用
*/
class MultiTable(row: Int, col: Int) {
private val maxWidthOfNum = (row * col).toString.length
private def makeRow(r: Int) = {
for (c <- 1 to col) yield {
val prod = (r * c).toString
val width = maxWidthOfNum - prod.toString.length
val padding = if (c == 1) " " * width else " " * (width + 2)
padding + prod
}
}
def printTable = {
for (r <- 1 to row) println(makeRow(r).mkString + "\n")
}
}
object Chapter7App {
def main(args: Array[String]): Unit = {
val multiTable = new MultiTable(12, 9)
multiTable.printTable
try {
val test1 = new ControlStructure(6, 2)
println(test1.tryCatch)
val test2 = new ControlStructure(2, 0)
println(test2.tryCatch)
} catch {
case ex: Exception => println(ex.getMessage)
}
}
}
}
第八章 函数和闭包
package SecondWithProgrammingInScala
/**
* 函数式风格会将函数划分一个一个小块 ,完成最简单最基本的功能 ,然后根据需要组装起来
* 小的组件易于理解/修改 ,这也与<代码清洁之道>的思想相同
* 只不过Java语言的设计上就是面向对象的 ,scala直接将函数作为主体 ,拆分起来更方便和简洁
*
* 因为主体是函数 ,scala设置了多种语法去描述/简化函数的定义和使用
* 本地函数 : 函数中定义并使用 ,作用于只在父函数内(嵌套,类似变量)
* 函数字面量 : (x:Int) => x+1 ,这是一个函数定义 ,但也可以将他作为变量保存 ,再调用变量
* 占位符 : _ ,用来指代函数字面量中的参数 (只能指代一个 ,保持没有歧义)
* 重复参数 : args:String* (类似java的 String ...args )
*/
object Fuction {
//封闭函数
def printTable(x: Int, y: Int) = {
def printRow(x: Int) = {
print("*" * x)
}
for (i <- 1 to y) {
printRow(x)
println()
}
}
printTable(5, 5)
//printRow() //error 封闭函数无法调用
val array = Array(1, 2, 4, 5, 6)
//函数字面量
var p = (x: Int) => print(x)
array.foreach(p)
println()
array.foreach((x: Int) => print(x))
println()
array.foreach(x => print(x))
println()
//占位符
array.foreach(print(_))
println()
//重复参数
def printWord(ch: String*): Unit = {
println(ch.mkString(" "))
}
printWord("I'm", "a", "good", "Person")
def main(args: Array[String]): Unit = {
Fuction
}
}
/**
* 闭包 : (x:Int) => x+more
* more是一个自由变量 , 其值及类型是在运行的时候得以确定的
* x是类型确定的 , 其值是在函数调用的时候被赋值的
* x+more 是开放的(包含自由变量)open term
* x+1 是封闭的closed term
* 函数会在运行时捕获自由变量 ,完成从开放到封闭的过程 -> 闭包
*/
object Closure {
def main(args: Array[String]): Unit = {
//1闭包会在使用时去捕捉自由变量的值 ,形成封闭的函数再进行调用
var more = 0
val addMore = (x: Int) => x + more //x+1
more = 1 //x+1
println(addMore(10)) //11
more = 99 //x+99
println(addMore(10)) //109
//2闭包内的变化也反馈到外部
val someNumbers = List(-11, -10, -5, 0, 5, 10)
var sum = 0
someNumbers.foreach(sum += _)
println(sum)
//3如果需要多个版本的more共存 ,并分别调用
//对每一个版本的more ,生成不同的addMore函数(快照)
//调用闭包时 ,依赖的外部变量已经确定( = 定义闭包时的more变量)
def makeIncreaser(more: Int) = (x: Int) => x + more
val func1 = makeIncreaser(1) // more=1
val func99 = makeIncreaser(99) // more=99
println(func1(10)) //11
println(func99(10)) //109
}
}
/**
* 尾递归
* 递归方法比循环更容易理解 ,代码也更加清晰
* 但是实际情况中 ,递归重复调用函数 ,导致方法栈溢出是一个严重的问题
* 因此scala对递归进行优化 ,让你书写递归并转换为高效的循环
* 但这个优化仅支持严格的尾递归
*/
object Chapter8App {
//最后一个是+操作
def boom(x: Int): Int = {
if (x == 0) throw new Exception("boom!") else boom(x - 1) + 1
}
//最后一个是递归
def bang(x: Int): Int = {
if (x == 0) throw new Exception("boom!") else bang(x - 1)
}
def main(args: Array[String]): Unit = {
//只调用一次bang函数 异常在里面
bang(5)
//调用多次boom函数
boom(5)
}
}
第九章 控制抽象
package SecondWithProgrammingInScala
import java.io.{File, PrintWriter}
/**
* 闭包的高级应用
*
* 参考知乎对JavaScript的闭包研究
* [到底什么是闭包 - 张恂老师](https://zhuanlan.zhihu.com/p/21346046)
*
* 函数A中声明函数B ,函数B作为参数传递给函数C并在C中执行 ,因为可以访问到A中的变量 ,称B的上下文是闭包
*
* 闭包是一个有状态的函数/一个有记忆的函数/一个只有一个方法的对象
* 传统函数是没有状态的 ,变量必须放在函数参数中传递 ,每次调用是幂等的
* 闭包则是把函数作为保存变量的环境 ,闭包内的变量被放在了不同的地方
* (函数内的局部变量在栈上 函数退出即销毁 ;闭包内的数据被放在堆上 可以重复访问)
*
* 闭包的出现是因为词法作用域(lexical scope) ,在函数作为头等公民的语言(scala,Javascript)中 ,函数可以作为参数和返回值
* 如果没有闭包 ,那么函数的free变量(不在使用处定义的变量)就没法正确的引用
*/
object ClosureApplication {
private def filesHere = (new java.io.File(".")).listFiles
//函数简化聚合 : 可以在函数内定义一个函数结构 ,将函数作为参数传递进去 类似与同一接口不同实
private def filesMatching(matcher: (String) => Boolean) = {
for (file <- filesHere; //函数作为变量
if matcher(file.getName)) //调用定义的函数->实际传入的函数
yield file
}
/**
* 这里使用了闭包的概念 - filesEnding函数A - _.endsWith(query)函数B - filesMatching函数C
* 调用filesEnding(".scala")时 ,".scala"作为一个自由变量(函数A的参数) ,被函数字面量_.endsWith(query)捕获
* 产生了新的函数_.endsWith(".scala") (引用函数A的参数的函数B),并将这个函数传入filesMatching(函数C)
* 此时传递的是已经闭合的函数快照(函数C中使用函数B ,函数B已经捕获函数A的参数)
*/
def filesEnding(query: String) =
filesMatching(_.endsWith(query))
def filesContaining(query: String) =
filesMatching(_.contains(query))
def filesRegex(query: String) =
filesMatching(_.matches(query))
filesEnding("txt")
//把函数作为变量可以创建复杂的控制结构(对函数进行操作)
//当一段逻辑需要重复多次 就可以进行重写 将变化的部分写成函数参数(有点像java的多态)
//定义一个函数 将传入的函数递归一次
def twice(op: Double => Double, x: Double) = op(op(x))
val three = (1 + 1) + 1
val four = twice(_ + 1, 2)
println(three)
print(four)
def main(args: Array[String]): Unit = {
ClosureApplication
}
}
/**
* 柯里化
* 把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数
* 类似于之前的闭包 将原函数拆分成2级 传入第一个参数生成函数快照 用快照接受剩下的函数进行运行
*/
object Currying {
def curriedSum(x: Int)(y: Int) = x + y
def closureSum(x: Int) = (y: Int) => x + y
val a = curriedSum(1)(2)
val b = closureSum(1)(2)
//因为参数列表分离 可以用Currying形成快照
//但原函数有2个参数列表 ,第二个参数用_标识 ,不可省略(与闭包快照不同)
val onePlus = curriedSum(1) _
val twoPlus = curriedSum(2) _
//调用函数快照
val result = onePlus(2) + twoPlus(3)
println(a)
println(b)
println(onePlus)
println(twoPlus)
println(result)
//租赁模式(柯里化+函数参数) : 比如对文件操作结束必须要关闭 ,将这个操作屏蔽在内部 ,把正常功能租给外界使用(有点像AOP)
def withPrintWriter(file: File)(op: PrintWriter => Unit) {
val writer = new PrintWriter(file)
try {
op(writer) //调用用户传入的方法 并将writer作为参数传入
} finally {
writer.close()
}
}
//(){}式传参 : 对只有一个参数的函数 (){}都可以传递参数 通过柯里化应用到多个参数上
val file = new File("test.txt")
//对于柯里化函数的每一个参数 ,都能生成 只有一个参数的函数(半成品) ,因此都可以使用{}
withPrintWriter(file) {
writer => writer.println("file")
}
withPrintWriter {
file
} { writer => writer.println("file") }
def main(args: Array[String]): Unit = {
Currying
}
}
/**
* 传名参数
* 传值参数
*/
object ByNameParameter {
//传名参数 : 传递函数类型的参数 并且省略参数列表
def method1(predicate: => Boolean) =
print("传名参数")
//传值参数 : 传递基本类型的参数
def method2(predicate: Boolean) =
print("传值参数")
//使用传名参数+柯里化 可以形成原生if else的效果
def if2(assertion: => Boolean)(method: => Any) {
if (assertion) method //如果判断条件成立 则执行条件体内的方法
else println("不成立")
}
def main(args: Array[String]): Unit = {
if2(3 > 2) {
println("成立")
}
if2(2 > 3) {
println("成立")
}
}
}