类
简单类和无参方法
Scala类最简单的形式看上去和Java中的很相似
class Counter {
private var value = 0 //字段必须初始化
def increment() { //方法默认是公有的
value += 1
}
def current() = value
}
Scala中类并不申明为public。Scala源文件可以包含多个类,所有这些类都具有公有可见性。
使用类要做的是构造对象并按照通常的方法来调用方法:
val myCounter = new Counter //或new Counter()
myCounter.increment()
println(myCounter.current) //或myCounter.current()
使用哪种风格?对于改变对象状态的方法,使用()。而对于不会改变对象状态的方法去掉()。
可以通过以不带()的方式申明current来强制这种风格:
class Counter {
...
def current = value //定义中不带()
}
//用此方法定义,使用者就必须使用myCounter.current。不能带圆括号
带getter和setter的属性
之所以说getter和setter方法比共有字段更好,是因为他们让你可以从简单的get/set机制出发,并在需要的时候做改进。
Scala对每个字段都提供getter和setter方法。
class Person { //定义一个公有字段
var age = 0
private var name = ""
}
scala生成面向jvm的类,有一个私有的age字段及相应的getter和setter方法。方法是共有的。(对私有字段而言,getter和setter方法也是私有的。)
scala中,getter和setter分别叫做age和age_=。
println(fred.age) //调用fred.age()
fred.age = 21 //调用fred_=(21)
编译命令:
$ scalac Person.scala
$ javap -private Person
# 在REPL内运行javap命令
:javap -private Person
自定义getter和setter方法:
class Person {
private var privateAge = 0 //变为私有并改名
def age = privateAge
def age_=(newValue:Int){
if(newValue > privateAge) privateAge = newValue
}
}
//依然可以访问fred.age
val fred = new Person
fred.age = 30
fred.age = 21
println(fred.age) //30
统一访问原则:某个模块提供的所有服务都应该能通过统一的表示法访问到,至于它们是通过存储还是通过计算来实现的,从访问方式上应无从获知。
Scala对每个字段生成getter和setter方法,可以进行控制。
- 如果字段是私有的,则getter和setter方法也是私有的
- 如果字段是val,则只有getter方法被生成。
- 如果你不需要任何getter和setter,可以将字段声明为private[this]
只带getter的属性
有时只需一个只读属性,有getter没setter。如果属性的值在对象构建完成后就不再改变,可以使用val字段:
//生成私有的final字段和一个getter方法,但没有setter
class Message {
val timeStamp = java.time.Instant.now
...
}
有时不能随意修改属性值,但能通过其他方法被改变:
class Counter {
private var value = 0
def increment() { value += 1}
def current = value
}
在实现属性时有如下四个选择:
- var foo:scala自动合成一个getter方法和一个setter方法
- val foo:scala自动合成一个getter方法
- 自定义foo和foo_=方法
- 自定义foo方法
Scala中不能只有setter,没有getter。
对象私有字段
Scala中,方法可以访问该类的所有对象的私有字段。
class Counter {
private var value = 0
def increment() { value += 1 }
def isLess(other: Counter) = value < other.value
//可以访问另一个对象的私有字段
}
访问other.value合法,是因为other同意也是Counter对象。
Scala允许更加严格的访问限制,通过private[this]这个修饰符来实现。
private[this] var value = 0 //访问someObject.value将不被允许
使用private后,Counter类的方法只能访问到当前对象的value字段,而不能访问同样是Counter类型的其他对象的该字段。这样访问有时被称为对象私有的。
对于类私有的字段,Scala生成私有的getter和setter方法,但对于对象私有的字段,Scala根本不会生成getter和setter方法。
Scala允许将访问权限赋予指定的类。private[this]修饰符可以定义仅有指定类的方法可以访问给定的字段。这里的类目必须是当前定义的类,或者是包含该类的外部类。这种情况下,编译器生成辅助的getter和setter方法,允许外部类访问该字段。
Bean属性
Java中的属性方法命名为getFoo/setFoo
Scala中,将字段标注为@BeanProperty时,该方法会自动生成
imoprt scala.beans.BeanProperty
class Person {
@BeanProperty var name: String = _
}
生成四个方法:
- name:String
- name_=(newValue: String): Unit
- getName(): String
- setName(newValue: String): Unit
不同情况下生成的方法:
Scala字段 | 生成的方法 | 何时使用 |
---|---|---|
val/var name | 公有的name,name_=(仅限于var) | 实现一个可以被公开访问并且背后是以字段形式保存的属性 |
@BeanProperty val/var name | 公有的name,getName(),name_=(仅限于var),setName(…)(仅限于var) | 与JavaBeans互操作 |
private val/var name | 私有的name,name_=(仅限于var) | 用于将字段访问限制在本类的方法,就和Java一样。尽量使用private,出发真的需要一个公有的属性 |
private[this] val/var name | 无 | 用于将字段访问限制在同一个对象上调用的方法。并不经常用到 |
private[类名] val/var name | 依赖于具体实现 | 将访问权限赋予外部类。并不经常用到。 |
以主构造器参数的方式定义某字段,需要使用javabeans的getter和setter方法。使用如下形式定义:
import scala.beans.BeanProperty
class Person(@BeanProperty var name: String,@BeanProperty var sex: String)
辅助构造器
scala可以有任意多的构造器,有一个主构造器,比其他所有构造器都更重要。除主构造器外,还可以有任意多的辅助构造器。
辅助构造器和Java的构造器十分相似,只有两处不同。
- 辅助构造器的名称为this。(Java中,构造器名称和类名相同 —— 修改类名就不方便)
- 每一个辅助构造器都必须以一个对先前已定义的其他辅助构造器或主构造器的调用开始。
举例:
class Person {
private var name = ""
private var age = 0
def this(name:String){ // 一个辅助构造器
this() // 调用主构造器
this.name = name
}
def this(name: String, age: Int){ //另一个辅助构造器
this(name) //调用前一个辅助构造器
this.age = age
}
}
一个类如果没有显示定义主构造器,则自动拥有一个无参的主构造器即可。
以三针方式构建对象:
val p1 = new Person
val p2 = new Person("Jack")
val p3 = new Person("Jack",32)
主构造器
scala中,每个类都有主构造器。主构造器并不以this方法定义,而是与类定义交织在一起。
-
主构造器的参数直接放指在类名之后:
class Person(val name:String, val age: Int) { // {...} ... }
主构造器的参数被编译成字段,其值被初始化成构造时传入的参数。本例中,name和age成为Person类的字段。如new Person(“Fred”,42)这样的构造器调用将设置name和age字段。
-
主构造器会执行类定义中的所有语句。
class Person(val name: String,val age: Int){ println("Just constructed another person") //每当有对象被构造时,改代码就会被执行 def description = s"$name is $age years old" } //如需在构造过程中配债某个字段 class MyProg{ private val props = new Properties props.load(new FIleReader("myprog.properties")) //上述语句是主构造器的一部分 ... }
如果类名后无参数,则该类具备一个无参主构造器。这样一个构造器仅仅是简单地执行类体中的所有语句而已。
通常可以通过在主构造器中使用默认参数来避免过多地使用辅助构造器
class Person(val name: String = "", val age: Int = 0)
主构造器的参数可以采用任意形态:
class Person(val name: String, private var age: Int)
//申明初始化了
val name:String
private var age:Int
构造参数也可以是普通的方法参数,不带val或var。该参数如何处理取决于他们在类中如何被使用。
-
如果不带val或var的参数至少被一个方法所使用,将被升格为字段
class Person(name:String,age:Int){ def description = name + " is " + age + " years old " }
以上代码中申明并初始化了不可变字段name和age,而这两个字段都是对象私有的。类似于这样的字段等同于private[this] val字段的效果
否则,该参数不被保存为字段。仅仅是一个可以被主构造器中的代码访问的普通参数。(严格讲,这是一个具体实现相关的优化)
不同类型的主构造器参数对应会生成的字段和方法。
主构造器参数 | 生成的字段/方法 |
---|---|
name: String | 对象私有字段,如果没有方法使用name,则没有该字段 |
private val/var name: String | 私有字段,私有的getter/setter方法 |
val/var name: String | 私有字段,公有的getter/setter方法 |
@BeanProperty val/var name: String | 私有字段,公有的Scala版本和JavaBean版的getter/setter方法 |
scala中,类也接受参数,就像方法一样。
class Person(val name:String){
var age = 0
def description = s"$name is $age years old"
}
//拆解为类定义
class Person(valname:String){
var age = 0
def description = s"$name is $age years old"
}
//和一个构造器定义
class Person(val name:String){
var age = 0
def description = s"$name is $age years old"
}
如果想让构造器变成私有的,可以
class Person private(val id,Int){
...
}
//这样一来,用户必须通过辅助构造器来构造Person对象
嵌套类
Scala中,可以在任何语法结构中内嵌任何语法结构。可以在函数中定义函数,在类中定义类。
import scala.collection.mutable.ArrayBuffer
class Network {
class Member(val name: String){
val contacts = new ArrayBuffer[Member]
}
private val members = new ArrayBuffer[Member]
def join(name:String) = {
val m = new Member(name)
members += m
m
}
}
假如有如下两个网络:
val chatter = new Network
val myFace = new Network
scala中,每个实例都有自己的Member类,就和他们有自己的members字段一样。不同实体中的Member是不同的两个类。
和Java不同,Java中的内部类从属于外部类。Scala中构建新的内部类,只需要new chatter.Member,在java中,需要使用特殊的语法:chatter.new Member()。
可以在各自的网络中添加成员,但不能跨网添加:
val fred = chatter.join("fred")
val wilma = chatter.join("wilma")
fred.contacts += wilma
val barney = myFace.join("Barney") //类型为myFace.Member
fred.contacts += barney
//不能将一个myFace.Member添加到chatter.Member元素缓冲当中
如果不希望这样,则有两种解决办法:
-
将Member类移到别处。作为Network的伴生对象。
object Network{ class Member(val name: String){ val contacts = new ArrayBuffer[Member] } } class Network{ private val members = new ArrayBuffer[Network.Member] }
-
使用类型投影(type projection)Network#Member,其含义是"任何Network的Member"
class Network{ class Member(val name: String){ val contacts = new ArrayBuffer[Network#Member] ... } }
如果只想在某些地方,而不是所有地方,利用这个细粒度的"每个对象有自己的内部类"的特性,则可以考虑使用类型投影。
嵌套类中,可以通过外部类.this的方式来访问外部类的this引用,就像Java那样。如果需要,可以用如下语法建立一个指向该引用的别名:
class Network(val name:String){
outer =>
class Member(val name:String){
...
def description = s"$name inside ${outer.name}"
}
}
class Network { outer => 语法使得outer变量指向Network.this。这个变量,可以用任何合法的名称。self这个名称较常见,但在嵌套类中可能会引发歧义。这样的语法和"自身类型"相关。