5.1 简单类和无参方法
Scala类最简单的形式和Java很相似:
scala> class counter{
| private var v=0 //必须初始化字段
| def increment() {v+=1}
def increment() {v+=1}
^
On line 3: warning: procedure syntax is deprecated: instead, add `: Unit =` to explicitly declare `increment`'s return type
| def current()=v
def increment() {v+=1}
^
On line 3: warning: procedure syntax is deprecated: instead, add `: Unit =` to explicitly declare `increment`'s return type
| }
def increment() {v+=1}
^
On line 3: warning: procedure syntax is deprecated: instead, add `: Unit =` to explicitly declare `increment`'s return type
defined class counter
scala> val mycount=new counter //使用类构造对象
mycount: counter = counter@abff8b7
scala> mycount.increment()
scala> mycount.v //私有变量无法被访问
^
error: variable v in class counter cannot be accessed in counter
scala> mycount.current //无参方法第一种调用方法——不带括号
res3: Int = 1
scala> mycount.increment
scala> mycount.increment()
scala> mycount.current() //无参方法第二种调用方法——带括号
res7: Int = 3 //注意这里是3而不是2,因为调用了两次increment方法(过程?)
在Scala中类并不声明为public。**方法默认是共有的。**Scala源文件可以包含多个类,所有这些类都具有公有可见性。
调用无参方法时(比如current),你可以写上(),也可以不写。
一般地,
-对改值器方法(即改变对象状态的方法)用()
-对取值器方法(即不会改变对象状态的方法)去掉()
在定义取值器方法时,也可以省去(),但是要注意,这么定义后,调用时也不能带有()
class counter{
....
def current= v
}
5.2 带getter和setter的属性
在Java中,这样一个类
public class person{
private int age;
public int getAge() { return age;}
public void setAge(int age) {this.age=age;}
}
像这样的一对getter/setter通常被称作属性。我们会说person类有一个age属性。
Scala对每个字段都提供getter和setter方法。
class person{
var age=0
}
在这里,Scala生成面向JVM的类,其中有一个私有的(私有??应该是公有吧)age字段以及相应的getter
和setter
方法。
这两个方法是公有的,因为我们没有将age声明为private。对私有字段而言,getter和setter方法也是私有的。
在Scala中,getter和setter分别叫做age
和age_=
。例如
println(chris.age) //调用方法chris.age()
chris.age =22 //调用chris.age_=(22)
你可以重新定义getter和setter方法。例如
class person{
private var privateAge=0
def age=privateAge
def age_=(v:Int) {
if (v>privateAge) privateAge= v; }
}
}
Scala对每一个字段生成getter和setter方法听上去有些恐怖。不过你可以控制这个过程。
- 如果字段是私有的,则getter和setter方法也是私有的
- 如果字段是val,则只有getter方法被生成
- 如果你不需要任何getter或setter,可以将字段声明为private[this]
5.3 只带getter的属性
有时你需要一个只读属性,有getter但没有setter。如果属性的值在对象构建完后就不再改变,则可以使用val字段:
class message{
val timestamp =java.time.instant.now
...
}
Scala会生成一个私有的final字段和getter方法,但没有setter。
有时,你需要这样的一个属性,客户端不能随便该值,但他可以通过某种方式被改变。前面的counter就是一个很好的例子。
class counter{
private var v=0 //必须初始化字段
def increment() {v+=1}
def current=v
}
counter有一个current属性(自定义getter),当increment方法(不是setter)被调用的时候更新,但没有对应的setter。
注意:
- 这里不能用val定义v
- getter方法没定义时没有(),调用时也没不能带()
小结一下,在实现属性时你有如下四个选择:
- var foo: Scala自动合成一个getter和setter方法
- val foo: Scala自动合成一个getter方法
- 有你自己来定义foo和foo_=方法
- 由你自己来定义foo方法
在Scala中,你不能实现只写属性——只有setter没有getter
在Scala类中看到字段的时候,记住它和java中的字段不同,它是一个私有字段,加上getter方法(对val字段而言)或者getter方法和setter方法(对var字段而言)。
5.4 对象私有字段
在Scala中,**方法可以访问该类的所有对象的私有字段。例如:
class counter{
private var v=0
def increment(){ v+=1}
def isless(other:counter) = v < other.v
//可以访问另一个对象的私有字段
}
这里访问other是合法的,因为other也是counter对象。
Scala允许我们定义更加严格的访问限制,通过private[this]
这个修饰符来实现。
private[this] var v=0 //访问someObject.v将不被允许
这样一来,counter类的方法只能访问到当前对象v的字段,而不能访问同样是counter类型的其他对象的该字段,这样的访问有时被称为对象是私有的。
5.5 Bean属性
如前面所说,Scala对于定义的字段提供了getter和setter方法。不过,这些方法的名称不是Java工具所预期的。JavaBeans规范把Java属性定义为一对getFoo/setFoo
方法(或者对于只读属性而言,为单个getFoo方法)。许多Java工具都依赖这样的命名习惯。
当你将Scala字段标注为@BeanProperty
时,这样的方法会自动生成。例如:
import scala.beans.BeanProperty
class person{
@BeanProperty var name: String= _
}
将会生成四个方法:
- name: String
- name_=(newValue: String): Unit
- getName(): String
- setName(): 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 | 依赖于具体实现 | 将访问权赋予外部类,不常用 |
说明:如果你以猪狗操起参数的方式定义了某字段(见5.7节),并且你需要JavaBeans版的getter和setter方法,像如下这样给构造器参数加上注解即可:
class person(@BeanProperty var name: String)
5.6 辅助构造器
和Java一样 ,Scala也可以有任意多的构造器。不过,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("chris") //第一个辅助构造器
val p3 = new person("chris", 22) //第二个辅助构造器
5.7 主构造器
在Scala中,每个类都有主构造器。主构造器并不以this方法定义,而是与类定义交织在一起。
-
主构造器的参数直接放置在类名之后。
class person(val name: String, val age: Int) { // (.....)中的内容就是主构造器的参数 ...... }
主构造器的参数被编译成字段,其值被初始化成构造时传入的参数。在本例中,name和age成为person类的字段。如new person(“chris”, 22)这样的构造器调用将设置name和age字段。
我们只用半行Scala就完成了七行Java代码的工作:
public 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;
}
}
-
主构造器会执行类定义中的所有语句。
例如:class person (val name:String, val age:Int){ println(" just constructed another person") def description = s"$name is $age years old" }
println语句时主构造器的一部分。每当有对象被构造出来的时候,上述代码就会被执行。当你需要在构造过程中配置某个字段时,这个特性特别有用。例如:
class myProg{
private val props = new properties
props.load(new FileReader("myProg.properties"))
//上述语句是主构造器的一部分
....
}
说明: 如果类名之后没有参数,则该类具备一个无参主构造器。这样一个构造器仅仅是简单地执行类体中地所有语句而已。
提示:你通常可以通过在主构造器中使用默认参数,来避免过多地使用辅助构造器。例如:
class person(val name:String, private var age:Int)
这段代码声明并初始化如下字段
val name:String
private val 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版和Java版的getter/setter方法 |
如果主构造器的表示法让你困惑,你可以不使用它,你可以使用一个或多个辅助构造器,不过要记得调用this()(如果你不和其他辅助构造器串接的话)。
但是,许多程序员都喜欢主构造器这种精简的写法。“在Scala中,类也接受参数,就像方法一样”,有一个名人说过。
当你吧主构造器的参数看作类参数时,不带val或var的参数就变得好理解了。这样的参数的作用域覆盖了整个类。因此,你可以在方法中使用它们。而一旦你这样做了,编译器就自动帮你把它保存为字段。
提示:Scala设计者认为每敲一个键都是珍贵的,因此他们让你可以把类和主构造器结合在一起。当你阅读一个Scala类时,你需要将它们分开理解。
如果你想让主构造器变成私有的,可以像这样放置private关键字:
class person **private** (val id:Int){.....}
5.8 嵌套类
在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字段一样。也就是说,chatter.Member和myFace.Member时不同的两个类。
说明:这和Java不同,在Java中的内部类从属外部类。
以我们的“网络”示例来说,你可以在各自的网络中添加成员,但不能跨网络添加成员。
val fred=chatter.join("Fred")
val willim= chatter.join("Willm")
fred.contacts+=willm //这没问题
val barney=myFace.join("Barney") //类型为myFace.Member
fred.contacts+=barney //你不能这么做,不能将myFace.Member添加到chatter.Member元素缓冲当中
对于社交网络而言,这样的行为时讲得通得。有两种解决办法:
- 首先,你可以将Member类迁移到别处。一个不错的位置就是Network’的伴生对象(我们在第6讲介绍)。
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 contracts=new ArrayBuffer[Network#Member]
}
....
}
如果你只是想在某些地方,而不是所有地方,利用这个细粒度的“每个对象有自己的内部类”特性,则可以考虑使用类型投影。关于类型投影,见后面19讲。
说明: 在嵌套类中,你可以通过外部类.this
的方式来访问外部类的this引用,就像Java那样。
如果你觉得需要,也可以通过如下语法建立一个指向该引用的别名:
class Network{ outer=>
class Member(val name:String){
...
def description= s"$name inside ${outer.name}"
}
....
}
注意outer=>
,这个语法使得outer变量只想Network.this。对这个变量,你可以用任何合法的名称。
这样的语法和自身类型有关,你会在19讲了解更多。