简单类和无参方法
1
2
3
4
5
|
class
Counter
{
private
var
value
=
0
// 必须初始化字段
def
increment
(
)
{
value
+=
1
}
// 方法默认公有
def
current
(
)
=
value
}
|
Scala中的类不声明为public,一个Scala源文件中可以有多个类。
1
2
|
val
myCounter
=
new
Counter
// 或new Counter()
myCounter
.
increment
(
)
|
调用无参方法时,圆括号是可写可不写的。推荐的做法是:如果是会改变对象状态的方法,就带上( );否则,就不带。
如果将方法的声明改为以下形式,那么可以强制不能带( )(下面要提到的getter就是这种风格的,所以调用getter时,必须不带( )):
1
2
3
4
5
|
class
Counter
{
private
var
value
=
0
// 必须初始化字段
def
increment
(
)
{
value
+=
1
}
// 方法默认公有
def
current
=
value
// 调用必须是myCounter.current这种风格
}
|
带getter和setter的属性
在Java中,通常将字段声明为私有的,然后添加公有的getter和setter方法来提供访问字段的接口。像这样拥有一对getter/setter的字段,通常被称为属性(property)。
Scala对每个字段都提供了getter和setter方法。
1
2
3
|
class
Person
{
var
age
=
0
}
|
在面向JVM的类中,这个简单的Person类有一个私有的age字段和相应的,公有的getter和setter方法。(如果将age声明为private的,getter和setter方法也是私有的。)Scala中,getter和setter分别叫做age和age_=。
1
2
|
println
(
fred
.
age
)
// 调用方法fred.age()
fred
.
age
=
21
// 调用方法fred.age_=(21)
|
将这个简单的Person编译后,使用javap查看生成的字节码,可以验证这一点。
1
2
|
// -private选项说明显示所有的类和成员
javap
-
private
Person
.
class
|
1
2
3
4
5
6
|
public
class
Person
implements
scala
.
ScalaObject
{
private
int
age
;
public
int
age
(
)
;
public
void
age_
$
eq
(
int
)
;
// =号被翻译成了$eq
public
Person
(
)
;
}
|
知道了这些默认实现后,就可以使用自己的实现来代替默认实现了。
1
2
3
4
5
6
7
8
|
class
Person
{
private
var
privateAge
=
0
def
age
=
privateAge
def
age_
=
(
newValue
:
Int
)
{
if
(
newValue
>
privateAge
)
privateAge
=
newValue
}
}
|
Scala中,字段和getter/setter间的关系,还有其他几种情况。
使用val声明的字段,是只有getter,因为val声明的是不可变的啊。Scala中不能实现只有setter的字段。
还有种对象私有字段。Scala中,方法可以访问该类的所有对象的私有字段,这一点与Java一样。如果通过private[this]来字段来修饰,那么这个字段是对象私有的,这种情况下,不会生成getter和setter。对象私有字段,只能由当前对象的方法访问,而该类的其他对象的方法是无法访问的。如果说分不清楚对象和类的区别,这里就又要犯浑了。
接下来是一种与private[this]相似的访问控制。Scala中可以使用private[class-name]来指定可以访问该字段的类,class-name必须是当前定义的类,或者是当前定义的类的外部类。这种情况会生成getter和setter方法。
Bean属性(L1)
使用 @BeanProperty注解来为字段生成符合JavaBeans规范的getter/setter方法。使用该注解后,将会生成4个方法:Scala的getter/setter和JavaBeans规范的getter/setter(如果是val声明,就没有setter部分了)。
1
2
3
4
5
6
|
import
scala
.
reflect
.
BeanProperty
// 在Scala 2.10.0之后已被废弃
// 使用scala.beans.BeanProperty代替
class
Person
{
@BeanProperty
var
name
:
String
=
_
}
|
构造器(Constructor)
在Scala中,有两种构造器,主构造器(primary constructor)和辅助构造器(auxiliary constructor)。
辅助构造器
首先来讨论辅助构造器,因为比主构造器更容易理解。辅助构造器与Java构造器很相似,但有两点不同:
- 名字是this(Java中构造器名称与类名相同)
- 辅助构造器必须以对已经定义的辅助构造器或主构造器的调用开始
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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
}
}
|
1
2
3
|
val
p1
=
new
Person
// 主构造器
val
p2
=
new
Person
(
"Fred"
)
// 第一个辅助构造器
val
p3
=
new
Person
(
"Fred"
,
42
)
// 第二个辅助构造器
|
主构造器
Scala中每个类都有主构造器,并且是与类定义混合在一起的。
主构造器的参数,是类名后()内的内容。
1
2
3
|
class
Person
(
val
name
:
String
,
val
age
:
Int
)
{
// val name: String, val age: Int 部分就是主构造器的参数
}
|
主构造器的参数被编译成字段,其值被初始化成构造时传入的参数,可以为这些字段也加上访问控制。
主构造器会执行类定义中的所有语句。
1
2
3
4
5
6
|
class
Person
{
println
(
0
)
def
printNum
(
num
:
Int
)
{
println
(
num
)
}
println
(
1
)
printNum
(
2
)
}
|
定义如上一个简单类,然后创建一个Person的对象,结果如下:
1
2
3
4
5
|
scala
>
val
fred
=
new
Person
0
1
2
fred
:
Person
=
Person
@
2c332d2a
|
也就是说,主构造器会将类定义内的,也就是{ }内的,所有语句执行一次。
如果主构造器参数不带val或var,那么会根据是否被方法使用来决定。如果不带val或var的参数被方法使用了,它会变为对象私有字段;如果没有被方法使用,则被当成一个普通的参数,不升级成字段。
主构造器或许不太好理解。Martin Odersky建议如此看主构造器:Scala中的类也接受参数,像方法一样。
可以将主构造器变为私有的,将private关键字放在圆括号前:
1
|
class
Person
private
(
val
id
:
Int
)
{
.
.
.
}
|
嵌套类(L1)
在Scala中,几乎可以在任何的语法结构中内嵌任何语法结构。可以类中定义类,也可以在方法中定义方法。
Scala中每个实例都有自己的内部类。参考下面这段代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
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
)
member
+=
m
m
}
}
val
chatter
=
new
NetWork
val
myFace
=
new
NetWork
|
chatter.Member类和myFace.Member类是不同的两个类。这一点与Java是不同的。
由于这一点特征,使得一些地方可能会碰见错误,这时候或许需要调整一下内部类。可以使用的方法有使用伴生对象或者使用类型投影(这里是将内部类中的Member换成NetWork#Member)。
与Java中一样,如果需要在内部类中使用外部类的引用,使用 外部类名.class的语法即可。不过Scala中有一个为这种情况服务的语法:
1
2
3
4
5
6
|
class
Network
(
val
name
:
String
)
{
outer
=
>
class
Member
(
val
name
:
String
)
{
.
.
.
def
description
=
name
+
" inside "
+
outer
.
name
}
}
|
本章习题解答。答案仅供参考,并不代表我的观点。
5.
1
2
3
4
5
6
|
import
scala
.
reflect
.
BeanProperty
class
Person
{
@BeanProperty
var
name
:
String
=
_
@BeanProperty
var
id
:
Long
=
_
}
|
生成的方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
Compiled
from
"Person.scala"
public
class
Person
implements
scala
.
ScalaObject
{
private
java
.
lang
.
String
name
;
private
long
id
;
public
java
.
lang
.
String
name
(
)
;
public
void
name_
$
eq
(
java
.
lang
.
String
)
;
public
void
setName
(
java
.
lang
.
String
)
;
public
long
id
(
)
;
public
void
id_
$
eq
(
long
)
;
public
void
setId
(
long
)
;
public
long
getId
(
)
;
public
java
.
lang
.
String
getName
(
)
;
public
Person
(
)
;
}
|
生成的JavaBeans方法当然应该是可以在Scala中调用的(不然有什么意义…)。不过个人不推荐在Scala中使用,这只是为了符合JavaBeans规范而已。
7.
1
2
3
4
|
class
Person
(
name
:
String
)
{
val
firstName
=
name
.
split
(
" "
)
(
0
)
val
lastName
=
name
.
split
(
" "
)
(
1
)
}
|
主构造参数到底是什么样的,推测是普通参数,因为并没有在任何方法中使用过。看字节码验证。
1
2
3
4
5
6
7
8
|
Compiled
from
"Person.scala"
public
class
Person
implements
scala
.
ScalaObject
{
private
final
java
.
lang
.
String
firstName
;
private
final
java
.
lang
.
String
lastName
;
public
java
.
lang
.
String
firstName
(
)
;
public
java
.
lang
.
String
lastName
(
)
;
public
Person
(
java
.
lang
.
String
)
;
}
|
字节码中并没有出现name,所以如我所想,是普通参数。
8.
主构造器4个参数,并且有默认参数。