scala的类可以有任意多的辅助构造器。
this
。class Demo {
private var name: String = _
private var age = 0
def this(name: String) {
this() // 调用主构造器
this.name = name
}
def this(name: String, age: Int) {
this(name) // 调用上一个辅助构造器
this.age = age
}
}
val a = new Demo // 主构造器
val b = new Demo("Alice") // 第一个辅助构造器
val c = new Demo("Alice", 42) // 第二个辅助构造器
在scala中,每个类都要主构造器。主构造器并不以this
方法定义,而是与类定义交织在一起。
class Demo(val name: String, val age: Int) {
// (...)中的内容就是主构造器的参数
}
主构造器的参数被编译成字段,其值被初始化成构造时传人的参数。在本例中, name和age成为Demo类的字段。如new Demo("Alice", 30)
这样的构造器调用将设置name和age字段。
2. 主构造器会执行类定义中的所有语句。例如:
class Demo(val name: String, val age: Int) {
println("类定义中的所有语句都会执行") // 对象被创建的时候,就会执行
def description = s"name: $name, age: $age"
}
println
语句是主构造器一部分。每当有对象被构造出来时,上述代码就会被执行。
说明:如果类名之后没有参数 ,则该类具备一个无参主构造器。这样一个构造器仅仅是简单地执行类体中的所有语句而已。
构造参数也可以是普通的方法参数,不带val或var。这样的参数如何处理取决于它们在类中如何被使用。
class Demo(val name: String, val age: Int) {
def description = s"name: $name, age: $age"
}
上述代码声明并初始化了不可变字段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版和JavaBeans版的getter和setter方法 |
如果主构造器的表示法让你困惑,则你不需要使用它。你只要按照常规的做法提供一个或多个辅助构造器即可,不过要记得调用this()
(如果你不和其他辅助构造器串接的话)。
你要额可以理解为:在Scala中,类也接受参数,就像方法一样。当你把主构造器的参数看作类参数时,不带val和var的参数就变得易于理解了。这样的参数的作用域涵盖了整个类。因此,你可以在方法中使用它们。而一旦你这样做了,编译器就自动帮你将它保存为字段。
如果想让主构造器变成私有的,可以像这样放置private 关键字:
class Demo private(val name: String, val age: Int) {
......
}
这样一来类用户就必须通过辅助构造器来构造Demo对象了。
在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): Member = {
val member = new Member(name)
members += member
member
}
}
考虑有如下两个网络:
val chatter = new Network
val myFace = new Network
在Scala中,每个实例都有它自己的Member类,就和它们有自己的members字段一样。也就是说,chatter.Member
和myFace.Member
是不同的两个类。
说明:这和Java不同,在Java中的内部类从属于外部类。Scala采用的方式更符合常规。举例来说,要构建一个新的内部对象,你只需要简单地new这个类名:new chatter.Member
。而在Java中,你需要使用一个特殊语法:chatter.new Member()
。
以上面的网络示例来讲,你可以在各自的网络中添加成员,但不能跨网添加成员。
val fred =chatter.join("Fred")
val wilma = chatter.join("Wilma")
fred.contacts += wilma // OK
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这个名称很常见,但用在嵌套类中可能会引发歧义。这样的语法和“自身类型”语法相关。
Scala没有静态方法或静态字段,可以用object这个语法结构来达到同样的目的。
对象的构造器在该对象第一次被使用时调用。对象本质上可以拥有类的所有特性,可以扩展其他类和特质。只有一个例外:不能提供构造器参数。
在Scala中,你可以通过类和与类同名的“伴生(companion)”对象来达到既有实例方法又有静态方法的类;类和它的伴生对象可以相互访问私有特性,它们必须存在于同一个源文件
中。
注意1:类的伴生对象的功能特性并不在类的作用域内。
一个object可以扩展类以及一个或多个特质,其结果是一个扩展了指定类以及特质的类的对象,同时拥有在对象定义中给出的所有特性。
注意2:Array(100)和new Array(100)很容易搞混。前一个表达式调用的是apply(100),交出一个单元素(整数100)的Array[Int],而第二个表达式调用的是构造器this(100),结果是Array[Nothing],包含了100个null元素。
每个Scala程序都必须从一个对象的main方法开始,如下:
object Demo {
def main(args: Array[String]): Unit = {
println("=======")
}
}
除了每次都提供自己的main方法,你还可以扩展App特质,然后将程序代码放入构造器方法体内,如下:
object Demo extends App {
println("=======")
}
如果需要命令行参数,则可以通过args属性得到,如下:
object Demo extends App {
if (args.length > 0) {
println(f"args: ${args(0)}")
println(s"args: ${args(0)}")
}
}
所有这些涉及一些小小的魔法。App特质扩展自另一个特质DelayedInit ,编译器对该特质有特殊处理。所有带有该特质的类,其初始化方法都会被挪到delayedini法中。App特质的main方法捕获到命令行参数,调用delayedInit方法,并且还可以根据要求打印出逝去的时间(-Dscala.time)。
Scala并没有枚举类型;不过,标准类库提供了一个Enumeration助手类,可以用于产出枚举。
定义一个扩展Enumeration类的对象并以Value方法调用初始化枚举中的所有可选值。例如:
object Demo extends Enumeration {
val Red, Yellow, Green = Value
def main(args: Array[String]): Unit = {
val Red = Value("stop")
val Yellow = Value("waiting")
val Green = Value(7, "go")
println(s"${Red.id}, ${Yellow.id}, ${Green.id}") // 输出: 3, 4, 7
println(s"${Demo2.Red.id}, ${Demo2.Yellow.id}, ${Demo2.Green.id}") // 输出: 0, 1, 2
}
}
每次调用Value方法都返回内部类的新实例,该内部类也叫作Value。或者,你也可以向Value方法传入ID 、名称,或两个参数都传。如果不指定,将在前一个枚举值的基础上加1,从0开; 默认名称为字段名。
记住枚举的类型是Demo.Value
而不是Demo,后者是握有这些值的对象。也可以增加一个类型别名:
object Demo extends Enumeration {
type Demo = Value
val Red, Yellow, Green = Value
}
现在枚举的类型变成了Demo.Demo
,但仅当你使用import语句时这样做才显得有意义。例如:
import Demo._
def doWhat(color: Demo)={
if(color == Red) "stop"
else if(color == Yellow) "waitting"
else "go"
}
枚举值的ID可通过id方法返回,名称通过toString方法返回。
参考:快学scala(第二版)