Scala 自学笔记3_特质

来源:互联网 发布:自动刷火车票软件 编辑:程序博客网 时间:2024/06/10 05:12

1、文件和正则表达式

读取行
import scala.io.Sourceval 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.toDoubleval 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没有提供读取二进制文件的方法,需要用Javaval file = new File(filename);val in = new FileInputStream(file)val bytes = new Array[Byte](file.length.toInt)in.read(bytes)in.close()

写入文本文件
需要用Javaval 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.Filedef 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方法来构造ProcessBuilderval p = Process(cmd, new File(dir), ("LANG", "en_UR")) // 分别为 命令,目录,变量(一串对偶)"echo 42" #| p !

正则表达式

val numPattern = "[0-9]+".r // 用String类的r方法,构建scala.util.matching.Regexval 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")// NonenumPattern.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]+)".rval numitemPattern(num, item) = "99 bottles" // num 设为99, item 设为 bottlesfor(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 Insttiecientval 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 ShortLogger1、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方法的类。











0 0
原创粉丝点击