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方法的类。
相关文章推荐
- Scala 自学笔记
- Scala 自学笔记 操作符
- scala自学笔记(2)
- Scala自学笔记
- scala学习笔记-特质
- Scala学习笔记--特质trait
- 快学Scala学习笔记及习题解答(10-11特质与操作符)
- scala学习笔记11 特质
- Scala自学代码笔记
- scala 自学笔记 高阶函数
- Scala学习笔记05--特质
- Scala 自学笔记 注解
- scala自学笔记(1)
- Scala学习笔记(七):Application特质
- Scala 自学笔记2_类
- Scala 自学笔记 模式匹配和样例类
- Scala学习笔记(3)-Scala特质
- Scala学习笔记——简化代码、柯里化、继承、特质
- scala学习笔记——特质
- Scala学习笔记5 - 特质