Scala 类的定义和 Java 非常类似,也是以 class 开始
访问修饰符
如果不带访问范围的修饰符 public、protected、private,Scala 缺省定义为 public
Scala 不要求 public 类定义和其文件名同名
Scala 的私有成员和 Java 类似
使用 private 修饰的类或对象成员,只能在该类或对象中访问
class Outer{ class Inner{ private def f(){ println("f") } class InnerMost{ f() //OK } } (new Inner).f();// error: f is not accessible }
不同之处:Java 允许外部访问其嵌套类型的私有成员。
由 Protected 定义的成员只能由定义该成员和其派生类型访问
class p{ class Super{ protected def f() { println("f") } } class Sub extends Super{ f() } class Other{ (new Super).f() //error: f is not accessible } }
扩展:
通过为访问修饰符添加作用域参数,可以非常精确的控制所定义的类型能够被其它类型访问的范围。
作用域的语法如下:
private[x]或protected[x],其中 x 代表某个包、类或者对象,表示可以访问这个 private 或 protected 的范围直到 X。
成员方法
Scala 里方法参数的一个重要特征是它们都是val,不是var
this
Scala 使用 this 来引用当前对象本身
一般来说访问类成员时无需使用 this,但如果需要引用对象自身,this 就无法省略
辅助构造函数
在 Scala 中,除主构造函数之外的构造函数都称为辅助构造函数(或是从构造函数)
Scala 使用 this(…) 语法定义辅助构造函数,所有辅助构造函数的名称为 this。
def this(n:Int) = this(n,1)
所有 Scala 的辅助构造函数的第一个语句都为调用其它构造函数,也就是 this(…),被调用的构造函数可以是主构造函数或是其它构造函数(最终会调用主构造函数)
这样使得每个构造函数最终都会调用主构造函数,从而使得主构造函数成为创建类的单一入口点。
在 Scala 中只有主构造函数才能调用基类的构造函数,这种限制有它的优点,使得 Scala 构造函数更加简洁和提高一致性
Scala 会根据成员变量出现的顺序依次初始化它们
对象实例的变量组成了对象的内存映射
范围限定
Scala 中使用 require 方法为传入构造函数和方法的参数限制范围
class Rational (n:Int, d:Int) {
require(d!=0)
override def toString = n + "/" +d
}
require 方法是 Predef 定义的一个方法,Scala 环境自动载入这个类的定义,因此无需使用 import 引入
对于上述代码,如果用 0 作为d的值传入,系统将抛出 IllegalArgumentException 异常。
隐式类型转换
Scala 通过 implicit def 定义一个隐含类型转换
定义由整数到 Rational 类型的转换如下:
implicit def intToRational(x:Int) = new Rational(x)
抽象类
abstract class Element { def contents: Array[String] }
abstract 关键字用来定义抽象类,此修饰符表示所定义的类可能含有一些没有定义具体实现的抽象成员
上例中成员 contents 没有定义具体实现,这个方法称为“抽象方法”
无参方法
Scala的惯例是在方法不需要参数并且只是读取状态时使用“无参方法”(“无参方法”:没有参数列表,甚至连个空列表都没有)
abstract class Element { def contents: Array[String] def height: Int = contents.length def Int = if (height == 0) 0 else contents(0).length }
注:
带有空括号的方法定义,如 def height(): Int,被称为“空括号方法”
原则上,Scala 的函数调用可以省略所有的空括号
但如果使用的函数不是纯函数,也就是说这个不带参数的函数可能修改对象的状态或是我们需要利用它的一些副作用(如:打印到屏幕,读写 I/O 等),一般建议还是使用空括号,如下:
"hello".length // 没有副作用 println() // 最好别省略()
永远不要定义没有括号却带副作用的方法,因为那样的方法调用看上去像在选择一个字段。
Scala 对于无参数方法和空括号方法的使用没有区分得很严格
可以用空括号方法重载无参数方法,反之亦可
继承
Scala 中派生子类的方法和 Java一样,也是通过 extends 关键字
class ArrayElement(conts: Array[String]) extends Element { def contents: Array[String] = conts }
在 Scala 中,如果在定义类时没有使用 extends 关键字,这个类缺省继承 scala.AnyRef,如同在 Java 中缺省继承 java.lang.Object。
重写
在 Scala 中重写父类的一个非抽象成员需要使用 override 关键字修饰,实现抽象成员无需使用 override
示例:使用 override 关键字来修饰重写基类定义的toString方法
class Rational (n:Int, d:Int) {
override def toString = n + "/" +d
}
如果子类没有重写父类中的成员,不可以使用 override 关键字修饰符
重载
Scala 支持方法重载,重载的方法参数类型不同而使用相同的方法名称
多态
派生意味着子类的值可以用在任何可以使用父类值的地方,这种现象称为“多态”
示例:
val e: Element = new ArrayElement(Array("hello"))
调用对象的方法或成员变量的过程是一个动态绑定的过程,也就是说调用哪个方法或成员变量取决于变量的实际类型,而不是定义变量时指明的类型。
final 修饰符
在定义类的继承关系时,如果不希望基类的某些成员被子类重写,和 Java 类似,可以使用 final 来修饰类的成员。
class ArrayElement extends Element { final override def demo() { println("ArrayElement's implementation invoked") } }
此时 ArrayElement 的子类则无法重写其父类的 demo 方法
class LineElement extends ArrayElement { override def demo() { println("LineElement's implementation invoked") } }
注:此代码编译报错
如果希望某个类不可以派生子类,则可以在类定义前加上 final 修饰符
成员变量 - 抽象成员函数实现
Scala 中成员函数和成员变量地位几乎相同,处在同一个命名空间,也就是 Scala 中不允许定义同名的成员函数和成员变量
好处:可以使用成员变量来实现一个不带参数的抽象成员函数
class ArrayElement(conts: Array[String]) extends Element { val contents: Array[String] = conts }
此处使用成员变量来实现基类中不带参数的抽象函数
类参数 - 参数化成员变量
Scala 支持使用参数化成员变量,也就是把参数和成员变量定义合并到一起
class ArrayElement(val contents: Array[String]) extends Element { }
要注意的是,参数 contents 前面加上了 val 关键字,这是使用同名参数和同名成员变量的一个缩写形式,此成员变量无法重新赋值,其初始值为参数的值,可以在类的外面访问这个成员变量。
它的一个等效的实现如下:
class ArrayElement(val x123: Array[String]) extends Element { val contents: Array[String] = x123 }
Scala 允许使用 var 关键字来定义参数化成员变量,使用 var 定义的成员变量可以重新赋值
此外,Scala 也允许使用 private,protected,override 来修饰参数化成员变量,和定义普通成员变量的用法一样
class Tiger ( override val dangerous: Boolean, private var age: Int ) extends Cat
类型参数
class Rational (n:Int, d:Int) extends Ordered[Rational]{ ... override def compare (that:Rational)= (this.numer*that.denom)-(that.numer*that.denom) }
基类构造函数
要调用基类构造函数,把需要传递的参数或参数列表放在基类名之后的括号里即可
示例:
class LineElement(s:String) extends ArrayElement(Array(s)) { override def width = s.length override def height = 1 }
ArrayElement 的主构造函数带一个参数(类型:Array[String]),由于 LineElement 继承了 ArrayElement,所以 LineElement 需要传递一个参数到其基类的主构造函数。
上例中 LineElement 传递了 Array(s) 到 ArrayElement 的主构造函数(注:把 Array(s) 放在基类 ArrayElement 的名称后面的括号里)
Singleton object
Scala 中不能定义静态成员,在 Scala 中提供类似功能的是称为“Singleton object(单例对象)”的对象
Scala 中定义 Singleton object 使用 object 关键字,其它的与类定义非常类似,与类定义不同的是,singleton object不可以带参数
仅仅定义 Singleton object本身不会创建一个新的实例,单例对象在第一次被访问的时候才会被初始化
和 Java 类似,Scala 中任何 Singleto object,如果包含 main 方法,都可以作为应用的入口点。
object HelloWorld {
def main(args: Array[String]) {
println("Hello, world!")
}
}
在 Scala 中,把与类同名的单例对象称为类的“伴侣”对象( Companion object ),如果需要定义类的 companion object,Scala 要求把这两个定义放在同一个文件中。
类和其 companion object 可以互相访问对方的私有成员。
abstract class Element { def contents: Array[String] def height: Int = contents.length def Int = if (height == 0) 0 else contents(0).length def above(that: Element) :Element = Element.elem(this.contents ++ that.contents) def beside(that: Element) :Element = { Element.elem( for( (line1,line2) <- this.contents zip that.contents ) yield line1+line2 ) } override def toString = contents mkString " " } object Element { def elem(contents: Array[String]):Element = new ArrayElement(contents) def elem(chr:Char, Int, height:Int) :Element = new UniformElement(chr,width,height) def elem(line:String) :Element = new LineElement(line) }
Scala 为 Singleton object 的 main 定义了一个 App trait 类型
object HelloWorld extends App{
println("Hello, world!")
}
嵌套类
库函数的用户不要了解 Element 的继承关系,甚至不需要知道类 ArrayElement,LineElement 定义的存在,为了避免用户直接使用 ArrayElement 或 LineElement 的构造函数来构造类的实例,可以把 ArrayElement,LineElement 和 UniformElement 定义为私有
进一步可以把它们定义在类 Element 内部
object Element { private class ArrayElement(val contents: Array[String]) extends Element { } private class LineElement(s:String) extends ArrayElement(Array(s)) { override def width = s.length override def height = 1 } private class UniformElement (ch :Char, override val Int, override val height:Int ) extends Element{ private val line=ch.toString * width def contents = Array.fill(height)(line) } def elem(contents: Array[String]):Element = new ArrayElement(contents) def elem(chr:Char, Int, height:Int) :Element = new UniformElement(chr,width,height) def elem(line:String) :Element = new LineElement(line) }
包对象
一些通用的函数、变量都可以直接定义在包中
package object shop{ def showFruit(fruit: Fruit){ import fruit._ println(name) } }
调用:
通过 import 语句引入
import shop.showFruit showFruit(fruit)
按照惯例,包对象一般定义在 package.scala 中,包名为定义的包,如:shop