> 文章列表 > Scala继承和抽象,trait,样例类

Scala继承和抽象,trait,样例类

Scala继承和抽象,trait,样例类

继承:

实际开发中, 我们发现好多类中的内容是相似的(例如: 相似的属性和行为), 每次写很麻烦. 于是我们可以把这些相似的内容提取出来单独的放到一个类中(父类), 然后让那多个类(子类)和这个类(父类)产生一个关系, 从而实现子类可以访问父类的内容, 这个关系就叫: 继承.因为scala语言是支持面向对象编程的,我们也可以使用scala来实现继承,通过继承来减少重复代码。

格式 

class/object A类 extends B类 {	..
}

叫法

  • 上述格式中, A类称之为: 子类, 派生类.

  • B类称之为: 父类, 超类, 基类.

代码示例:

使用非继承版:

package test6object Test1 {class Teacher {var name = ""var age = 0def eat() = println("老师在吃饭")}class Student {var name = ""var age = 0def eat() = println("学生在吃饭")}def main(args: Array[String]): Unit = {val teacher = new Teacherteacher.name = "王老师"teacher.age = 23println(teacher.name, teacher.age)teacher.eat()println("-" * 15)val student = new Studentstudent.name = "张三"student.age = 21println(student.name, student.age)student.eat()}}

 使用继承版:(可以使用父类方法,也可以重写父类方法)

package test6object Test2 {class Person {var name = ""var age = 0def eat() = println("人要吃饭")}class Teacher extends Person {super.eat()override def eat(): Unit = println("老师吃饭")}class Student extends Person {super.eat()override def eat(): Unit = println("学生吃饭")}def main(args: Array[String]): Unit = {val teacher = new Teacherteacher.name = "刘老师"teacher.age = 32println(teacher.name, teacher.age)teacher.eat()println("-" * 15)val student = new Studentstudent.name = "张三"student.age = 23println(student.name, student.age)student.eat()}
}

单例对象继承

在Scala中, 单例对象也是可以继承类的.

package test6object Test3 {class Person {var name = ""def sayHello() = println("hello")}object Student extends Persondef main(args: Array[String]): Unit = {Student.name = "张三"println(Student.name)Student.sayHello()}
}

方法重写

子类中出现和父类一模一样的方法时, 称为方法重写. Scala代码中可以在子类中使用override来重写父类的成员,也可以使用super来引用父类的成员.

  • 子类要重写父类中的某一个方法,该方法必须要使用override关键字来修饰

  • 可以使用override来重写一个val字段.

  • 使用super关键字来访问父类的成员方法

  • 父类用var修饰的变量, 子类不能重写.

代码示例:

  • 定义Person类, 属性(姓名, 年龄), 有一个sayHello()方法.
  • 然后定义Student类继承Person类, 重写Person类中的字段和方法, 并测试.
package test6object Test4 {class Person {var name = "张三"val age = 23def sayHello() = println("hello")}class Student extends Person {//    override var name = "李四"  //这样写会报错, 子类不能重写父类用var修饰的变量.override val age = 56override def sayHello(): Unit = {super.sayHello()println("hello student")}}def main(args: Array[String]): Unit = {val student = new Studentprintln(student.name, student.age)student.sayHello()}
}

类型推断

在scala中,如何来进行类型判断呢?

有两种方式:

  • isInstanceOf:判断对象是否为指定类的对象

  • getClass/classOf: 将对象转换为指定类型

1.isInstanceOf,asInstanceOf 

格式:

// 判断对象是否为指定类型
val trueOrFalse:Boolean = 对象.isInstanceOf[类型]// 将对象转换为指定类型
val 变量 = 对象.asInstanceOf[类型]

 代码示例:

package test6object Test5 {class Personclass Student extends Person {def sayHello() = println("hello scala..")}def main(args: Array[String]): Unit = {val p: Person = new Student//    p.sayHello() 这么写会报错,父类引用不能直接访问子类特有成员//    判断其是否是Student,Person类型的对象 都是trueprintln(p.isInstanceOf[Person])println(p.isInstanceOf[Student])val p1 = p.asInstanceOf[Student] //将Person类型对象转换为Student类型对象p1.sayHello() //现在可以使用sayHello这个方法了}}

2.getClass和classOf

isInstanceOf 只能判断对象是否为指定类以及其子类的对象,而不能精确的判断出: 对象就是指定类的对象。如果要求精确地判断出对象的类型就是指定的数据类型,那么就只能使用 getClass 和 classOf 来实现.

代码示例:

package test6object Test6 {class Personclass Student extends Persondef main(args: Array[String]): Unit = {val p: Person = new Studentprintln(p.isInstanceOf[Person]) //trueprintln(p.isInstanceOf[Student]) //trueprintln(p.getClass == classOf[Person]) //falseprintln(p.getClass == classOf[Student]) //true}
}

抽象类

格式:

// 定义抽象类
abstract class 抽象类名 {// 定义抽象字段val/var 抽象字段名:类型// 定义抽象方法def 方法名(参数:参数类型,参数:参数类型...):返回类型
}

代码示例:

package test6import com.sun.xml.internal.ws.message.saaj.SAAJHeaderobject Test7 {abstract class Shape {def area: Double}class Square(var edge: Double) extends Shape {override def area: Double = edge * edge}class Reactangle(var length: Double, var width: Double) extends Shape {override def area: Double = length * width}class Circle(var radius: Double) extends Shape {override def area: Double = Math.PI * radius * radius}def main(args: Array[String]): Unit = {val s1: Shape = new Square(2)val s2: Shape = new Reactangle(2, 3)val s3: Shape = new Circle(2)println(s1.area)println(s2.area)println(s3.area)}
}

抽象字段

格式:

abstract class 抽象类 {val/var 抽象字段:类型
}

代码示例:

package test6object Test8 {abstract class Person {val occupation: String}class Student extends Person {override val occupation: String = "学生"}class Teacher extends Person {override val occupation: String = "老师"}def main(args: Array[String]): Unit = {val student = new Studentprintln(student.occupation)val teacher = new Teacherprintln(teacher.occupation)}
}

匿名内部类

匿名内部类是继承了类的匿名的子类对象,它可以直接用来创建实例对象。Spark的源代码中大量使用到匿名内部类。学完这个内容, 对我们查看Spark的底层源码非常有帮助.

格式:

new 类名() {//重写类中所有的抽象内容
}

注意: 上述格式中, 如果的类的主构造器参数列表为空, 则小括号可以省略不写.

使用场景

  • 当对对象方法(成员方法)仅调用一次的时候.

  • 可以作为方法的参数进行传递.

代码示例: 

  1. 创建一个Person抽象类,并添加一个sayHello抽象方法

  2. 定义一个show()方法, 该方法需要传入一个Person类型的对象, 然后调用Person类中的sayHello()方法.

  3. 添加main方法,通过匿名内部类的方式来创建Person类的子类对象, 调用Person类的sayHello()方法.

  4. 调用show()方法.

 

package test6object Test9 {abstract class Person {def sayhello()}def show(p: Person) = p.sayhello()def main(args: Array[String]): Unit = {new Person {override def sayhello(): Unit = println("hello scala,当对成员方法仅调用一次的时候")}.sayhello()val p = new Person {override def sayhello(): Unit = println("hello scala,可以作为方法的实际参数进行传递")}show(p)}
}

代码示例:

已知有猫类和狗类, 它们都有姓名和年龄, 都会跑步, 而且仅仅是跑步, 没有什么不同. 它们都有吃饭的功能, 不同的是猫吃鱼, 狗吃肉. 而且猫类独有自己的抓老鼠功能, 狗类独有自己的看家功能, 请用所学模拟该需求.

  1. 定义抽象动物类(Animal), 属性: 姓名, 年龄, 行为: 跑步, 吃饭.

  2. 定义猫类(Cat)继承自动物类, 重写吃饭的方法, 并定义该类独有的抓老鼠的方法.

  3. 定义狗类(Dog)继承自动物类, 重写吃饭的方法, 并定义该类独有的看家的方法.

package test6object Test10 {abstract class Animal {var name = ""var age = 0def run() = println("动物会跑步!")def eat() = println("动物吃饭!")}class Cat extends Animal {override def eat(): Unit = println("猫吃鱼!")def catchMouse() = println("猫抓老鼠!")}class Dog extends Animal {override def eat(): Unit = println("狗吃肉!")def lookHome() = println("狗看家!")}def main(args: Array[String]): Unit = {val cat = new Catcat.name = "汤姆"cat.age = 23println(cat.name, cat.age)cat.eat()if (cat.isInstanceOf[Cat]) {val c = cat.asInstanceOf[Cat]c.catchMouse()} else {println("您传入的不是猫类!")}val dog = new Dogdog.name = "大黄"dog.age = 21println(dog.name, dog.age)dog.eat()if (dog.isInstanceOf[Dog]) {val d = dog.asInstanceOf[Dog]d.lookHome()} else {println("您传入的不是狗类!")}}
}

trait

作用:

​​​​​​​有些时候, 我们会遇到一些特定的需求, 即: 在不影响当前继承体系的情况下, 对某些类(或者某些对象)的功能进行加强, 例如: 有猴子类和大象类, 它们都有姓名, 年龄, 以及吃的功能, 但是部分的猴子经过马戏团的训练后, 学会了骑独轮车. 那骑独轮车这个功能就不能定义到父类(动物类)或者猴子类中, 而是应该定义到特质中. 而Scala中的特质, 要用关键字trait修饰.

特点:

  • 特质可以提高代码的复用性.

  • 特质可以提高代码的扩展性和可维护性.

  • 类与特质之间是继承关系, 只不过类与类之间只支持单继承, 但是类与特质之间, 既可以单继承, 也可以多继承.

  • Scala的特质中可以有普通字段, 抽象字段, 普通方法, 抽象方法.

注意:

  1. 如果特质中只有抽象内容, 这样的特质叫: 瘦接口.

  2. 如果特质中既有抽象内容, 又有具体内容, 这样的特质叫: 富接口.

格式: 

trait 特质名称 {// 普通字段// 抽象字段// 普通方法// 抽象方法
}

 继承特质 :

class 类 extends 特质1 with 特质2 {// 重写抽象字段// 重写抽象方法
}

 

注意

  • scala中不管是类还是特质, 继承关系用的都是extends关键字

  • 如果要继承多个特质(trait),则特质名之间使用with关键字隔开

代码示例: (类继承单个特质

  1. 创建一个Logger特质,添加log(msg:String)方法

  2. 创建一个ConsoleLogger类,继承Logger特质,实现log方法,打印消息

  3. 添加main方法,创建ConsoleLogger对象,调用log方法.

package test7object Test1 {trait Logger {def log(msg: String)}class ConsoleLogger extends Logger {override def log(msg: String): Unit = println(msg)}def main(args: Array[String]): Unit = {val consoleLogger = new ConsoleLoggerconsoleLogger.log("trait入门:类继承单个特质")}
}

 

代码示例:(类继承多个trait

  1. 创建一个MessageSender特质,添加send(msg:String)方法

  2. 创建一个MessageReceiver特质,添加receive()方法

  3. 创建一个MessageWorker类, 继承这两个特质, 重写上述的两个方法

  4. 在main中测试,分别调用send方法、receive方法

package test7object Test2 {trait MessageSender {def send(msg: String)}trait MessageReceiver {def receive()}class MessageWorker extends MessageSender with MessageReceiver {override def send(msg: String): Unit = println("发送消息:" + msg)override def receive(): Unit = println("消息已经收到!")}def main(args: Array[String]): Unit = {val messageWorker = new MessageWorkermessageWorker.send("hello,你好!")messageWorker.receive()}
}

 

代码示例:(object继承trait

  1. 创建一个Logger特质,添加log(msg:String)方法

  2. 创建一个Warning特质, 添加warn(msg:String)方法

  3. 创建一个单例对象ConsoleLogger,继承Logger和Warning特质, 重写特质中的抽象方法

  4. 编写main方法,调用单例对象ConsoleLogger的log和warn方法

package test7object Test3 {trait Logger {def log(msg: String)}trait Warning {def warn(msg: String)}object ConsoleLogger extends Logger with Warning {override def log(msg: String): Unit = println("控制台日志信息:" + msg)override def warn(msg: String): Unit = println("控制台警告信息:" + msg)}def main(args: Array[String]): Unit = {ConsoleLogger.log("我是一条普通日志信息!")ConsoleLogger.warn("我是一条警告日志信息!")}
}

代码示例:(演示trait中的成员)

  1. 定义一个特质Hero, 添加具体字段name(姓名), 抽象字段arms(武器), 具体方法eat(), 抽象方法toWar()

  2. 定义一个类Generals, 继承Hero特质, 重写其中所有的抽象成员.

  3. 在main方法中, 创建Generals类的对象, 调用其中的成员.

package test7object Test4 {trait Hero {//具体字段var name = ""//抽象字段var arms: String//具体方法def eat() = println("喝酒吃肉,养精蓄锐!")//抽象方法def toWar()}class Generals extends Hero {override var arms: String = ""override def toWar(): Unit = println(s"${name}带着${arms},上阵杀敌!")}def main(args: Array[String]): Unit = {val generals = new Generalsgenerals.name = "关羽"generals.arms = "青龙偃月刀"println(generals.name, generals.arms)generals.eat()generals.toWar()}
}

样例类

在Scala中, 样例类是一种特殊类,一般是用于保存数据的(类似于Java POJO类), 在并发编程以及Spark、Flink这些框架中都会经常使用它。

格式:

case class 样例类名([var/val] 成员变量名1:类型1, 成员变量名2:类型2, 成员变量名3:类型3){}
  • 如果不写, 则变量的默认修饰符是val, 即: val是可以省略不写的.

  • 如果要实现某个成员变量值可以被修改,则需手动添加var来修饰此变量.

代码示例:

package test7object Test5 {case class Person(var name: String = "张三", var age: Int = 23) {}def main(args: Array[String]): Unit = {var person = new Person()println(person)person.age = 56println(person)}
}

样例类中的默认方法

  • apply()方法

  • toString()方法

  • equals()方法

  • hashCode()方法

  • copy()方法

  • unapply()方法

功能详解:

  • apply()方法

    • 可以让我们快速地使用类名来创建对象, 省去了new这个关键字

    • 例如: val p = Person()

  • toString()方法

    • 可以让我们通过输出语句打印对象时, 直接打印该对象的各个属性值.

    • 例如: println(p) 打印的是对象p的各个属性值, 而不是它的地址值

  • equals()方法

    • 可以让我们直接使用==来比较两个样例类对象的所有成员变量值是否相等.

    • 例如: p1 == p2 比较的是两个对象的各个属性值是否相等, 而不是比较地址值

  • hashCode()方法​​​​​​​
    • 用来获取对象的哈希值的. 即: 同一对象哈希值肯定相同, 不同对象哈希值一般不同.
    • 例如:

         

 

copy()方法

  • 可以用来快速创建一个属性值相同的实例对象,还可以使用带名参数的形式给指定的成员变量赋值.

  • 例如:

        

 

unapply()方法

  • 一般用作提取器

 代码示例:

package test7object Test6 {case class Person(var name: String, var age: Int) {}def main(args: Array[String]): Unit = {val person1 = Person("张三", 23)println(person1)val person2 = Person("张三", age = 23)println(person2)println(person1 == person2)println(person1.hashCode())println(person2.hashCode())val person3 = person2.copy(age = 50)println(person3)}
}

样例对象:

​​​​​​​在Scala中, 用case修饰的单例对象就叫: 样例对象, 而且它没有主构造器 , 它主要用在两个地方:

  1. 当做枚举值使用.

    枚举: 就是一些固定值, 用来统一项目规范的.

  2. 作为没有任何参数的消息传递

    注意: 这点目前先了解即可, 后续讲解Akka并发编程时会详细讲解.

case object 样例对象名

 代码示例:

  • 定义特质Sex, 表示性别, 且它只有两个实例(Male: 表示男, Female: 表示女)

  • 定义Person类,它有两个成员变量(姓名、性别)

  • 在测试类中创建Person类的对象, 并测试.

package test7object Test7 {trait Sexcase object Male extends Sexcase object Female extends Sexcase class Person(name: String, sex: Sex) {}def main(args: Array[String]): Unit = {val person = Person("张三", Male)var person1 = Person("李四", Female)println(person)println(person1)}
}

 代码示例:(计算器)​​​​​​​

  • 定义样例类Calculate, 并在其中添加4个方法, 分别用来计算两个整数的加减乘除操作.

  • 在main方法中进行测试.

package test7object Test8 {case class Calculate(a: Int, b: Int) {def add() = a + bdef sub() = a - bdef mul() = a * bdef div() = a / b}def main(args: Array[String]): Unit = {val calculate = Calculate(10, 3)println("加法" + calculate.add())println("减法" + calculate.sub())println("乘法" + calculate.mul())println("除法" + calculate.div())}
}