3 继承(extends)
3.1 简单继承
scala和Java一样,使用extends关键字来实现继承。可以在子类中定义父类中没有的字段和方法,或者重写父类的方法。
示例1:实现简单继承
class Person { var name = "super" def getName = this.name } class Student extends Person object Main13 { def main(args: Array[String]): Unit = { val p1 = new Person() val p2 = new Student() p2.name = "张三" println(p2.getName) } }
示例2:单例对象实现继承
class Person { var name = "super" def getName = this.name } object Student extends Person object Main13 { def main(args: Array[String]): Unit = { println(Student.getName) } }
3.2 override和super
- 如果子类要覆盖父类中的一个非抽象方法,必须要使用override关键字
- 可以使用override关键字来重写一个val字段
- 可以使用super关键字来访问父类的成员
示例1:class继承class
class Person { val name = "super" def getName = name } class Student extends Person { // 重写val字段 override val name: String = "child" // 重写getName方法 override def getName: String = "hello, " + super.getName } object Main13 { def main(args: Array[String]): Unit = { println(new Student().getName) } }
3.3 isInstanceOf和asInstanceOf
我们经常要在代码中进行类型的判断和类型的转换。在Java中,我们可以使用instanceof关键字、以及(类型)object来实现,在scala中如何实现呢?
scala中对象提供isInstanceOf和asInstanceOf方法。
- isInstanceOf判断对象是否为指定类的对象
- asInstanceOf将对象转换为指定类型
class Person3 class Student3 extends Person3 object Main3 { def main(args: Array[String]): Unit = { val s1:Person3 = new Student3 // 判断s1是否为Student3类型 if(s1.isInstanceOf[Student3]) { // 将s1转换为Student3类型 val s2 = s1.asInstanceOf[Student3] println(s2) } } }
3.4 getClass和classOf
isInstanceOf 只能判断出对象是否为指定类以及其子类的对象,而不能精确的判断出,对象就是指定类的对象。如果要求精确地判断出对象就是指定类的对象,那么就只能使用 getClass 和 classOf 。
- p.getClass可以精确获取对象的类型
- classOf[x]可以精确获取类型
- 使用==操作符就可以直接比较
示例:
class Person4 class Student4 extends Person4 object Student4{ def main(args: Array[String]) { val p:Person4=new Student4 //判断p是否为Person4类的实例 println(p.isInstanceOf[Person4])//true //判断p的类型是否为Person4类 println(p.getClass == classOf[Person4])//false //判断p的类型是否为Student4类 println(p.getClass == classOf[Student4])//true } }
3.5 访问修饰符
Java中的访问控制,同样适用于scala,可以在成员前面添加private/protected关键字来控制成员的可见性。但在scala中,没有public关键字
,任何没有被标为private或protected的成员都是公共的。
private[this]修饰符
被修饰的成员只能在当前类中被访问。或者可以理解为:只能通过this.来访问
(在当前类中访问成员会自动添加this.)。
示例:
- 创建一个Person类
- 添加一个
private[this]
修饰符的name字段,并赋值为"super"
- 添加一个getName方法,获取name字段
- 添加一个sayHelloTo方法,接收一个Person类型参数,尝试打印该参数的name字段
- 创建一个Person的伴生对象
- 添加一个showName方法,接收一个Person类型参数,尝试打印该参数的name字段
代码:
class Person { // 只有在当前对象中能够访问 private[this] var name = "super" def getName = this.name // 正确! def sayHelloTo(p:Person) = { println("hello" + p.name) // 报错!无法访问 } } object Person { def showName(p:Person) = println(p.name) // 报错!无法访问 }
protected[this]修饰符
被修饰的成员只能在当前类和当前子类中被访问。也可以理解为:当前类通过**this.访问或者子类通过this.**访问
示例:
- 将Person类的name字段访问修饰符改为
protected[this]
- 创建一个Student类
- 添加一个showName方法,在方法中访问name字段
- 添加一个sayHelloTo2方法,接收一个Person类型参数,在方法中打印该参数的name字段
class Person { // 只有在当前对象以及继承该类的当前对象中能够访问 protected[this] var name = "super" def getName = { // 正确! this.name } def sayHelloTo1(p:Person) = { // 编译错误!无法访问 println(p.name) } } object Person { def sayHelloTo3(p:Person) = { // 编译错误!无法访问 println(p.name) } } class Student extends Person { def showName = { // 正确! println(name) } def sayHelloTo2(p:Person) = { // 编译错误!无法访问 println(p.name) } }
3.6 调用父类的constructor
实例化子类对象,必须要调用父类的构造器,在scala中,只能在子类的主构造器
中调用父类的构造器
步骤:
- 创建一个Person类,编写带有一个可变的name字段的主构造器
- 创建一个Student类,继承自Person类
- 编写带有一个name参数、clazz班级字段的主构造器
- 调用父类的构造器
- 创建main方法,创建Student对象实例,并打印它的姓名、班级
代码:
class Person5(var name:String) // 直接在父类的类名后面调用父类构造器 class Student5(name:String, var clazz:String) extends Person5(name) object Main5 { def main(args: Array[String]): Unit = { val s1 = new Student5("张三", "三年二班") println(s"${s1.name} - ${s1.clazz}") } }
3.7 抽象类
如果类的某个成员在当前类中的定义是不包含完整的,它就是一个抽象类
不完整定义有两种情况:
- 方法没有方法体
- 变量没有初始化
没有方法体的方法称为抽象方法,没有初始化的变量称为抽象字段。定义抽象类和Java一样,在类前面加上abstract关键字就可以了
3.7.1 抽象方法
示例1:
设计4个类,表示上述图中的继承关系,每一个形状都有自己求面积的方法,但是不同的形状计算面积的方法不同。
步骤:
- 创建一个Shape抽象类,添加一个area抽象方法,用于计算面积
- 创建一个Square正方形类,继承自Shape,它有一个边长的主构造器,并实现计算面积方法
- 创建一个长方形类,继承自Shape,它有一个长、宽的主构造器,实现计算面积方法
- 创建一个圆形类,继承自Shape,它有一个半径的主构造器,并实现计算面积方法
- 编写main方法,分别创建正方形、长方形、圆形对象,并打印它们的面积
代码:
// 创建形状抽象类 abstract class Shape { def area:Double } // 创建正方形类 class Square(var edge:Double /*边长*/) extends Shape { // 实现父类计算面积的方法 override def area: Double = edge * edge } // 创建长方形类 class Rectangle(var length:Double /*长*/, var width:Double /*宽*/) extends Shape { override def area: Double = length * width } // 创建圆形类 class Cirle(var radius:Double /*半径*/) extends Shape { override def area: Double = Math.PI * radius * radius } object Main6 { def main(args: Array[String]): Unit = { val s1:Shape = new Square(2) val s2:Shape = new Rectangle(2,3) val s3:Shape = new Cirle(2) println(s1.area) println(s2.area) println(s3.area) } }
3.7.2 抽象字段
示例2:
步骤:
- 创建一个Person抽象类,它有一个String抽象字段WHO_AM_I
- 创建一个Student类,继承自Person类,重写WHO_AM_I字段,初始化为学生
- 创建一个Policeman类,继承自Person类,重写WHO_AM_I字段,初始化警察
- 添加main方法,分别创建Student/Policeman的实例,然后分别打印WHO_AM_I
代码
// 定义一个人的抽象类 abstract class Person6 { // 没有初始化的val字段就是抽象字段 val WHO_AM_I:String } class Student6 extends Person6 { override val WHO_AM_I: String = "学生" } class Policeman6 extends Person6 { override val WHO_AM_I: String = "警察" } object Main6 { def main(args: Array[String]): Unit = { val p1 = new Student6 val p2 = new Policeman6 println(p1.WHO_AM_I) println(p2.WHO_AM_I) } }
3.8 匿名内部类
匿名内部类是没有名称的子类,直接用来创建实例对象。Spark的源代码中有大量使用到匿名内部类。
示例:
- 创建一个Person10抽象类,并添加一个sayHello抽象方法
- 添加main方法,通过创建匿名内部类的方式来实现Person10
- 调用匿名内部类对象的sayHello方法
代码:
abstract class Person7 { def sayHello:Unit } object Main7 { def main(args: Array[String]): Unit = { // 直接用new来创建一个匿名内部类对象 val p1 = new Person7 { override def sayHello: Unit = println("我是一个匿名内部类") } p1.sayHello } }