Scala类与对象
类简介
简介
类是对象的蓝图。一旦你定义了类,就可以用关键字new根据类的蓝图创建对象。在类的定义里,可以放置字段和方法,这些被笼统地称为成员。对于字段,不管是val还是var定义的,都是指向对象的变量。对于方法,用def定义,包含了可执行代码。字段保留了对象的状态或数据,而方法使用这些数据执行对象的运算工作。当类被实例化的时候,运行时环境会预留一些内存来保留对象的状态映像——即变量的内容。
示例
创建类示例:
class SumAccumulator { var sum = 0 }
然后实例化两次:
val acc: SumAccumulator = new SumAccumulator val csa: SumAccumulator = new SumAccumulator
这时内存里对象的状态映像如下:
由于字段sum是var类型而非val,所以sum可以被赋予新的Int类型的值:例如
acc.sum = 3
此时映像会变成:
Scala类的重要特性
访问修饰符
- Public是Scala默认的访问级别
为了保证对象的健壮性,可以把类中字段变为私有的(private)以阻止外界直接对它访问。因为私有字段只能被定义成在同一类里的方法访问,所有跟新字段的代码将锁定在类里。要声明字段是私有的,可以把访问修饰符private放在字段的前面。
代码示例:
class SumAccumulator { private var sum = 0 }
使用private修饰后,任何从类外部对sum的访问都将失败
val acc: SumAccumulator = new SumAccumulator acc.sum = 3 //编译不通过,因为sum是私有的
方法
1. Scala方法参数类型
Scala方法参数中的类型都是val而不是var类型
def add(a: Int, b: Int): Int = { a = a + b //编译错误:Reassignment to val (就是对val类型赋值的错误) a }
2. Scala方法中return
Scala方法中的return可以去掉,从而简化代码
代码示例:
def numSum(num:Int):Int ={ val sum = num + 10 sum //省略return关键字,简化代码 }
此时返回值就是sum
3. Scala方法中的“=”号
Scala方法中的“=”号非常重要:因为Scala编译器可以把任何类型转换为Unit,如果定义的函数中”=”号忘记了添加,那么Scala会默认它返回值为Unit类型。若你本来想返回一个String类型的值,由于没有添加”=”,String类型返回值会被转换为Unit类型而丢弃掉。
代码示例:
def printStr() { "这是String类型的返回值" }
返回结果为: ()
正确代码示例:
def printStr(): String = { "这是String类型的返回值" }
返回结果为: 这是String类型的返回值
4. Scala方法表达式
假如某个方法仅计算单个结果的表达式,这可以去掉花括号,如果结果表达式很短,甚至可以把它放在def的同一行里。
代码示例:
def numSum(num:Int):Int = num + 10
5. Scala中分号推断
scala程序中,语句末尾的分号通常是可选的,若一行仅有一个语句也可以不加分号。不过,如果一行包含多条语句时,分号则是必须的。
不加分号:
if(x < 2) println("too small") else println("ok")
必须加分号:
val x = 10; println(x) //两个语句,必须加分号
Scala通常的风格是把操作符放在行尾而不是行头:
错误示例:
val x = 10; val y = 3 val z = x //它会被编译器识别为z = x ; +y 两个语句 +y
打印结果:
10
正确示例:
val x = 10; val y = 3 val z = x + y println(z)
打印结果:
13
Scala分号推断规则:
- 疑问行由一个不能合法作为语句结尾的字结束,如句点或中缀操作符。
- 下一行开始于不能作为语句开始的词。
- 行结束于括号(...)或方框[...]内部,因为这些符号不能容纳多个语句。
6. Scala无参方法
调用Scala类中无参方法时,可以写上括号,也可以不写。对于类中的取值器来说,去掉()是不错的风格。
代码示例:
object exam1 { def main(args: Array[String]): Unit = { val per: Person = new Person per.talk() //ok per.talk //同样ok } } class Person { def talk(): Unit = println("Talking") }
如果你想强制使用这种风格,可以在声明方法时不带()
代码示例:
per.talk() //此时这种写法是错误的 per.talk //OK的 class Person { def talk = println("Talking") }
Scala类中getter和setter
getter和setter
Scala类中使用公有字段的话,任何人都可以修改这个字段,使得安全性大大降低。所以我们更倾向于使用getter和setter方法:
Scala对类中每个字段都提供了getter和setter方法,分别叫做age和age_=,
代码示例:
val per: Person = new Person per.age = 18 class Person { var age = 0 }
如果想查看这些方法,可以先编译Person类,然后用javap查看字节码:
你可以自己重新定义getter和setter方法
代码示例:
object exam1 { def main(args: Array[String]): Unit = { val per: Person = new Person per.age = 18 per.age = 16 //由于在setter里面控制了age不能变小,所以执行结果age不会变 println(per.age) per.age = 19 //使用setter,赋予大于原来的age println(per.age) } } class Person { private var privateAge = 0 //变成私有变量并改名 def age = privateAge def age_=(newAge: Int) { // age_= 不能有空格 if (newAge > privateAge) privateAge = newAge //使得年龄不能变小 } }
打印结果为:
18 19
Scala中每个字段生成getter和setter的限制:
- 如果字段是私有的,则getter和setter方法也是私有的。
- 如果字段是val,则只有getter方法被生成。
- 如果你不需要任何getter或setter,可以将字段声明为private[this]
Scala在实现类中属性时的四个选择:
- 自动生成一个getter和setter。
- 自动生成一个getter。
- 自定义foo和foo_=方法。
- 自定义foo方法。
Bean属性
JavaBean规范把Java属性定义为一堆getFoo和setFoo方法。类似于Java,当你将Scala字段标注为 @BeanProperty时,getter和setter方法会自动生成。
代码示例:
import scala.beans.BeanProperty object exam1 { def main(args: Array[String]): Unit = { val per: Person = new Person per.name = "zhagnsan" per.setName("lisi") //BeanProperty生成的setName方法 println(per.getName) //BeanProperty生成的getName方法 } } class Person { @BeanProperty var name:String = _ }
打印结果为:
Lisi
上述类Person中由@BeanProperty生成了四个方法:
1. name: String 2. name_= (newValue: String): Unit 3. getName(): String 4. setName (newValue: String): Unit
图示为针对字段生成的方法:
Scala类中构造器
和java或C++一样,Scala也可以有任意多的构造器。不过,Scala类有一个构造器比其他所有构造器都更为重要,它就是主构造器。除了主构造器外,Scala类还可以有任意多的辅助构造器。
辅助构造器
Scala中辅助构造器和Java或C++十分相似,只有两处不同:
- 辅助构造器的名称为this。
- 每一个辅助构造器都必须以一个对先前已定义的其他辅助构造器或主构造器的调用开始
这里有一个带有两个辅助构造器的类。
代码示例:
object exam1 { def main(args: Array[String]): Unit = { val per1: Person = new Person //主构造器 val per2: Person = new Person("Bob") //第一个辅助构造器 val per3: Person = new Person("Bob",18) //第二个辅助构造器 } } 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 } }
主构造器
在Scala中,每个类都有主构造器。主构造器并不以this方法定义,而是与类定义交织在一起。
主构造器的参数直接放在类名之后
代码示例:
object exam1 { def main(args: Array[String]): Unit = { val per: Person = new Person("Bob", 18) //使用主构造器实例化对象 println(per.name + " : " + per.age) } } class Person(val name: String, val age: Int) { //产生私有的name和age,没有setter //..... }
打印结果:
Bob : 18
上述简短的Person类定义极大简化了相同功能的Java代码:
与上例相同功能的Java代码示例:
class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } public String name() { return this.name; } public int age() { return this.age; } }
Scala主构造器定义的所有语句都会执行
代码示例:
class Person(val name: String, val age: Int) { //产生私有的name和age,没有setter println("主构造器定义的所有语句都会执行") //每次使用主构造器时都会执行 def description = name + "is" + age + "years old" }
不同类型的主构造器参数对应会生成的字段和方法:
如果想让主构造器变成私有的,可以像这样放置private关键字:
class Person private (val name: String, val age: Int) { //...... }
这样一来,类用户就必须通过辅助构造器来构造Person对象了。
Scala嵌套类
在Scala中,你几乎可以在任何语法结构中内嵌任何语法构造。你可以在函数中定义函数,在类中定义类。
代码示例:
class Network { //在Network类中定义类Member 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字段一样。也就是说chatter.Member和myFace.Member是不同的两个类。
作用:拿网络示例来说,你可以在各自的网络中添加成员,但是不能跨网添加成员。
代码示例:
val chatter = new Network val myFace = new Network val fred = chatter.join("Fred") val wilma = chatter.join("wilma") fred.contacts += wilma //OK val barney = myFace.join("Barney") //错误:不能将一个myFace.Member添加到chatter.Member元素缓冲中 fred.contacts += barney
在嵌套类中,你可以通过 外部类.this 的方式来访问外部类的this引用,就像Java那样。
class Network(val name: String) { //在Network类中定义类Member class Member(val name: String) { //.... def description = name + "inside" + Network.this.name } }
如果你觉得需要,也可以使用如下语法建立一个指向该引用的别名:
class Network(val name: String) { outer => //在Network类中定义类Member class Member(val name: String) { //.... def description = name + "inside" + outer.name } }
上述语法使得outer变量指向Network.this。对这个变量,你可以使用任何合法的名称。