知识点:
1.扩展类 extends关键字,在定义中给出子类需要而超类没有的字段和方法,或者重写超类的方法。
2.重写方法 在Scala中重写一个非抽象方法必须 override 修饰符
public class Person{
…….
override def toString = getClass.getName + “[name=”+name+”]”
}
override 修饰符可以在多个常见情况下给出有用的错误提示,包括:
1)当你拼错了要重写的方法名
2)当你不小心在新方法中使用了错误了参数类型
3)当你在超类中引入了新的方法,而这个新的方法与子类的方法相抵触(易违约基类问题的体现,超类的修改无法在不检查所有子类的前提下被验证)
在scala中调用超类的方法和Java完全一样,使用super关键字。
public class Employee extends Person{
……
override def toString = super.toString + “[salary=”+salary+ “]”
}
3. 类型检查和转换
要测试某个对象是否属于某个给定的类,可以用isInstanceOf方法,如果测试成功,可以用asInstanceOf方法将引用转换为子类的引用:
if(p.isInstanceOf[Employee]){
val s = p.asInstanceOf[Employee]//s的类型为Employee
……
}
如果p指向的是Employee类及其子类(比如Manager)的对象,则p.inInstanceOf[Employee]将会成功;如果p是null,则p.isInstanceOf[Employee]返回false,且p.asInstanceOf[Employee]将会成功;如果p不是一个Employee,则p.asInstanceOf[Employee]将抛出异常;如果想测试p指向的是一个Employee对象但又不是其子类的话,可以用 if(p.getClass == classOf[Employee]).classOf方法定义在scala.Predef对象中,会被自动引入。
4.受保护字段和方法 将字段或方法声明为protected,这样的成员可以被任何子类访问,但不能从其他位置看到,protected的成员对于类所属的包而言,是不可见的。Scala提供了一个protected[this]变体,将访问权限定在当前的对象。
5.类有一个主构造器和任意数量的辅助构造器,每个辅助构造器都必须以对先前定义的辅助构造器或主构造器的调用开始。子类的辅助构造器最终都会调用住构造器,只有主构造器可以调用超类的构造器。
6.重写字段
Scala字段由一个私有字段和取值器、改值器方法构成,你可以用另一个同名的val字段重写一个val(或不带参数的def)。
常见案例:val重写抽象的def。
abstract class Person {
def id: Int //每个人都有一个以某种方式计算出来的ID
……
}
class Student(override val id:Int) extends Person //学生ID通过构造器输入
用val | 用def | 用var | |
重写val | 子类有一个私有字段(与超类的字段名字相同——没问题) getter方法重写超类的getter的方法 |
错误 | 错误 |
重写def | 子类有一个私有字段 getter方法重写超类的方法 |
和JAva一样 | var可以重写getter/setter对。只重写getter会报错。 |
重写var | 错误 | 错误 | 仅当超类的var是抽象的才可以 |
def只能重写另一个def;val只能重写另一个val或不带参数的def;var只能重写另一个抽象的var。
7. 匿名子类
可以通过包含带有定义或重写的代码块的方式创建一个匿名的子类,比如:
val alien = new Person("Fred") {
def greeting = "Greetings,Earthling!My name is Fred."
}
将会创建一个结构类型的对象,该类型记为Person{def greeting:String},可以用这个类型作为参数类型的定义:
def meet(p:Person{def greeting:String}){
println(p.name + "says:" + p.greeting)
}
8.抽象类
和java一样,可以用abstract关键字来标记不能被实例化的类,通常是因为它的某个或某几个方法没有被完整定义。在Scala中,不想Java,不需要对抽象方法使用abstract关键字,只是省去其方法体,但和Java一样,如果某个类至少存在一个抽象方法,则该类必须声明为abstract。在子类中重写超类的抽象方法时,不需要使用override关键字。
9. 抽象字段
抽象字段就是一个没有初始值得字段。
abstract class Person{
val id: Int//带有抽象的getter方法的抽象字段
var name:String//带有抽象的getter和setter方法
}
具体的子类必须提供具体的字段,和方法一样,在子类中重写超类的抽象字段时,不需要override关键字。
10.对象相等性
在scala中,AnyRef的eq方法检查两个引用是否指向同一个对象,AnyRef的equals方法调用eq,当你实现类的时候,应该考虑重写equals方法。
如果定义class Item(val description: String, val price: Double), 你可能认为当两个物件有着相同描述和价格的时候是相等的。它的equals方法定义:
final override def equals(other :Any)={
val that = other.asInstanceOf[Item]
if (that == null) false
else description == that.description && price == that.price
}
将方法定义为final,是因为通常在子类中正确扩展相等性判断非常困难,问题出在对称性上,a.equals(b)和b.equals(a)的结果相同,尽管b属于a的子类。需要确保equals方法的参数类型为Any。
定义了equals是,还需要定义hashCode,在计算哈希码时,只应使用那些用来做相等性判断的字段。。
final override def hashcode = 13 * description.hahscode + 17 * price.hashCode
练习:参考网址
1.扩展如下的BankAccount类,新类CheckingAccount对每次存款和取款都收取1美元的手续费。
class BankAccount(initialBalance: Double){
private var balance = initialBalance
def deposit(amount: Double) = { balance += amount;balance}
def withdraw(amount: Double) = { balance –= amount; balance}
}
class CheckingAccount(initialBalance:Double) extends BankAccount(initialBalance){
override def deposit(amount:Double): Double = super.deposit(amount - 1)
override def withdraw(amount:Double): Double = super.withdraw(amount + 1)
}
2.扩展前一个练习的BankAccount类,新类SavingsAccount每个月都有利息产生(earnMonthlyInterest方法被调用),并且有每月三次免手续费的存款或取款。在earnMonthlyInterest方法中重置交易计数。
class SavingsAccount(initialBalance:Double) extends BankAccount(initialBalance){
private var num:Int = _
def earnMonthlyInterest()={
num = 3
super.deposit(1)
} override def deposit(amount:Double): Double = {
num -= 1
if(num<0) super.deposit(amount-1) else super.deposit(amount)
}
override def withdraw(amount:Double): Double = {
num -= 1
if(num<0) super.withdraw(amount + 1) else super.deposit(amount)
}
}
3.翻开你喜欢的Java或C++书籍,一定会找到用来讲解继承层级的示例,可能是员工、宠物、图形等,用Scala来实现这个示例。
//java
class Art{
Art(){System.out.println("Art constractor");}
} class Drawing extends Art{
Drawing(){System.out.println("Drawing constructor");}
}
public class Carton extends Drawing{
Carton(){Ststem.out.println("Carton costructor");}
}
//scala
class Art{
println("Art constructor")
}
class Drawing extends Art{
println("Drawing constructor")
}
class Carton extends Drawing{
println("Carton constructor")
}
4.定义一个抽象类Item,加入方法price和description,SimpleItem是一个在构造器中给出价格和描述的物件。利用val可以重写def这个事实。Bundle是一个可以包含其他物件的物件。其价格是打包中所有物件的价格之和。同时提供一个将物件添加到打包当中的机制,以及一个合适的description方法。
import collection.mutable.ArrayBuffer abstract class Item{
def price():Double
def description():String
override def toString():String = {
"description: " + description() + "price: " + price()
}
} class SimpleItem(val price:Double,val description:String) extends Item{ } class Bundle extends Item{
val items = new ArrayBuffer[Item]()
def addItem(item:Item){
items += item
}
def price():Double = {
var total = 0d
items.foreach(total += _.price())
total
} def description():String = {
items.mkString(" ")
}
}
5.设计一个Point类,其x和y坐标可以通过构造器提供,提供一个子类LabeledPoint,其构造器接受一个标签值和x\y坐标,比如:
new LabeledPoint(“Black Thurstry”,1929,230.07)
class Point(x:Int,y:Int){ }
class LabeledPoint(label:String,x:Int,y:Int) extends Point(x,y){ }
6.定义一个抽象类Shape,一个抽象方法centerPoint,以及该抽象类的子类Rentangle和Circle。为子类提供合适的构造器,并重写centerPoint方法。
abstract class Shape{
def centerPoint()
} class Rectangle(startX:Int,startY:Int,endX:Int,endY:Int) extends Shape{
def centerPoint(){}
} class Circle(x:Int,y:Int,radius:Double) extends Shape{
def centerPoint(){}
}
7.提供一个Square类,扩展自java.awt.Rectangle并且有三个构造器:一个以给定的端点和宽度构造正方形,一个以(0,0)为端点构造和给定的宽度构造正方形,一个以(0,0)为端点、0为宽度构造正方形。
import java.awt.{Point,Rectangle} class Square(point:Point,width:Int) extends Rectangle(point.x,point.y,width,width) {
def this(){
this(new Point(0,0),0)
}
def this(width:Int){
this(new Point(0,0),width)
}
}
8.编译8.6节中的Person和SecretAgent类并使用javap分析类文件。总共有多少name的getter方法?它们分别取什么值?(提示:可以用 –c 和 -private选项)
2,Person中取得的是传入的name,而SecretAgent取得的是默认的“secret”
9.在8.10节的Creature类中,将val range 替换成一个def。如果你在Ant子类中也用def的话会有什么效果?如果子类中使用val又会有什么效果?为什么?
在Ant中使用def没有问题,但是如果使用val则无法通过编译,因为val只能重写不带参数的def,这里的def是带参数的。
10.文件scala/collection/immutable/Stack.scala包含如下定义:
class Stack[A] protected(protected val elems: List[A])
请解释protected关键字的含义。(提示:回顾我们在第5章关于私有构造器的讨论。)
此构造方法只能被其子类来调用,而不能被外界直接调用。