Scala 面向对象(中)
1、继承和多态
用法和Java基本一致。
基本语法
class 子类名 extends 父类名称 {}
案例演示
object Test07_Inherit { def main(args: Array[String]): Unit = { val student = new Student1("GG Bond",18) student.printInfo() } } //定义一个父类 class Person1(){ var name: String = _ var age: Int = _ println("1. 父类主构造器调用") def this(name: String,age: Int){ this() println("2.父类的辅助构造器被调用") this.name = name this.age = age } def printInfo(): Unit = { println(s"${name} ${age}") } } //继承父类可以指定调用父类的构造器(需要给继承的父类传入参数,使用父类的主构造器还是辅助构造器取决于传入的参数是什么) 也可以不调用(直接extends Person1) class Student1(name: String,age: Int) extends Person1(name,age){ var school: String = _ println("3. 子类的主构造器被调用") def this(name: String,age: Int,school: String){ this(name,age) //调用主构造方法 this.school = school println("4. 子类的辅助构造器被调用") } //重写父类的方法 override def printInfo(): Unit = { println(s"${name} ${age} ${school}") } }
2、多态
2.1、动态绑定
Java中,只有方法的动态绑定的,但在Scala中,方法和属性都是动态绑定的。
在面向对象编程中,使用父类的引用来引用子类的对象,从而实现对不同子类对象的统一操作。虽然使用父类引用创建子类对象可能无法直接调用子类特有的方法,但多态的真正价值在于增强程序的灵活性和可扩展性。
通过多态,您可以将一组具有相似行为的子类对象看作是它们共同的父类类型,这样可以更方便地管理和处理这些对象。例如,假设有一个动物类作为父类,有狗类和猫类作为子类,它们都有发出声音的行为。如果我们使用父类的引用来引用狗类和猫类的对象,我们可以通过调用父类的方法来让这些不同的子类对象发出声音,而无需关心具体是哪种类型的子类对象。
此外,多态也可以让代码更易于扩展和维护。当我们需要新增一个新的子类时,只需继承自父类并实现相应的方法,而不需要修改已有的代码。这样可以避免影响已有的代码和程序的稳定性。
多态案例
object Test { def main(args: Array[String]): Unit = { //多态 val dog:Animal = new Dog val cat:Animal = new Cat dog.say() //汪汪汪~ cat.say() //喵喵喵~ } } abstract class Animal{ //定义一个抽象方法 def say():Unit } class Dog extends Animal { def say(): Unit = println("汪汪汪~") } class Cat extends Animal{ def say(): Unit = println("喵喵喵~") }
Java:
public class Test { public static void main(String[] args) { Worker worker = new Worker(); System.out.println(worker.name); worker.hello(); System.out.println("============"); //多态 Person person = new Worker(); System.out.println(person.name); person.hello(); } } class Person{ String name = "person"; public void hello(){ System.out.println("hello person"); } } class Worker extends Person{ String name = "worker"; public void hello(){ System.out.println("hello worker"); } }
输出结果
worker hello worker ============ person hello worker
Scala:
Scala在重写父类的属性和方法时,需要再属性名和方法名之前使用 “override” 关键字。
package chapter05 object Test08_DynamicBind { def main(args: Array[String]): Unit = { val student:Student8 = new Student8 println(student.name) student.hello() println("=============") //多态 val person:Person8 = new Student8 println(person.name) person.hello() } } class Person8{ val name = "person" def hello(): Unit = { println("hello person") } } class Student8 extends Person8 { override val name = "student" override def hello(): Unit = { println("hello student") } }
运行结果
student hello student ============= student hello student
可以看到,Scala通过父类创建子类对象后,在调用对象的方法时,会动态绑定当前的对象的属性和方法。
3、抽象类
3.1、抽象属性和方法
Scala中定义变量和方法时一般必须要求赋初始值,但是在抽象类中可以不遵守。
- 在Scala中,只要一个类中有抽象方法和抽象属性,该类必须是抽象类。
- 在Scala中,一个抽象类的属性和方法可以是已实现的。
- 定义抽象类:abstract class Person{} //通过 abstract 关键字标记抽象类
- 定义抽象属性:val|var name:String //一个属性没有初始化,就是抽象属性
- 定义抽象方法:def hello():String //只声明而没有实现的方法,就是抽象方法
3.2、抽象类的继承与重写
- 如果父类为抽象类,那么子类需要将抽象的属性和方法实现,否则子类也需声明为抽象类
- 重写非抽象方法需要用 override 修饰,重写抽象方法则可以不加 override。
- 子类中调用父类的方法使用 super 关键字
- 子类对抽象属性进行实现,父类抽象属性可以用 var/val 修饰;
- 子类可以直接对一个被 var 修饰的变量进行赋值来实现重写,不需要使用 override 关键字,所以对于非抽象属性,不建议用 var 修饰。
abstract class Person9{ //抽象属性 val name: String //非抽象属性 val age: Int = 18 //被val 修饰 子类需要重写 var school = "山西农业大学" //被var修饰 子类不需要重写 //抽象方法 def hello():Unit //非抽象方法 def eat(): Unit = { println("person eat") } } class Student9 extends Person9{ //实现抽象属性 val name: String = "李元芳" //实现抽象方法 def hello(): Unit = { println("hello") } //重写非抽象属性 override val age = 10 school = "北京中医药大学" //父类中被 var 修饰的非抽象变量可以直接重新赋值 不需要重写 所以不建议非抽象属性用 var 修饰 //重写非抽象方法 override def eat(): Unit = { super.eat() println("student eat") } }
4、匿名子类
匿名子类,也就是说不需要继承抽象父类,直接在定义对象的时候重写抽象属性和方法。
object Test { def main(args: Array[String]): Unit = { val person:Person10 = new Person10 { override var name: String = "GG Bond" override def hello(): Unit = { println("hello gays") } println(person.name) person.hello() } } } //定义抽象类 abstract class Person10{ //定义抽象属性和方法 var name: String def hello(): Unit }
5、单例对象(伴生对象)
Scala语言是 完全面向对象 的语言,所以并没有静态的操作(即在 Scala 中没有静态的概念)。但是为了能够和Java 语言交互(因为 Java 中有静态概念),就产生了一种特殊的对象来 模拟类对象, 该对象为 单例对象 。若单例对象名与类名一致,则称该单例对象这个类的伴生对象,这个类的所有“静态”内容都可以放置在它的伴生对象中声明。
说明
- 单例对象采用 object 关键字声明
- 单例对象对应的类称之为伴生类,伴生对象的名称应该和伴生类名一致。
- 单例对象中的属性和方法都可以通过伴生对象名(类名)直接调用访问。
- 伴生类和伴生对象必须名字相同且在一个文件中。
object Test { def main(args: Array[String]): Unit = { val bob = new Student11("bob", 18) bob.printInfo() } } //定义伴生类 class Student11(val name: String,val age: Int){ def printInfo(): Unit = { println(s"${name} ${age} ${Student11.school}") } } //定义伴生对象 object Student11{ val school: String = "北京中医药大学" }
5.1、主构造器私有化
有些时候我们不希望直接通过主构造器来创建对象实例,而是通过工厂方法来创建对象,在Scala中,我们可以借助伴生对象和伴生类可互相调用的特点来实现:
我们需要在伴生类的主构造器参数前使用 private 关键字进行修饰:
object Test { def main(args: Array[String]): Unit = { // val bob = new Student11("bob", 18) // bob.printInfo() val bob = Student11.newStudent("bob", 18) bob.printInfo() } } //定义伴生类 class Student11 private (val name: String,val age: Int){ def printInfo(): Unit = { println(s"${name} ${age} ${Student11.school}") } } //定义伴生对象 object Student11{ val school: String = "北京中医药大学" //定义一个类的对象实例的创建方法(工厂方法) def newStudent(name: String,age: Int): Student11 = { new Student11(name,age) } }
5.2、apply方法
使用 apply方法来实现工厂模式,可直接使用类名来创建对象,不需要显式地使用apply方法。
object Test { def main(args: Array[String]): Unit = { val alice = Student11.apply("alice",10) val tom = Student11("alice",10) //scala 调用apply方法可以省去方法名 简化代码 alice.printInfo() tom.printInfo() } } //定义伴生类 class Student11 private (val name: String,val age: Int){ def printInfo(): Unit = { println(s"${name} ${age} ${Student11.school}") } } //定义伴生对象 object Student11{ val school: String = "北京中医药大学" def apply(name: String,age: Int): Student11 = { new Student11(name,age) } }
5.3、单例设计模式
单例设计模式,始终只有一个对象,即使我们在创建对象的时候,赋予不同的属性,但是返回的结果都只会是第一个对象的实例。
object Test12_Singleton { def main(args: Array[String]): Unit = { val student2 = Student12.getInstance("bob",20) student2.printInfo() //bob 20 北京中医药大学 val student1 = Student12.getInstance("alice",18) student1.printInfo() //bob 20 北京中医药大学 //输出相同的对象引用 println(student1) println(student2) } } //定义伴生类 class Student12 private (val name: String,val age: Int){ def printInfo(): Unit = { println(s"${name} ${age} ${Student12.school}") } } //饿汉式 //object Student12{ // val school = "北京中医药大学" // // private val student: Student12 = new Student12("alice",18) // // def getInstance(): Student12 = student //} //懒汉式 object Student12{ val school = "北京中医药大学" private var student: Student12 = _ def getInstance(name: String,age: Int): Student12 = { if (student == null){ //没有对象实例的话就创建一个 student = new Student12(name,age) } student } }
使用伴生对象可以很轻松地实现各种设计模式。