知识点:
1.简单类和无参方法
class Counter {
private var value = 0 //必须初始化字段
def increment() { value += 1} //方法默认是公有的
def current() = value
}
在Scala中,类并不声明为public,Scala源文件可以包含多个类,所有这些类都具有共有可见性。
val myCounter = new Counter //or new Counter()
myCounter.increment()
println(myCounter.current())
调用无参方法,可以写上圆括号,也可以不写。对于改值器方法(即改变对象状态的方法)使用(),对于取值器方法(不会改变对象状态的方法)去掉().可以通过以不带()的方式声明current来使用不带圆括号的风格。
2.带gettter和setter的属性
Scala生成面向JVM的类,对于声明的字段都会提供getter和setter方法。在Scala中,getter和setter分别叫做age和age_=,可以重新定义getter和setter方法。
- 对于私有字段而言,getter和setter方法也是私有的;
- 如果字段是val, 则只有getter方法被生成;
- 如果你需要任何getter和setter,可以将字段声明为 private[this].
颇具影响的Eiffel语言的发明者Bertrand Meyer提出了“ 统一访问原则”:某个模块提供的所有服务都应该能通过统一的表示法访问到,至于它们是通过存储还是通过计算来实现的,从访问方式上应无从可知。Scala符合这个原则。
3.只带getter的属性
如果属性的值在对象构建完成后不再改变,则可以使用val字段,scala会生成一个私有的final字段和getter方法。总结一下,在实现属性时有如下四个选择:
- var foo: Scala自动合成一个getter和setter方法
- val foo: Scala自动合成一个getter
- 由你来定义foo和foo_=方法
- 由你来定义foo方法
当你在Scala类中看到字段的时候,它代表的是一个私有字段加上getter方法(对val字段)或者getter和setter方法(对var字段而言)。
4.对象私有字段
private[this]修饰字段时,表明某个对象.value这样的访问不被允许。类定义的方法只能访问到当前对象的value字段,而不能访问同样类的其他对象的字段,这样的访问被称为对象私有的。
对于类私有的字段,Scala生成私有的getter和setter方法,而对于对象私有的字段,Scala不会生成getter和setter方法。
5.Bean属性
将Scala字段标注为@BeanProperty时,会自动生成getFoo/setFoo这样的方法。
Scala字段 | 生成的方法 | 何时使用 |
val/var name | 公有的name name_=(仅限于var) |
实现一个可以被公开访问并且背后是以一个字段形式保存的属性 |
@BeanProperty val/var name | 公有的name getName() name_=(仅限于var) setName(…) (仅限于var) |
与JavaBean互操作 |
private val/var name | 私有的name name_=(仅限于var) |
用于将字段访问限制在本类的方法,就和Java一样,尽量使用private——除非你真的需要一个公有的属性 |
private[this] val/var name | 无 | 用于将字段访问限制在同一个对象上调用的方法,并不经常用到 |
private[类名] val/var name | 依赖于具体实现 | 将访问权赋予外部类,不经常用到 |
6.辅助构造器
1)辅助构造器的名称为this
2)每一个辅助构造器都必须以一个对先前已定义的其他辅助构造器或猪狗在其的调用开始。
如果没有显示定义主构造器则自动拥有一个无参的主构造器。
class Person{
private var age = 0
private var name = "" def this(name:String){
this()
this.name=name
} def this(name:String,age:Int){
this(name)
this.age = age
}
}
7.主构造器
1)主构造器的参数直接放置在类名后
2)主构造器会执行类定义中的所有语句
如果类名之后没有参数,则该类具备一个无参主构造器。通常可在主构造器中使用默认参数来避免过多的使用辅助构造器。构造参数可以是普通的方法参数,不带val或var,这样的参数如何处理取决于它们在类中如何被使用。如果不带val或var的参数至少被一个方法所使用,它将被升格为字段;否则,该参数将不被保存为字段,仅仅是一个可以被主构造器中的代码访问的普通参数。
class Person(name:String,age:Int){
def description = name + " is " + age + " years old"
}
//声明和初始化了不可变字段name、age,这两个字段都是对象私有的,等同于private[this] val字段的效果
针对主构造器参数生成的字段和方法
主构造器参数 | 生成的字段/方法 |
name: String | 对象私有字段,如果没有方法使用那么,则没有该字段 |
private val/var name: String | 私有字段,私有的getter/setter方法 |
val/var name: String | 私有字段,公有的getter/setter方法 |
@BeanProperty val/var: String | 私有字段,公有的Scala版和JavaBean版的getter/setter方法 |
8.嵌套类
在Scala中,几乎可以在任何语法结构中内嵌任何语法结构,在任何函数中定义函数,在类中定义类。
练习:(参考网址)
1.改进5.1节的Counter类,让它不要在Int.MaxValue时变成负数
class Counter {
private var value = 0 //必须初始化字段
def increment() {
if(value < Int.MaxValue){
value += 1
}
else value } //方法默认是公有的
def current() = value
}
2.编写一个BankAccount类,加入deposit和withdraw方法,和一个只读的balance属性
class BankAccount{
val balance = 0
def deposit(amount:Double) {}
def withdraw(){}
}
3.编写一个Time类,加入只读属性hours和minutes,和一个检查某一时刻是否早于另一时刻的方法before(other:Time):Boolean。Time对象应该以new Time(hrs,min)方式构建。其中hrs以军用时间格式呈现(介于0和23之间)
class Time(val hours:Int,val minutes:Int){ def before(other:Time):Boolean={
hours < other.hours || (hours == other.hours && minutes < other.minutes)
} override def toString():String={
hours + " : " + minutes
}
}
4.重新实现前一个类中的Time类,将内部呈现改成午夜起的分钟数(介于0到24*60-1之间)。不要改变公有接口。也就是说,客户端代码不应因你的修改而受影响
class Time(val hours:Int,val minutes:Int){ def before(other:Time):Boolean={
hours < other.hours || (hours == other.hours && minutes < other.minutes)
} override def toString():String={
(hours * 60 + minutes)+""
}
}
5.创建一个Student类,加入可读写的JavaBeans属性name(类型为String)和id(类型为Long)。有哪些方法被生产?(用javap查看。)你可以在Scala中调用JavaBeans的getter和setter方法吗?应该这样做吗?
import scala.beans.BeanProperty
//答案里导入的包 scala2.11.8没有这个类了,在API中查找包位置
class Student {
@BeanProperty var name:String = _
@BeanProperty var id:Long = _
}
javap –c Student 查看 生成了name()\name_=\setName()\getName() id()\id_=\setId()\getId()
Compiled from "Student.scala"
public class Student {
public java.lang.String name();
Code:
0: aload_0
1: getfield #15 // Field name:Ljava/lang/String;
4: areturn public void name_$eq(java.lang.String);
Code:
0: aload_0
1: aload_1
2: putfield #15 // Field name:Ljava/lang/String;
5: return public void setName(java.lang.String);
Code:
0: aload_0
1: aload_1
2: putfield #15 // Field name:Ljava/lang/String;
5: return public long id();
Code:
0: aload_0
1: getfield #24 // Field id:J
4: lreturn public void id_$eq(long);
Code:
0: aload_0
1: lload_1
2: putfield #24 // Field id:J
5: return public void setId(long);
Code:
0: aload_0
1: lload_1
2: putfield #24 // Field id:J
5: return public java.lang.String getName();
Code:
0: aload_0
1: invokevirtual #30 // Method name:()Ljava/lang/String;
4: areturn public long getId();
Code:
0: aload_0
1: invokevirtual #33 // Method id:()J
4: lreturn public Student();
Code:
0: aload_0
1: invokespecial #37 // Method java/lang/Object."<init>":
()V
4: return
}
6.在5.2节的Person类中提供一个主构造器,将负年龄转换为0
class Person(var age:Int){
age = if(age < 0) 0 else age
}
7.编写一个Person类,其主构造器接受一个字符串,该字符串包含名字,空格和姓,如new Person("Fred Smith")。提供只读属性firstName和lastName。主构造器参数应该是var,val还是普通参数?为什么?
val,如果为var的话,对应的字符串有get和set方法,因为只提供只读属性firstName和lastName,不能重复赋值。
8.创建一个Car类,以只读属性对应制造商,型号名称,型号年份以及一个可读写的属性用于车牌。提供四组构造器。每个构造器fc都要求制造商和型号为必填。型号年份和车牌可选,如果未填,则型号年份为-1,车牌为空串。你会选择哪一个作为你的主构造器?为什么?
class Car(val maker:String,val typeName:String){
辅助构造器参数分别为 型号年份、车牌、型号年份和车牌,不知道对不。
9.考虑如下的类
class Employ(val name:String,var salary:Double){
def this(){this("John Q. Public",0.0)}
}
重写该类,使用显示的字段定义,和一个缺省主构造器。你更倾向于使用哪种形式?为什么?
class Employ{
val name:String = "John Q. Public"
var salary:Double = 0.0
}
2