在事实上解读Scala规范(Scala Language Reference 2.11.x)之前,这里不保证内容的正确性。
用Scala IDE吧。
以val
定义的值实际上是一个常量。
以var
定义的变量,其值可变。
普通的变量声明时必须做初始化,可以借助类型推断而不指定其类型。
声明变量时可以指定其类型:
val greeting: String = "hello"
多个变量一起赋值:
val x, y = 100 // x == 100 and y == 100
Scala常见的类型:
// classes
Byte Char Short Int Long Float Double Boolean
Scala不刻意区分基本类型和引用类型,不需要类Java中的包装类型。
在Scala中使用方法,而不是类型转换来做数值类型之间的转换。
Scala中一些操作符实际上是方法。
Scala没有提供++
和--
操作符。
除方法外,Scala还支持函数,例:
import scala.math._
sqrt(2)
Scala没有静态方法,其对应的类似特性为单例对象(singleton object)。
Scala的类可以有个伴生对象(companion object),该对象中方法类似与Java中的静态方法。
不带参数的Scala方法可以省略圆括号。
在Scala中随处可见类似于函数调用的语法。
字符串中字符访问:
val hello = "hello"
hello(4) // 'o'
// equals to
hello.apply(4)
在Scala中,使用伴生对象的apply方法创建对象很常见,例:
val num = BigInt("12345")
BigInt.apply("12345")
READ THE FUCKING SPECIFICATION AND DOCUMENT.
scala.Unit
在Scala中if/else表达式有值,即if或else后表达式的值。
val s = if (x > 0) 1 else -1
在Scala中每个表达式都有一个类型。
// type: Any
val s2 = if (x > 0) "positive" else -1
无else部分:
val s3 = if (x < 0) -1
// euqalivant to
val s4 = if (x > 0) 1 else ()
()
的类型是scala.Unit
,表示无用的值。Java中void
表示没有值,而Unit
表示没有值的值。
多分支示例:
val s4 = if (x > 0) 1 else if (x == 0) 0 else -1
与Java不同,Scala的语句行尾不需要分号。
在单行中填入多个语句时,需要以分号隔开:
if (n > 0) { r = r * n; n -= 1 }
在Scala中,{}
块中可以包含一组表达式,块中最后一个表达式的值是块表达式的值。
val distance = { val dx = x - x0; val dy = y - y0; Math.sqrt(dx * dx + dy * dy) }
注意:Scala中赋值语句的类型是Unit
。x = y = 1
,则x的值为()
。
println()
, C风格的printf
。
与console交互:scala.io.StdIn.readLine
、scala.io.StdIn.readInt
等。
Scala有while
和do
循环语法。
Scala没有for(init; check; update)循环语法,其for循环的语法是:for( variable <- expression)。
示例:
for (i <- 1 to n) { // [1, n]
r = r * i
}
1 to n
返回[1, n]的Range
对象,循环变量i
前没有val或var修饰符,其作用域在循环体中。
1 unitl n
返回[1, n)的Range
对象,即不包含n。
Scala没有直接提供break或continue来退出循环。一种mock:
import scala.util.control.Breaks._
breakable {
for (i <- 1 to n) {
print(i + " ")
if (i == 5) break;
}
}
使用 variable <- expression的形式提供生成器,多个生成器之间用分号隔开。
for (i <- 1 to 3; j <- 1 to 3) print((10 * i + j) + " ")
每个生成器可以带有一个护卫,其是if开头的布尔表达式。
for (i <- 1 to 3; j <- 1 to 3 if i != j) print((10 * i + j) + " ")
如果for中循环体以yield开始,则构造出一个集合,这种循环称为for推导式。
val resultOfYield = for (i <- 1 to 3) yield i % 3
for推导式生成集合的类型与第一个生成器的类型是兼容的。
println(for (c <- "Hello"; i <- 0 to 1) yield (c.toInt + i).toChar)
println(for (i <- 0 to 1; c <- "Hello") yield (c.toInt + i).toChar)
方法对对象进行操作,而函数不是。在Java中只能用静态方法模拟。
def abs(x : Double) = if (x >= 0) x else -x
def factor(x : Int) = {
var r = 1
for (i <- 1 to x) r = r * i
r
}
def factor2(x : Int) : Int = if (x <= 0) 1 else x * factor2(x - 1)
必须指定函数参数的类型。只要函数不是递归的,就不需要指定函数的返回类型。
一定不能遗漏=
。
函数定义中可以指定参数的默认值。
调用函数时也可以指定参数名,带名参数调用不需要与函数定义中参数列表顺序相同。
混合使用未命名参数和命名参数调用函数时,需要保证未命名参数排在前面。
def decorate(str : String, left : String = "[", right : String = "]") = left + str + right
println(decorate("Hello"))
println(decorate("Hello", "<<<", ">>>"))
println(decorate("Hello", right = "]<<<"))
def sum(args : Int*) = {
var result = 0
for (arg <- args) result += arg
result
}
函数参数args
的类型是Seq
,可以使用任意数量的参数值调用:
val s = sum(1, 2, 3, 4, 5)
以Range
类型值调用时,需要转换(_*
):
sum(1 to 5 : _*)
过程(procedure)是没有=
的函数,其返回类型是Unit
。
为保持与函数语法的一致,也可以显式将过程以函数语法定义:
def p(s: String) {}
def p(s: String): Unit = {}
将val变量声明为lazy
时,该变量的初始化将被延迟,直到首次对其取值时。
可以将lazy
视为介于val和def的中间状态:
// 定义时立即求值
val words = scala.io.Source.fromFile("file.txt", "utf-8").mkString
// 首次使用时求值
lazy val words2 = scala.io.Source.fromFile("file.txt", "utf-8").mkString
// 每一次使用时求值
def words3 = scala.io.Source.fromFile("file.txt", "utf-8").mkString
Scala抛出异常方式与Java一样:throw new Exception("wrong")
,抛出的对象必须是java.lang.Throwable
的子类。
Scala没有受检异常概念:不用声明函数或方法可能会抛出的异常。
Scala的throw表达式的类型是Nothing
。在if/else表达式中,如果一个分支的类型是Nothing
,则整个表达式的类型是另一个分支的类型。
Scala异常捕获语法使用模式匹配:
var stream : InputStream = null
try {
stream = new URL("http2://www.google.com").openStream()
} catch {
case _ : MalformedURLException => println("Bad URL!")
case t : Throwable => t.printStackTrace()
} finally {
if (stream != null) {
stream.close()
}
}
()
访问元素需要一个长度不变的数组时,使用scala的Array
。
在JVM中,scala的Array
以Java数组的方式实现。
val nums = new Array[Int](10)
val a = new Array[String](10)
// companion object's `apply`
val s = Array("Hello", "World")
// modify element
s(0) = "Greeting"
变长数组在Scala中为ArrayBuffer
。
一些常见的操作:
val b = ArrayBuffer[Int]()
b += 1
b += (1, 2, 3, 5)
b ++= Array(8, 13, 21)
b.trimEnd(5)
在数组缓冲的尾部添加或删除元素是高效的操作。
在数组缓冲中任意位置插入或移除元素:
b.insert(2, 5)
b.insert(2, 7, 8, 9)
b.remove(2)
b.remove(2, 3)
数组和数组缓冲之间的转换:
val ss : Buffer[String]= s.toBuffer
val bb : Array[Int] = b.toArray
用for循环遍历数组或数组缓冲:
val arr = Array(1, 2, 3, 4, 5, 6)
for (i <- 0 until arr.length) {
print(arr(i) + " ")
}
每两个元素一跳,即存在步长:
for (i <- 0 until (arr.length, 2)) {
print(arr(i) + " ")
}
反向遍历:
for (i <- (0 until arr.length).reverse) {
print(arr(i) + " ")
}
不需要访问数组下标:
for (i <- arr) {
print(i + " ")
}
使用for推导式,不修改原始数组,产生全新的数组。
val arr = Array(1, 2, 3, 4, 5, 6)
// yield
val arr2 = for (e <- arr) yield 2 * e
// yield with guard
val arr3 = for (e <- arr if e % 2 == 0) yield 2 * e
数值数组/数组缓冲求和:
Array(1, 2, 3).sum
求最大和最小元素:
Array(1, 2, 3).min
ArrayBuffer("Mary", "Had", "a", "little", "lamb").max
排序:
val b = ArrayBuffer(1, 7, 2, 8)
val bSorted = b.sorted
指定比较函数排序:
val bDescending = b.sortWith(_ > _)
数组原位替换排序:
val arr = Array(1, 7, 2, 8)
scala.util.Sorting.quickSort(arr)
输出内容:
arr.mkString(" and ")
arr.mkString("<", ",", ">")
AGAIN, READ THE FUCKING DOCUMENT.
Scala的多维数组与Java一样,是通过数组的数组来实现的。
// 3 rows, 4 columns
val matrix : Array[Array[Double]] = Array.ofDim[Double](3, 4)
// access
matrix(1)(2) = 42
for (row <- matrix) {
for (col <- row) {
print(col + " ")
}
println
}
创建不规则的数组(非矩阵):
val triangle = new Array[Array[Int]](10)
for (i <- 0 until triangle.length) {
triangle(i) = new Array[Int](i + 1)
}
引入scala.collection.JavaConversions
中的隐式转换方法。
import scala.collection.JavaConversions.bufferAsJavaList
val command = ArrayBuffer("ls", "-al", "/home")
// java.lang.ProcessBuilder(List<String>)
val pb : ProcessBuilder = new ProcessBuilder(command)
import scala.collection.JavaConversions.asScalaBuffer
val cmd : Buffer[String] = pb.command()
println(cmd.asInstanceOf[ArrayBuffer[String]])
// they are the same
println(cmd == command)
创建不可变映射:
val scores = Map("Alice" -> 10, "Bob" -> 3, "Cartman" -> 8)
// or
val scores_ = Map(("Alice" -> 10), ("Bob" -> 3), ("Cartman" -> 8))
在Scala中,->
操作符用来创建对偶:"Alice" -> 10
产生("Alice" -> 10)
。
创建可变映射:
val scores_mutable = scala.collection.mutable.Map(("Alice" -> 10), ("Bob" -> 3), ("Cartman" -> 8))
创建指定类型的空映射:
val scores_empty = scala.collection.mutable.HashMap[String, Int]
使用()
记法获取映射中的值,如果映射中不包含指定的键则抛出异常。
val bobScore = scores("Bob")
使用contains
检查键值是否存在:
val bobScore2 = if (scores.contains("Bob_")) scores("Bob_") else 0
// a shortcut
val bobScore3 = scores.getOrElse("Bob", 0)
map.get(key)返回的是Option
对象。
只能对可变映射执行更新、添加和删除操作:
val scores_ = scala.collection.mutable.Map(("Alice" -> 10), ("Bob" -> 3), ("Cartman" -> 8))
// update
scores_("Alice") = 8
// add
scores_ += ("Frank" -> 5, "Green" -> 6)
// remove
scores_ -= "Green"
对不可变映射执行操作:
(1) val immutableMap
val scores = Map("Alice" -> 10, "Bob" -> 3, "Cartman" -> 8)
// new map
val newScores = scores + ("Frank" -> 5, "Green" -> 6)
(2) var immutableMap
var map = Map("Alice" -> 10, "Bob" -> 3, "Cartman" -> 8)
map = map + ("Frank" -> 5, "Green" -> 6)
注意:可变/不变的对象引用和可变/不可变的对象。
遍历:
val scores = Map("Alice" -> 10, "Bob" -> 3, "Cartman" -> 8)
for ((key, value) <- scores) {
println(key + ": " + value)
}
访问键或者值:
scores.keySet // Set[String]
scores.values // Iterable
生成新的映射:
val scores2 = Map("Alice" -> 10, "Bob" -> 3, "Cartman" -> 3)
val scores_ = for ((key, value) <- scores2) yield (value, key)
Scala的不可变树形映射:
val scores = scala.collection.immutable.SortedMap("Alice" -> 10, "Doug" -> 2, "Bob" -> 3, "Cartman" -> 8)
还是使用scala.collection.JavaConversions
中便捷的隐式转换方法。
//1 java to scala
import scala.collection.JavaConversions.mapAsScalaMap
val scores : scala.collection.mutable.Map[String, Int] = new java.util.TreeMap[String, Int]
// 2 java properties to scala map
import scala.collection.JavaConversions.propertiesAsScalaMap
val props : scala.collection.mutable.Map[String, String] = System.getProperties
//3 scala to java
import scala.collection.JavaConversions.mapAsJavaMap
val scalaMap = Map("A" -> 1, "B" -> 2)
val javaMap : java.util.Map[_, _] = scalaMap
Scala中元组(tuple)是不同类型的值的聚集,映射中的项(即对偶)是元组的特例。
元组定义:
val tuple = (1, 3.14, "Alice")
其类型解读为:Tuple3[Int, Double, java.lang.String]
或者(Int, Double, java.lang.String)
。
元组中元素(组元)访问:
println(tuple._1)
println(tuple._2)
println(tuple._3)
以模式匹配方式获取组元:
val (first, second, third) = tuple
println(first)
println(second)
println(third)
val (first_, second_, _) = tuple
println(first_)
println(second_)
压缩生成对偶数组:
val symbols = Array("<", "-", ">")
val count = Array(2, 10, 2)
// an array of tuple
val pairs = symbols.zip(count)
for ((s, c) <- pairs) {
print(s * c)
}
可以使用toMap
将对偶的集合转换为映射:
val map = pairs.toMap
@BanProperty
生成JavaBeans的getter/setter方法在Scala中,类并不声明为public
。Scala源文件中可以包含多个类,所有这些类具有public可见性。
class Counter {
// must initialize variable
private var value = 0
// default is `public`
def increment() { value += 1 }
// def current() = value
def current = value
}
object Counter extends App {
val myCounter = new Counter // or new Counter()
myCounter.increment() //setter
println(myCounter.current) //getter
}
调用对象的无参方法时可以省去()
。一种好的实践是对setter方法使用()
,而getter方法不使用。
Scala对类中每个字段都提供了getter和setter方法。
class Person {
// public field
var publicAge = 0
// private fields
private var privateAge = 0
// val field: final
val valAge = 0
// Scala getter
def age = privateAge
// Scala setter
def age_=(newAge : Int) {
if (newAge > privateAge) privateAge = newAge
}
}
// 定义伴生对象为App是不好的实践,这里为了方便
object Person extends App {
val person = new Person
// 1 public var field
// auto generated getter/setter
println(person.publicAge)
person.publicAge = 10
println(person.publicAge)
// 2 public val field
// auto generated getter
println(person.valAge)
// 3 self defined getter/setter
person.age = 30 //call age_=(30)
person.age = 21
println(person.age) //call age()
}
解读:
(1) publicAge
为公有字段,生成的JVM类中有一个私有字段publicAge
和相应的getter/setter方法;
(2) privateAge
为私有字段,生成的JVM类中getter/setter方法也是私有的;
(3) 对应于类中字段age
,Scala的getter/setter方法分别写为age
和age_=
。总是可以重新定义getter和setter方法。
$ scalac xxx.scala
$ javap -private xxx
(1) 字段是私有的,则生成的getter/setter也是私有的
(2) val字段,只有getter生成
(3) 不需要任何getter/setter时,将字段声明为private[this]
。
对应于类Person
中字段valAge
,Scala会生成一个私有的final字段和一个getter方法,但没有setter方法。
(1) var foo
: Scala自动生成getter/setter
(2) val foo
: Scala自动生成getter
(3) 自定义foo
和foo_=
方法
(4) 自定义foo
方法
在Scala中,不能实现只写属性,即只带setter不带getter的属性。
一般,类中方法可以访问该类的所有对象的私有字段。
限定不可访问另一个对象的私有字段:
private[this] var instanceValue = -1
对于对象私有的字段,Scala不生成getter/setter方法。
Scala允许将访问权限赋予指定的类:使用private[类名]
修饰符。约束只有指定类的方法可以访问指定的字段。类名
必须是当前定义的类,或包含该类的外部类。Scala会对此字段生成辅助的getter/setter方法。
class PrivateField {
// 1 private field
private var value = 0
// 2 private object field
private[this] var instanceValue = -1
//3 private class field
private[PrivateField] var classValue = -2
def increment() { value += 1 }
// can access other's private field
def isLess(other : PrivateField) = value < other.value
// cannot access private[this] field of another instance
// def isGreater(other : PrivateField) = instanceValue > other.instanceValue
def isGreater(other : PrivateField) = classValue > other.classValue
}
class CNestedClasses {
class InnerClass {
// TODO WTF???
//private[this] var innerField: Int
private[CNestedClasses] var innerField = -1
def compare(other : InnerClass) = {
innerField > other.innerField
}
}
def outerMethod() = {
val innerInstance = new InnerClass
val innerInstance2 = new InnerClass
innerInstance.compare(innerInstance2)
}
}
object PrivateField extends App {
val instance = new PrivateField
val instance2 = new PrivateField
// object field
println(instance.isLess(instance2))
// class field
println(instance.isGreater(instance2))
// nested class
val outerInstance = new CNestedClasses
println(outerInstance.outerMethod)
}
将Scala字段标注为@BeanProperty
时,会生成相应的Java属性。
class Person{
@BeanProperty var name: String =_
}
将生成四个方法:
name: String
name_=(newValue: String): Unit
getName(): String
setName(newValue: String): Unit
通常@BeanProperty
也可以用在主构造器上:class Person(@BeanProperty var name: String)
。
辅助构造器的名称为this。
每个辅助构造器必须以调用另一个辅助构造器或者主构造器开始。
class Person {
private var name = ""
private var age = 0
// 辅助构造器(auxiliary constructor)
def this(name : String) {
this() // call primary constructor
this.name = name
}
// another auxiliary constructor
def this(name : String, age : Int) {
// call another auxiliary constructor
this(name)
this.age = age
}
}
Scala的每个类都有主构造器。没有显式定义主构造器的类自动有一个无参的主构造器。
主构造器不以this方法定义,而与类的定义放在一起:
1 主构造器的参数直接放在类名之后;
2 主构造器会执行类定义中的所有语句。
class Person(val name : String, val age : Int) {
//
println("executing primary constructors")
def description = name + " is " + age + " years old."
}
私有的主构造器: class Person private(val id: Int) {}
Scala对内嵌的语法结构支持程度很高,可以在函数中定义函数,在类中定义类等。
class Network {
class Member(val name : String) {
// val contacts = new ArrayBuffer[Member]//1
// 类型投影, any Network's Member
val contacts = new ArrayBuffer[Network#Member] //2
}
private val members = new ArrayBuffer[Member]
def join(name : String) = {
val m = new Member(name) // inner class
members += m
m
}
}
object NetWork_App extends App {
val n1 = new Network
val n2 = new Network
val m1 = n1.join("m1")
val m2 = n1.join("m2")
m1.contacts += m2 // OK
val m3 = n2.join("m3")
// WRONG: m1.Member is different from m3.Member
//m1.contacts += m3 // 1 FAIL
m1.contacts += m3 // 2 OK
}
在Scala中每个实例都有它自己的Member
类,即n1.Member
和n2.Member
是两个不同的类。
在内嵌类中访问外部类的this,可以通过外部类.this
,或者:
class Network2(val name : String) { outer => // replace of `this`
class Member(val name : String) {
def description = name + ", " + outer.name
def description2 = name + ", " + Network2.this.name
}
}
Scala中没有Java中静态方法或静态字段的对应物,替代物是用object
这个语法结构定义对象。
对象定义了某个类的单个实例:
object Accounts {
private var lastNumber = 0
def newUniqueNumber = { lastNumber += 1; lastNumber }
}
Scala中欧给你对象可以实现:
1 存放工具函数和常量的地方
2 共享单个不可变实例
3 实现单例
对象本质上可以拥有类的所有特性。
对应于Java中同时有实例方法和静态方法的类,Scala中可以使用伴生对象实现。
类和它的伴生对象可以相互访问私有特性,它们必须在同一个源文件中。
对于类来说,可以访问其伴生对象,但伴生对象并不在作用域中。
class Account {
//class and its companion object can access
// each other's private field
val id = Account.newUniqueNumber
private var balance = 0.0
def deposit(amount : Double) {
balance += amount
}
}
// companion object(伴生对象)
// class and its companion object must in the same source file
object Account {
private var lastNumber = 0
def newUniqueNumber = { lastNumber += 1; lastNumber }
}
一个object
可以扩展类以及一个或多个特质,其结果是一个扩展了指定类和特质的类的对象、同时有在对象定义中给出的所有特性。
// class
abstract class UndoableAction(val description : String) {
def undo() : Unit
def redo() : Unit
}
// companion object as a default implementation
object DoNothingAction extends UndoableAction("Do nothing") {
override def undo() {}
override def redo() {}
}
object Action_app extends App {
val actions = Map("open" -> DoNothingAction, "save" -> DoNothingAction)
println(actions)
}
Object(param1, param2, ...)这个apply方法返回的是伴生类的对象。
class Account_ private (val id : Int, initBalance : Double) {
private var balance = initBalance
def currentBalance = balance
}
// companion object
object Account_ {
private var lastNumber = 0
// return companion class's object
def apply(initBalance : Double) = new Account_(newUniqueNumber, initBalance)
def newUniqueNumber = { lastNumber += 1; lastNumber }
}
App
特质扩展自特质DelayedInit
,所有带有该特质的类,其初始化方法都会被挪到delayedInit
方法中。
App
特质的main
方法捕获到命令行参数,调用delayedInit
方法。
object Hello {
def main(args : Array[String]) {
println("Hello")
}
}
object HelloApp extends App {
// access command line arguments through the `args`
if (args.length > 0) {
println(args(0))
} else {
println("HelloApp")
}
}
Scala没有枚举类型。可以借助Enumeration
类定义一个对象,并以Value方法调用初始化枚举中的所有可选值。
object ColorEnum extends Enumeration {
val Red, Yellow, Green = Value
val Blue = Value(4, "Blue")
// default name is field's name
val White = Value(5)
val Black = Value("Black")
}
object _main extends App {
// the 'enum''s type is ColorEnum.Value not ColorEnum
println(ColorEnum.Red.isInstanceOf[ColorEnum.Value])
println
import ColorEnum._
println(Red)
println(Yellow)
println(Green)
println(Blue)
println(White)
println(Black)
println
// access all values of ColorEnum
for (c <- ColorEnum.values) println(c.id + ": " + c)
// access enum with id or name
println(ColorEnum(0))
println(ColorEnum.withName("Red"))
}
与对象或类的定义不同,同一个包可以定义在多个文件中。
可以在同一个文件中为多个包贡献内容。
Scala的包和其他作用域一样支持嵌套,可以访问上层作用域中的名称。
在Scala中,包名是相对的。
使用绝对包名,以_root_
开始,例如:
val clients = new _root_.scala.collection.mutable.ArrayBuffer[Object]
包可以包含一个“串”,或者说路径区段。
package com.horstman.impatient {
// com和com.horstman中的成员在这里不可见
package people {
class Person {}
}
}
package com.horstman.impatient
package people
class Person_ { println("......") }
包可以包含类、对象和特质,但不能包含函数或变量的定义。
每个包可以有一个包对象,在父包中定义它,其名称与子包一样。包对象中可以放置函数和变量。
对源文件使用相同的命名规则是好习惯,一般将包对象放到诸如com/spike/package.scala
的文件中。
package com.horstman.impatient
class UserInnerMethod {
// can be access because of private[impatient]
def _description = "Hello" + new people.Person_().upperDescription
// cannot be accessed
//def __description = "Hello" + new people.Person_().description
}
package people {
class Person_ {
// access package object's constant variable
var name = defaultName
// only seen in this package
private[people] def description = "description"
// can be seen in upper package
private[impatient] def upperDescription = "upperDescription"
}
}
import java.awt.Color
import java.awt._
def handler(evt: event.ActionEvent){} // access subpackages
class Manager{
import scala.collection.mutable._
}
选择器(selector):
import java.awt.{Color, Font}
重命名:
import java.util.{HashMap => JavaHashMap}
隐藏:
// HashMap => _
import java.util.{HashMap => _, _}
import java.lang._
import scala._
import Predef._
以scala开头的包可以省略scala.
,如collection.mutable.HashMap
等价于scala.collection.mutable.HashMap
。
class Employee extends Person {
...
Scala中final
的含义与Java一致。
在Scala中重写一个非抽象方法必须使用override
关键字。
override
修饰符可以给出有用的错误提示:
调用超类方法: super.toString
等。
obj.isInstanceOf[C1] => obj instanceof C1
obj.asInstanceOf[C1] => (C1) obj
classOf[C1] => C1.class
模式匹配通常是更好的选择。
protected成员可以被任何子类访问,但不能从其他位置访问。
与Java不同,Scala的protected成员对类所属的包而言是不可见的。
protected[this]修饰符号:跟private[this]类似,将访问权限限制在当前的对象。
辅助构造器永远都不能调用超类的构造器,只有主构造器可以调用超类的构造器,注意这里的超类的构造器不一定是其主构造器。
Scala的类可以扩展Java类,其主构造器必须调用Java类的一个构造方法。
可以在子类中用同名的val字段重写超类的中的val字段(或不带参数的def)。
更常见的是用val重写抽象的def。
和Java一样,在Scala中可以通过包含带有定义或重写的代码块的方式创建匿名子类。
例:
class Person(val name : String) {}
val alice = new Person("Alice") {
def greeting = "greeting " + this.name
}
匿名子类可以理解为是一种结构类型,相应的可以作为参数类型定义:
def meet(p : Person { def greeting : String }) {
println(p.greeting)
}
其类别标记为Person { def greeting : String }
。
和Java一样,可以用abstract关键字来表示不能被实例化的类。
在子类中重写超类的抽象方法时,可以省略override关键字。
Scala类除抽象方法外,还可以拥有抽象字段。
抽象字段就而是一个没有初始值的字段。
具体的子类必须提供具体的字段。也可以使用匿名子类重写抽象字段。
超类会在子类构造前被构造。
提前定义语法可以在超类的构造器执行钱初始化子类的val字段。
一个示例:
class Creature {
val range : Int = 10
val env : Array[Int] = new Array[Int](range)
}
class Ant extends Creature {
override val range = 2
}
// predefinition
class Ant2 extends {
override val range = 2
} with Creature
object Main extends App {
val ant = new Ant
println(ant.range)
// since `Creature` is construct before `Ant`
println(ant.env.length) // 0
val ant2 = new Ant2
println(ant2.range)
println(ant2.env.length) // 2
}
Scala中,AnyRef
的eq
方法会检查两个引用是否指向同一个对象,其equals
方法默认调用eq
。
应用重写类的equals
和hashCode
方法,以提供业务实例的相等性判断。
对引用类型,==
会在null检查后调用equals
方法。
用scala.io.Source
对象处理文件。
import scala.io.Source
// 1 read lines
val source1 = Source.fromFile(currentDir + "myfile.txt", "UTF-8")
// get line iterator
val lineIter = source1.getLines
for (line <- lineIter) println(line)
// read again??? NO!
println(lineIter.isEmpty)
// close Source
source1.close
getLines
返回的是一个迭代器,可以对该迭代器使用toArray
或toBuffer
进行转换。
文件较小时,可以使用Source#mkString
将整个文件读取成一个字符串。
Source
对象扩展了Iterator[Char]
,可以逐字符处理。
调用Source
对象的buffered
方法,可以在返回的迭代器上调用head
查看而不是消耗下一个字符。
调用toInt
或toDouble
方法将字符串转换为数字。
使用StdIn.readInt()
从终端读整数。
使用Source.fromURL()
方法从URL中读取内容。
使用Source.fromString()
方法从字符串中读取内容,方便调试。
使用Source.stdin
从终端读去内容。
Scala没有提供读取二进制文件的方法。
Scala没有提供写入文件的支持。
Scala没有正式提供访问目录的支持。
@SerialVersionUID(-1L)
class Person extends Serializable
Scala的集合类都是可序列化的。
sys.process
包中有从字符串到ProcessBuilder
对象的隐式转换。用!
操作符执行,例:
import sys.process._
val result1 : Int = "ls -al .." !
val result2 : String = "ls -al .." !!
"ls -al .." #| "grep scala" !
import java.io.File
"ls -al .." #| "grep scala" #> new File("output.txt") !
"ls -al .." #| "grep scala" #>> new File("output.txt") !
"grep scala" #< new File("output.txt") !
p #&& q // 如果p执行成功,则执行q
P #|| q // 如果p执行失败,则执行q
使用Process
对象:
// parameter: command, start directory
val p = Process("pwd", new File("/home/zhoujiagen/local_git_repository/scala/scala"), ("LANG", "en_US"))
// execute it
p!
用String
类的r
方法构造Regex
对象。
val numPattern = "[0-9]+".r
如果正则表达式中包含\或",建议使用""""""(原始字符串语法)。
val wsNumwsPattern = """\s+[0-9]+\s+""".r
numPattern.findAllIn(testString)
wsNumwsPattern.findFirstIn(testString)
numPattern.findPrefixOf(testString)
numPattern.replaceFirstIn(testString, "XX")
numPattern.replaceAllIn(testString, "XX")
与Java类似,在要提取的子表达式两侧加上括号,构成组。
要匹配组,可以将正则表达式对象作为提取器使用。
val numItemPattern = "([0-9]+)\\s+([a-z]+)".r
// regex object as an extractor
val numItemPattern(number, item) = "99 alice"
println(number)
println(item)
// extract multiple matches
for (numItemPattern(number, item) <- numItemPattern.findAllIn(testString))
println(number + ": " + item)
Scala和Java一样,不允许类从多个超类继承。
Scala提供了特质(trait)而非接口。特质可以同时拥有抽象方法和具体方法,类可以实现多个特质。
Scala的特质完全可以像Java中的接口一样工作。
类扩展特质,用extends
关键字。用with
关键字连接扩展的多个特质。
特质中未被实现的方法默认就是抽象的。在重写特质的抽象方法时,不需要给出override
关键字。
在Scala中,特质中的方法并不需要一定是抽象的。
类扩展带有具体实现的特质,称该特质被“混入”了类。
这种情况下,当特质改变时,所有混入了该特质的类都必须重新编译。
在构造单个对象时,可以为它添加特质。
可以为类或对象添加多个相互调用的特质,从最后一个开始。
对特质而言,super.someMethod
并不像类那样拥有相同的含义。super.someMethod
调用的是级联的特质层次中的下一个特质,具体是哪一个,要按照特质被添加的顺序。一般来说,特质是从最后一个开始被处理。
对特质而言,无法直接从源代码中判断super.someMethod
会执行哪里的方法。如果想控制具体是哪一个特质的方法被调用,可以使用限定语法:super[DirectlyParentTrait].someMethod
,这里给出的类型DirectlyParentTrait
必须是当前特质的直接超类型。
特质中没有提供方法实现,在其子特质中提供的方法实现中调用super.someMethod
,需要在子特质实现方法前加上abstract
关键字,即该方法仍是抽象的。
特质可以包含大量工具方法,而这些方法依赖一些抽象方法来实现。使用该特质的类可以任意调用这些工具方法。
特质中的字段可以是具体的,也可以是抽象的。
如果给出字段的初始值,则字段就是具体的。
对于特质中每个具体字段,扩展/混入该特质的类都会获得一个与之相应的字段。这些字段不是被继承的,而是简单的被添加到类中。
特质中未被初始化的字段在具体的子类中必须被重写。
这种提供特质参数值的方式在临时构造对象时很便利。
和类一样,特质也可以有构造器,有字段的初始化和其他特质体中的语句构成。
构造器以如下顺序执行:
(1) 首先调用超类的构造器
(2) 特质构造器在超类构造器之后、类构造器之前执行
(3) 特质由左至右被构造
(4) 每个特质当中,父特质先被构造
(5) 如果多个特质共有一个父特质,且父特质已经被构造,则父特质不会被再次构造
(6) 所有特质构造完成后,子类被构造
一个示例:
object ConstructorsOfTrait extends App {
// output:
// Account
// Logger
// FileLogger
// ShortLogger
// SavingAccount
val account = new SavingAccount
}
trait Logger {
println("Logger")
def log(msg : String) {}
}
trait ConsoleLogger extends Logger {
println("ConsoleLogger")
override def log(msg : String) {
println(msg)
}
}
trait FileLogger extends Logger {
println("FileLogger")
// body of trait, as part of constructor of trait
val out = new PrintWriter("account.log")
out.println("# " + new java.util.Date().toString())
out.flush()
override def log(msg : String) {
out.println(msg)
out.flush()
}
}
trait ShortLogger extends Logger {
println("ShortLogger")
// abstract field
val maxLength : Int
override def log(msg : String) {
super.log(
if (msg.length > maxLength) msg.substring(0, maxLength) + "..." else msg
)
}
}
class Account {
println("Account")
var balance = 0.0
}
class SavingAccount extends Account with FileLogger with ShortLogger {
println("SavingAccount")
var interest = 0.0
// should override abstract filed in trait
val maxLength = 5
def withDraw(amount : Double) {
if (amount > balance) log("Insuffient funds")
else balance -= amount
}
}
特质不能有构造器参数,每个特质都有一个无参数的构造器。
从技术上讲,缺少构造器参数是特质与类的唯一区别。
因特质的构造器会早于子类构造器执行,对特质字段的初始化需要一些奇怪的语法:
(1) 在带有特质的对象中使用提前定义语法;
(2) 在类定义中extends
之后使用提前定义语法;
(3) 在特质构造器中使用lazy
值。
an example:
object InitialFieldsInTrait extends App {
// use case of predefinition in object
val account = new {
val filename = "account.log"
} with SavingAccount with FileLogger
account.withDraw(1.0)
// use case of predefinition in class definition
val account2 = new SavingAccount2
account2.withDraw(1.0)
// use case of lazy filed
val account3 = new SavingAccount with LazyFileLogger {
val filename = "account3.log"
}
account3.withDraw(1.0)
}
trait Logger {
def log(msg : String) {}
}
trait FileLogger extends Logger {
val filename : String
import java.io.PrintStream
val out = new PrintStream(filename)
override def log(msg : String) {
out.println(msg)
out.flush()
}
}
trait LazyFileLogger extends Logger {
val filename : String
import java.io.PrintStream
// use `lazy`: evaluated when firstly used
lazy val out = new PrintStream(filename)
override def log(msg : String) {
out.println(msg)
out.flush()
}
}
class Account {
var balance = 0.0
}
class SavingAccount extends Account with Logger {
var interest = 0.0
def withDraw(amount : Double) {
if (amount > balance) log("Insuffient funds")
else balance -= amount
}
}
// predefinition in class definition
class SavingAccount2 extends {
val filename = "account2.log"
} with Account with FileLogger {
var interest = 0.0
def withDraw(amount : Double) {
if (amount > balance) log("Insuffient funds")
else balance -= amount
}
}
特质也可以扩展类,这个类将会自动成为所有混入该特质的类的超类。
特质T扩展了类TSC,如果混入该特质的类C已经扩展了另一个类CSC时,只要CSC是特质的超类TSC的一个子类即可。如果CSC与TSC没有此关系,则C不能混入特质T。
除了扩展类的特质中对能够混入该特质的子类类型的约束外,Scala还提供了自身类型(self type)对一般的特质能够混入的类的类型做约束:
trait SomeTrait extends ...{
this: 类型Type =>
}
则该特质SomeTrait
只能被混入类型Type的子类。
自身类型同样也可以处理结构类型(structural type):这种类型只给出类必须拥有的方法,而不是类的名称。
Scala需要将特质翻译成JVM的类和接口。
(1) 只有抽象方法的特质被简单的变成一个Java接口;
(2) 如果特质有具体的方法,Scala会创建一个伴生类,该伴生类用静态方法存放特质的方法;
(3) (2)中的伴生类不会有任何字段。特质中的字段对应于接口中抽象的getter/setter方法,当某个类实现该特质时,这些字段被自动加入;
(4) 如果特质扩展了类,则伴生类并不继承这个超类,该超类会被任何实现该特质的类继承。