目录
- 9.文件和正则表达式
- 10.特质
- 11.操作符
- 12.高阶函数
9.文件和正则表达式
Source.fromFile(...).getLines.toArray输出文件的所有行
Source.fromFile(...).mkString 以字符串形式输出文件内容
将字符串转换为数字,可以用toInt或toDouble方法
“正则”.r是一个Regex对象
如果你的正则表达式包含反斜杠或引号的话,用"""...""",如val p = """\s+[0-9]\s+""".r
如果正则模式包含分组,你可以用如下语法来提取它们的内容,for(regex(变量,...,变量)<- 字符串)
读取行
val source = Source.fromFile("myfile.txt","UTF-8")
source.getLines
- 读取字符 for( c <- source ) 处理c
- 读取词法单元和数字。 val tokens = source.mkString.split("\s+")
- 从URL或其他源读取 source.formURL、fromString、stdin
- 读取二进制文件:需要使用Java类库
- 写入文本文件:使用java的PrintWriter来写入文本文件
访问目录
- Scala没有访问目录中所有文件和遍历所有目录的类。
- 自己写函数解决,如下:
def subdirs(dir:File):Iterator[File]={
val children = dir.listFiles().filter(_.isDirectory)
children.toIterator ++ children.toIterator.flatMap(subdirs(_))
}
def main(args: Array[String]): Unit = {
val dirStr = "C:\\Users\\zhouliang6\\Desktop\\项目";
val dir = new File(dirStr)
for( d <- subdirs(dir)) println(d.toString)
}
序列化
- @SeriaVersionUID(42L) class Person extends Serializable
- 如果能接受缺省ID,可以省略@SeriaVersionUID注解
- scala集合类都是可序列化的
进程控制
- scala.sys.process包提供了用于与shell程序交互的工具,可以用Scala编写shell脚本。
import sys.process._
"ls -al .."!
- ls -al ..命令被执行,显示上层目录的所有文件。
- sys.process包包含一个从字符串到ProcessBuilder对象的隐式转换,!操作符执行的就是这个ProcessBuilder对象。
- !操作符返回结果:0:程序执行成功。失败显示非0
- !!操作会以字符串形式返回。
正则表达式组
《快学Scala》第九章习题解答
10.特质
- 特质中方法没有实现,则特质可以当做接口使用,子类要实现特质的接口
- 带有具体实现的特质,在子类中可以直接使用特质的实现方法。
叠加在一起的特质
- super调用特质层级中的下一个特质,特质从最后一个开始被处理。
带有特质的对象
- 特质B中的方法log有实现,但是特质B的实现满足不了子类Obj的需求,此时可以在构造子类Obj具体对象的时候混入一个更好的特质A(A实现了B并且重写了方法log)
trait Logged {
def log (msg :String){ } //有实现,非抽象方法
}
class SavingAccount extends Logged{
def withdraw(ammount:Double): Unit ={
log("withdraw :"+ammount)
}
}
trait ConsLogger extends Logged{ //重写特质Logged的log方法
override def log(msg:String){println(msg)}
}
object SavingAccount{
def main(args: Array[String]): Unit = {
val acct = new SavingAccount with ConsLogger //构造对象时候加入特质
acct.withdraw(100)
}
}
特质中抽象字段和具体字段
- 字段给出了具体值,那么字段是具体的
- 使用该特质的类都会获得一个字段与之对应,这些字段不是被继承的,而是简单地被加到了类当中,而超类中的字段是被继承的。
- 抽象字段,在一个具体的类中必须提供值。
trait ShortLogger{
val maxLength:Int //注:该抽象字段并没有在特质的构造器中使用,如果在构造器中使用了,则下面方法有误
}
class SavingAccount extend Account with ShortLogger{
val maxLength = 20 //不需要override
}
val acct1 = new SavingsAccount
val acct2 = new SavingsAccount with ShrotLogger{
val maxLength = 20
}
特质构造顺序
-
构造器由特质字段的初始化和其他特质中的语句构成
初始化特质中的字段
- 特质不能有构造器参数,每个特质都有一个无参数的构造器,如果特质的构造器中使用到了抽象字段,那么在子类中如何初始化该字段呢?
-
解决:在子类SavingsAccount中初始化抽象字段
扩展类的特质
- 特质可以扩展类,这个类将自动成为所有混入该特质的超类
trait PropertyChange extends PropertyChangeSupport{
}
class MyPoint extends PropertyChange{
}
class Test extends App{
val myp = new MyPoint
}
- 如果累已经扩展另一个类怎么办?
- 只要那是特质超类的一个子类即可。
class MyPoint extends Exception with PropertyChage //错误,因为Exception不是PropertyChangeSupport的子类
自身类型
- 当特质以如下代码开始定义时,它便只能被混入指定类型的子类。
this:类型 =>
快学scala第十章习题答案
11.操作符
apply和update方法
- f(arg1,arg2,...)是函数调用语法,但是可以应用到非函数和方法之外的值
- f(arg1,arg2,...) 等于 f.apply(arg1,arg2,...) //此时的f不是函数或方法
- f(arg1,arg2,...)=value 等于 f.update(arg1,arg2,...,value) //该机制只限于数组和映射
提取器
- 一个带有unapply方法的对象,一般可以创建的伴生对象中,其它地方也可以。
- 从对象中提取值。
- 提取固定数量值
- 返回Option[(元组类型)]或、单值目标类型的Option[类型]、Boolean
* Name对象,其unapply方法返回一个Option[(String,String)]
object Name{
def unapply(input:String)={
val pos = input.indexOf(" ")
if( pos == -1 ) None
else Some( (input.substring(0,pos) , input.substring(pos+1) )
}
}
- 使用方式
1.变量定义时使用,如下first、last变量
val author ="Cay Horstmann"
val Name(first,last) =author
// 实际调用Name.unapply(author )并返回(Cay,Horstmann)元组,然后将元组值赋给first、last
2.模式匹配中使用
author match{
case Name(a,b) => ...a,b分别绑定到Option的值上
}
带单个参数或无参数的提取器
- 此处的单参数和无参数不是unapply方法的参数,而是提取器可传入的参数,如Name(a,b)两个参数,也就是Option返回的元组中组件个数。
- 带unapply方法的对象
- 返回Option[Int]
- 返回Boolean
- 提取固定数量值
使用unapply提取单值,则返回一个目标类型Option
object Number{
def unapply(input: String ):Option[Int] ={
try{
Some( Integer.parseInt( input.trim ) )
}catch{
case ex :NumberFormatException => None
}
}
}
使用:定义变量n
val Number( n ) = "123"
- 无参:即IsCompond() 没有参数,说明其返回的不是Option[类型]也不是Opting[()],不然必须在IsCompond()中定义变量,那么要他干嘛?IsCompond()可以返回Boolean类型,Boolean值不用变量来接收,而只是做一个判断。即提取器只是测试输入而不将值提取出来。
// unapply返回Boolean,
object IsCompond{
def unapply(input: String )= input.contains(" ")
}
使用:
author match{
case Name(a, last @ IsCompound() ) => ...a,b分别绑定到Option的值上,
}
- @的用法
unapplySeq方法
- 要取任意长度的值得序列,应该用unapplySeq来命名方法.
- 返回一个Option[ Seq[A] ]
- 提取不固定数量值
object Name{
def unapplySeq(input:String):= Option[ Seq[String] ]={
if( input.trim == "" ) None
else Some( input.trim.split( "\\s+" ) )
}
}
12.高阶函数
作为值得函数
import scala.math._
val num =3.14
val fun = ceil _ //函数
fun(num) // 4
- ceil函数后的_意味着你确实指定的是这个函数,而不是碰巧忘记了给它参数。
匿名函数
- (x: Double ) = > 3 * x
- 该函数将传给它的参数乘以3
- Array(3, 2).map( (x: Double ) = > 3 * x ) //Array(9, 6)
- val triple = (x: Double ) = > 3 * x //存放到变量中
带函数参数的函数
- 接受函数参数的函数叫做高阶函数
- 函数类型写作:(参数类型)=>结果类型
- 1.接收函数作为参数的函数
valueAtOneQuarter(ceil _)
def valueAtOneQuarter( f:(Double) => Double ) = f(0.25)
- valueAtOneQuarter的类型为:结果类型为Double,参数类型为(Double)=>Double,所以函数类型为( (Double)=>Double ) => Double
- 2.高阶函数也可以产出另一个函数。
def mulBy(factor:Double) = (x :Double) => factor * x
- mulBy(3)返回匿名函数(x:Double)=>3*x。
- val quintuple = mulBy(5)
quintuple (20) //100 - mulBy的类型为:(Double) => ( (Double) => Double )
参数(类型)推断
def valueAtOneQuarter( f:(Double) => Double ) =f(0.25)
valueAtOneQuarter函数接收一个函数作为参数,且参数类型为(Double)=>Double
传入匿名函数调用,4种方式:
-
- valueAtOneQuarter( (x:Double) => 3 * x )
-
- valueAtOneQuarter( (x) => 3 * x )
因为valueAtOneQuarter知道传入的函数类型为(Double)=>Double所以可以省略Double
- valueAtOneQuarter( (x) => 3 * x )
-
- valueAtOneQuarter( x => 3 * x )
对于只有一个参数的函数,可以省略外围的()
- valueAtOneQuarter( x => 3 * x )
-
- valueAtOneQuarter( 3 * _ )
如果参数在=>右侧只出现一次,可以用"_"替换它。
- valueAtOneQuarter( 3 * _ )
注意:这些简写仅在参数类型已知情况下有效。valueAtOneQuarter函数在定义时已经知道了参数的类型,所以可以这样简写
如下4种都是相同函数:
val fun = (x:Double) => 3 * x
val fun = 3 * (_:Double)
val fun = x = > 3* (x:Double) (错误)
val fun = (x:Double) = > 3* (x:Double)
val fun:(Double)=>Double = 3 * _
先声明了fun为(Double)=>Double 类型,所以3 * _ 正确调用: fun(3) //结果:9.0
常用高阶函数
- Array类型有一个map方法,参数为函数,且参数类型为泛型。
def map[B](f: (A) ⇒ B): [Array][B]
- 调用
(1 to 9).map("*" * _).foreach(println _)
传入的匿名函数的非简写形式:(x:Int)=>"*" * x
Array("z","liang").map( "jd" + _).foreach(println _)
Array("z","liang").map( (x:String)=>"jd" + x ).foreach(println _)
传入的匿名函数的非简写形式:(x:String)=>"jd" + x
柯里化
- 将原来接受两个参数的函数变成新的接受一个参数的函数的过程。新的函数返回一个以原有第二个参数作为参数的函数。
- 接受两个参数的函数
def mul(x:Int ,y:Int) =x * y
定义柯里化函数
- 接受一个参数的函数,生成另一个接受单个参数的函数
def mulOneAtAtime(x:Int ) = (y: Int) => x * y
调用:mulOneAtAtime(6)(7) ,
调用过程mulOneAtAtime(6)结果是(y :Int)=>6 * y ,这个函数被应用到7,最终得到42
- 简写定义柯里化函数
def mulOneAtAtime(x: Int)(y: Int) = x * y
目的:可以把某个函数参数单领出来,已提供更多用于类型推断的信息。
控制抽象
- 将一系列语句组成不带参数也没有返回值的函数。
- 是个函数
- 没有参数
- 没有返回值
- 但要里面要执行一系列语句
- 过程就没有返回值也可以没有参数
def box(){
println("hi");
Thread.sleep(1000)
}
- 把一系列语句传给函数执行,称为控制抽象。因为执行的内容是通过外部传过来的。函数中用来接收一系列语句的参数类型为()=>Unit的函数,因为该一系列语句组成的函数不接受参数也没有返回值。
- 举例
def runInThread(block: ()=>Unit){
new Thread{
override def run(){ block() }
}.start()
}
- 调用
runInThread{ ()=> println("hi"); Thread.sleep(1000); }
- 省略()=>,可以使用换名调用表示法:在参数声明和调用该函数参数的地方去掉(),但保留=>
def runInThread(block: =>Unit){
new Thread{
override def run(){ block }
}.start()
}
- 调用
runInThread{ println("hi"); Thread.sleep(1000); }
柯里化
- 第二个参数类型为()=>Unit的函数,
- 调用
var x =10
until( x== 0) {
x -= 1
println(x)
}