您的位置:首页 > 其它

Scala 自学笔记3_特质

2015-02-13 17:05 176 查看

1、文件和正则表达式

读取行
import scala.io.Source
val source = Source.fromFile("myfile.txt","UTF-8") //第一个参数可以是字符串或是java.io.File
// 如果文件使用的是当前平台缺省的字符编码,则可以略去第二个字符串编码参数

val lineIterator = source.getLines //返回一个迭代器
for(l <- lineIterator) println(l)
val lines = source.getLines.toArray // 将行放到数组或数组缓冲中
val contents = source.mkString //整个文件读取成一个字符串。
注意:用完Source对象,需要close


读取字符
可以直接把Source对象当做迭代器,因为Source类扩展自Iterator[Char]
for( c <- source) println(c)


空格分词和数字
val tokens = source.mkString.split("\\S+")

如果文件中都是浮点数,可以转换
val numbers = for(w <- tokens) yield w.toDouble
val numbers = tokens.map(_.toDouble)

从控制台读取数字
println("How old are you ?")
val age = readInt() // 或者使用readDouble或readLong
注:这些方法假定下一行输入是数字,且前后无空格,否则报错NumberFormatException


从URL或其他源读取
val source1 = Source.fromURL("http://baidu.com","UTF-8")
val source2 = Source.fromString("Hello, World!")

val source = Source.stdin


读取二进制文件
Scala没有提供读取二进制文件的方法,需要用Java
val file = new File(filename);
val in = new FileInputStream(file)
val bytes = new Array[Byte](file.length.toInt)
in.read(bytes)
in.close()


写入文本文件
需要用Java
val out = new PrintWriter("numbers.txt")
for (i <- 1to 100) out.println(i)
out.close()

例外:当传递数字给pintf,需要 将它转换为AnyRef:
out.printf("%6d %10.2f", quantity.asInstanceOf[AnyRef], price.asInstanceOf[AnyRef])
为了避免麻烦,也可以使用String类的format方法:
out.print("%6d %10.2f".format(quantity, price))
注意:Console类的printf没有这个问题
printf("%6d %10.2f", quantity, price)


访问目录
import java.io.File
def subdirs(dir: File): Iterator[File] = {
val children = dir.listFiles.filter(_.isDirectory)
children.toIterator ++ children.toIterator.flatMap(subdirs _)
}
for (d <- subdirs(dir)) 处理 d


序列化
Java和Scala 中 声明一个可被序列化的类。
Java:
public class Person implements java.io.Serializable{
private static final long serialVersionUID = 42L;
}
Scala:
@SerialVersionUID(42L) class Persion extends Serializable
//Serializable特质定义在scala包,因此不需要显式引入。
注:如果能接受缺省的ID,可略去@SerialVersionnUID注解。

可以按照常规的方式对对象进行序列化和反序列化

val fred = new Persion(...)
import java.io._
val out = new ObjectOutputStream(new FileOutputStream("/temp/test.obj"))
out.writeObject(fred)
out.close()
val in = new ObjectInputStream(new FileInputStream("/tmp/test.obj"))
val savedFred = in.readObject().asInstanceOf[Person]

Scala集合类都是可序列化的,因此可以把他们用作可序列化类的成员:
class Person extends Serializable{
private val friends = new ArrayBuffer[Person]  //OK --- ArrayBuffer 是可序列化的
}


进程控制,用Scala编写shell脚本
import sys.proccess._
"ls -al .."!
sys.process 包含一个从字符串到ProcessBuilder对象的隐式转换。 ! 操作符执行的就是 这个ProcessBuilder对象。
! 操作符返回的结果是 被执行程序的返回值:成功是0,否则是非0
如果用 !! 而不是 !  , 输出会以字符串的形式返回:
val result = "ls -al .."!!

"ls -all .."#| "grep sec" !   // 管道

"ls -all .." #> new File("output.txt") // 重定向

"ls -all .." #>> new File("output.txt") //追加到文件末尾 而不是 从头覆盖

"grep sec" #< new File("output.txt") ! // 把某个文件的内容作为输入

"greg Scala" #< new URL("http://xxx/index.html") ! //从URL重定向输入

如果需要在调用命令时,设置环境变量和进入不同的目录,可以用Process对象的apply方法来构造ProcessBuilder
val p = Process(cmd, new File(dir), ("LANG", "en_UR")) // 分别为 命令,目录,变量(一串对偶)
"echo 42" #| p !


正则表达式

val numPattern = "[0-9]+".r // 用String类的r方法,构建scala.util.matching.Regex

val wsPattern = """\s+[0-9]+\s""".r // 用“原始”字符串语法"""...""",来规避反斜杠或引号的转义

for(matchString <- numPattern.findAllIn("99 bottles, 98 bottles")) // findAllIn方法返回遍历所有匹配项的迭代器。
处理 matchString

val matches = numPattern.findAllIn("99 bottles, 98 bottles").toArray // Array(99, 98), 把迭代器转换为数组。

val ml = wsPattern.findFirstIn("99 bottles, 98 bottles") // findFirstIn 返回首个匹配项, 返回的是Some("98"),一个Option[String]

numPattern.findPrefixOf("99 bottles, 98 bottles') // finPrefixOf开始部分匹配 , Some(98)
wsPattern.findPrefixOf("99 bottles, 98 bottles")// None

numPattern.replaceFirstIn("99 bottles, 98 bottles", "XX") ""XX bottles, 98 bottles"", 首个匹配替换
numPattern.replaceAllIn("99 bottles, 98 bottles", "XX") ""XX bottles, XX bottles"", 全部匹配替换

表达式分组
val numitemPattern = "([0-9]+) ([a-z]+)".r
val numitemPattern(num, item) = "99 bottles" // num 设为99, item 设为 bottles

for(numitemPattern(num, item) <- numitemPattern.findAllIn("99 bottles, 98 bottles"))
处理 num 和 item


2、特质

当接口使用的特质
trait Logger{
def log(msg: String) // 特质中未被实现的方法皆是抽象方法
}
class ConsoleLogger extends Logger{ // 用extends, 而不是implements
def log(msg: String) {println(msg)} //无需override
}

class ConsoleLogger extends Logger with Cloneable with Serializable // 多个特质用 with 关键字

和Java类似,Scala只能有一个超类,但可以有多个特质。


带有具体实现的特质
trait ConsoleLogger{
def log(msg: String) {println(msg)}
}
class SaveAccount extends Account with ConsoleLogger{
def withdraw(amount: Double){
if(amount > balancce) log("Insufficient funds")
else balance -= amount
}
}
与Java接口不同,特质 可以有具体方法


构造对象时,混入同类的不同实现特质
trait Logged{
def log(msg: String) {} //空实现
}
class SavingsAccount extends Account with Logged{
def withdraw(amount : Double){
if(amount > balance) log("Insufficient Funds")
}
}
这里的log 什么都不会做
trait ConsoleLogger extends Logged{
override def log(msg : String) {println(msg)}
}
val acct = new SavingAccount with ConsoleLogger
这里的acct 对象 在调用log方法时, ConsoleLogger特质的log方法就会被执行。


叠加调用的特质
trait TimestampLogger extends Logged{
override def log(msg:String){
super.log(new java.util.Data() + " " + msg)
}
}
trait ShortLogger extends Logged{
val maxLength = 15
override def log(msg: String){
super.log(){
if(msg.length <= maxLength) msg else msg.substring(0, maxLength -3) + "...")
}
}
}
特质的super.log方法,并不像类一样调用父类的log方法,
super.log调用的是特质层级中的下一个特质,具体哪一个,是根据特质添加顺序决定,由后向前执行。
val acct1 = new SavingAccount with ConsoleLogger with TimestampLogger with ShortLogger // Sun Feb 16 10:55:45 ICT 2015 Insttiecient
val acct1 = new SavingAccount with ConsoleLogger with ShortLogger with TimestampLogger // Sun Feb 16 1...

根据特质的顺序,执行super方法,
如果需要控制具体哪一特质的方法,则可以使用
super[ConsoleLogger].log(...),这里给出的类必须是直接超类型,无法使用继承层级中更远的特质或类


特质中重写抽象方法
trait Logger{
def log(msg: String) // 与上面空实现不同,这里是个抽象方法
}
trait TimestampLogger extends Logger{
abstract override def log(msg:String){
super.log(...)   //这里的super.log 是抽象的,Logger.log方法没有实现,无法知道哪个log方法最终被调用,取决于特质的混入顺序。
}
}<pre name="code" class="java">//因为调用了父特质的抽象方法,Scala认为TimestampLogger依旧是抽象的,需要混入一个具体的log方法,这里必须 给出 abstract override。



当做富接口使用它的特质
trait Logger{
def log(msg: String)
def info(msg: String){ log("INFO" + msg) }
def warn(msg: String){log("WARN" + msg)}
def sevre(msg: String){log("SEVERE:" + msg)}
}// 把抽象和具体方法结合在一起
class SavingAcccount extends Account with Logger{
def withdraw(amount : Double){
if(amount > balance ) server("Insufficient funds")
else ...
}
...
override def log(msg: String) {pirntln(msg)}
}
在Scala中 这样在特质中使用具体和抽象方法十分普遍。
在Java中, 就需要声明一个接口和一个额外的扩展该接口的类(一般是抽象类)。


特质中的具体字段
trait ShortLogger extends Logged{
val maxLength = 15 // 具体字段,不给初始值即为抽象字段
}
当有类混入这个特质时, 这个类会自动获得一个maxLength字段,这个字段不是被继承的,而是 简单地被加入到了 子类中。(这是由于JVM,一个类智能扩展一个超类)


特质中的抽象字段
trait ShortLogger extends Logged{
val maxLength : Int //抽象字段
override def (msg :String){
super.log{
if(msg.length <= maxLength) msg else ... // 在这个实现中 使用了 抽象字段 maxLength
}
}
}
1、在具体的类中,混入该特质时,必须提供maxLength字段
class SavingsAccount extends Account with ConsoleLogger with ShortLogger{
val maxLength = 20 // 无需override
}//<span style="font-family: Arial, Helvetica, sans-serif;">这里要求在特质的构造中没有用到这个值,否则需要提前声明或用懒值,详细看下面 ”初始化特质中的字段“</span>

2、在定义时未用,但在使用时混入该特质 构造对象时
class SavingAccount extends with Logged{...} //定义时,使用了 父特质
val acct = new SavingsAccount with ConsoleLogger with ShortLogger{
val maxLength = 20 // 增加的灵活性,非常便利
}


特质构造顺序
和类一样,特质也可以有构造器,由字段初始化和其他特质体中的语句构成
trati FileLogger extends Logger{
val out = new PrintWriter("app.log")
out.printlnn("#" + new Date().toString)
def log(msg: String) {out. println(msg); out.flush()}
}
以上语句在任何混入该特质的对象在构造时都会被执行。

类的构造器以如下顺序执行:
1、首先调用超类的构造器
2、特质构造器在超类构造器之后,类构造器之前执行
3、特质由左到右被构造
4、每个特质中,父特质先被构造
5、如果多个特质共有一个特质,而父特质已被构造,则不会在此构造
6、所有特质构造完毕,子类被构造。

例:
class SavingsAccount extends Account with FileLogger with ShortLogger
1、Account(超类)
2、Logger(第一个特质的父特质)
3、FileLogger(第一个特质)
4、ShortLogger(第二个特质,父特质已被构造)
5、SavingsAccount(类)


初始化特质中的字段
特质不能有构造参数, 每个特质都有一个无参的构造器
特质与类之间唯一的技术差别,就是缺少构造器参数
所以我们只能通过其他方式来初始化特质中的字段

trait FileLogger extends Logger{
val filename: String
val out = new PrintStream(filename)
def log(msg:String) {out.prinltn(msg);out.flush()}
}
val acct = new SavingsAccount with FileLogger{ val filname = "myapp.log"} //  这样行不通
这里和上一节类似,不过这里的问题出在构造顺序上,FileLogger先于子类构造执行,这里的子类并不那么容易看清楚
new语句构造的其实是一个扩展自SavingsAccount(超类)并混入了FileLogger特质的匿名类的实例。
filenam的初始化只发生在这个匿名子类中,但在轮到子类之前,FileLogger的构造器就抛出了空指针异常。

解决方法:
1、提前定义
val acct = new { // new 之后的提前定义块
val filename = "myapp.log" // 提前定义发生在常规的构造序列之前
} with SavingsAccount with FileLogger
如果要在类中做同样的事情,语法如此:
class SavingsAccount extends { // extends 后是提前定义块
val filename = "savings.log"
}with Account with FileLogger{
// SavingAcccount 的实现
}

2、在FileLogger构造器中使用懒值
trait FileLogger extends Logger{
val filename: String
lazy val out = new printStream(filename)
def log(msg : String){ out.println(msg)}
}//由于懒值在每次使用前都会检查是否已经初始化,所有不高效。


扩展类的特质
特质扩展特质,由特质组成的继承层级很常见
不常见的一种用法是, 特质也可以扩展类
这个类将会自动成为所有混入该特质的超类。
trait LoggedException extends Exception with Logged{
def log() { log( getMessage())} // log方法调用了从Exception 超类继承下来的getMessage()方法
}
class UnhappyException extends LoggedExxception{  // Exception自动成为我们类的超类
override def getMessage() =  "arggh!"
}

class UnhappyException extends IOExcpetion with LoggedExecption //ok , IOException是 Exception的子类
class UnhappyFrame extends JFrame with LoggedException //错误,不相关的超类


自身类型

this: 类型 =>

trait LoggedException extends Logged {
this: Exception =>
def log(){ log(getMessage()) }
}//该特质并不扩展Exception类,而是有一个自身类型,这以为这它只能被混入Exception的子类。
在特质方法中,我们可以调用该自身类型的任何方法,比如getMessage(),因为this必定是一个Exception。
val f = new JFrame with LoggedException // 错误,JFrame不是Exception 的子类型

在某些情况小夏自身类型 比 超类型版的 特质更灵活。 自身类型可以解决特质间的循环依赖。

自身类型也同样可以处理结构类型(structural type) -----这种类型只给出类必须拥有的方法,而不是类的名称。
trait LoggedException extends Logged{
this:{ def getMessage(): String } =>
def log() {log(getMessage())}
} 这个特质可以被混入任何拥有getMessage方法的类。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: