继承:
实际开发中, 我们发现好多类中的内容是相似的(例如: 相似的属性和行为), 每次写很麻烦. 于是我们可以把这些相似的内容提取出来单独的放到一个类中(父类), 然后让那多个类(子类)和这个类(父类)产生一个关系, 从而实现子类可以访问父类的内容, 这个关系就叫: 继承.因为scala语言是支持面向对象编程的,我们也可以使用scala来实现继承,通过继承来减少重复代码。
格式
class/object A类 extends B类 {
..
}
叫法
-
上述格式中, A类称之为: 子类, 派生类.
-
B类称之为: 父类, 超类, 基类.
代码示例:
使用非继承版:
package test6
object Test1 {
class Teacher {
var name = ""
var age = 0
def eat() = println("老师在吃饭")
}
class Student {
var name = ""
var age = 0
def eat() = println("学生在吃饭")
}
def main(args: Array[String]): Unit = {
val teacher = new Teacher
teacher.name = "王老师"
teacher.age = 23
println(teacher.name, teacher.age)
teacher.eat()
println("-" * 15)
val student = new Student
student.name = "张三"
student.age = 21
println(student.name, student.age)
student.eat()
}
}
使用继承版:(可以使用父类方法,也可以重写父类方法)
package test6
object Test2 {
class Person {
var name = ""
var age = 0
def 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 Teacher
teacher.name = "刘老师"
teacher.age = 32
println(teacher.name, teacher.age)
teacher.eat()
println("-" * 15)
val student = new Student
student.name = "张三"
student.age = 23
println(student.name, student.age)
student.eat()
}
}
单例对象继承
在Scala中, 单例对象也是可以继承类的.
package test6
object Test3 {
class Person {
var name = ""
def sayHello() = println("hello")
}
object Student extends Person
def 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 test6
object Test4 {
class Person {
var name = "张三"
val age = 23
def sayHello() = println("hello")
}
class Student extends Person {
// override var name = "李四" //这样写会报错, 子类不能重写父类用var修饰的变量.
override val age = 56
override def sayHello(): Unit = {
super.sayHello()
println("hello student")
}
}
def main(args: Array[String]): Unit = {
val student = new Student
println(student.name, student.age)
student.sayHello()
}
}
类型推断
在scala中,如何来进行类型判断呢?
有两种方式:
-
isInstanceOf:判断对象是否为指定类的对象
-
getClass/classOf: 将对象转换为指定类型
1.isInstanceOf,asInstanceOf
格式:
// 判断对象是否为指定类型
val trueOrFalse:Boolean = 对象.isInstanceOf[类型]
// 将对象转换为指定类型
val 变量 = 对象.asInstanceOf[类型]
代码示例:
package test6
object Test5 {
class Person
class Student extends Person {
def sayHello() = println("hello scala..")
}
def main(args: Array[String]): Unit = {
val p: Person = new Student
// p.sayHello() 这么写会报错,父类引用不能直接访问子类特有成员
// 判断其是否是Student,Person类型的对象 都是true
println(p.isInstanceOf[Person])
println(p.isInstanceOf[Student])
val p1 = p.asInstanceOf[Student] //将Person类型对象转换为Student类型对象
p1.sayHello() //现在可以使用sayHello这个方法了
}
}
2.getClass和classOf
isInstanceOf 只能判断对象是否为指定类以及其子类的对象,而不能精确的判断出: 对象就是指定类的对象。如果要求精确地判断出对象的类型就是指定的数据类型,那么就只能使用 getClass 和 classOf 来实现.
代码示例:
package test6
object Test6 {
class Person
class Student extends Person
def main(args: Array[String]): Unit = {
val p: Person = new Student
println(p.isInstanceOf[Person]) //true
println(p.isInstanceOf[Student]) //true
println(p.getClass == classOf[Person]) //false
println(p.getClass == classOf[Student]) //true
}
}
抽象类
格式:
// 定义抽象类
abstract class 抽象类名 {
// 定义抽象字段
val/var 抽象字段名:类型
// 定义抽象方法
def 方法名(参数:参数类型,参数:参数类型...):返回类型
}
代码示例:
package test6
import com.sun.xml.internal.ws.message.saaj.SAAJHeader
object 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 test6
object 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 Student
println(student.occupation)
val teacher = new Teacher
println(teacher.occupation)
}
}
匿名内部类
匿名内部类是继承了类的匿名的子类对象,它可以直接用来创建实例对象。Spark的源代码中大量使用到匿名内部类。学完这个内容, 对我们查看Spark的底层源码非常有帮助.
格式:
new 类名() {
//重写类中所有的抽象内容
}
注意: 上述格式中, 如果的类的主构造器参数列表为空, 则小括号可以省略不写.
使用场景
-
当对对象方法(成员方法)仅调用一次的时候.
-
可以作为方法的参数进行传递.
代码示例:
-
创建一个Person抽象类,并添加一个sayHello抽象方法
-
定义一个show()方法, 该方法需要传入一个Person类型的对象, 然后调用Person类中的sayHello()方法.
-
添加main方法,通过匿名内部类的方式来创建Person类的子类对象, 调用Person类的sayHello()方法.
-
调用show()方法.
package test6
object 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)
}
}
代码示例:
已知有猫类和狗类, 它们都有姓名和年龄, 都会跑步, 而且仅仅是跑步, 没有什么不同. 它们都有吃饭的功能, 不同的是猫吃鱼, 狗吃肉. 而且猫类独有自己的抓老鼠功能, 狗类独有自己的看家功能, 请用所学模拟该需求.
-
定义抽象动物类(Animal), 属性: 姓名, 年龄, 行为: 跑步, 吃饭.
-
定义猫类(Cat)继承自动物类, 重写吃饭的方法, 并定义该类独有的抓老鼠的方法.
-
定义狗类(Dog)继承自动物类, 重写吃饭的方法, 并定义该类独有的看家的方法.
package test6
object Test10 {
abstract class Animal {
var name = ""
var age = 0
def 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 Cat
cat.name = "汤姆"
cat.age = 23
println(cat.name, cat.age)
cat.eat()
if (cat.isInstanceOf[Cat]) {
val c = cat.asInstanceOf[Cat]
c.catchMouse()
} else {
println("您传入的不是猫类!")
}
val dog = new Dog
dog.name = "大黄"
dog.age = 21
println(dog.name, dog.age)
dog.eat()
if (dog.isInstanceOf[Dog]) {
val d = dog.asInstanceOf[Dog]
d.lookHome()
} else {
println("您传入的不是狗类!")
}
}
}
trait
作用:
有些时候, 我们会遇到一些特定的需求, 即: 在不影响当前继承体系的情况下, 对某些类(或者某些对象)的功能进行加强, 例如: 有猴子类和大象类, 它们都有姓名, 年龄, 以及吃的功能, 但是部分的猴子经过马戏团的训练后, 学会了骑独轮车. 那骑独轮车这个功能就不能定义到父类(动物类)或者猴子类中, 而是应该定义到特质
中. 而Scala中的特质, 要用关键字trait
修饰.
特点:
-
特质可以提高代码的复用性.
-
特质可以提高代码的扩展性和可维护性.
-
类与特质之间是继承关系, 只不过类与类之间只支持
单继承
, 但是类与特质之间,既可以单继承, 也可以多继承
. -
Scala的特质中可以有普通字段, 抽象字段, 普通方法, 抽象方法.
注意:
-
如果特质中只有抽象内容, 这样的特质叫: 瘦接口.
-
如果特质中既有抽象内容, 又有具体内容, 这样的特质叫: 富接口.
格式:
trait 特质名称 {
// 普通字段
// 抽象字段
// 普通方法
// 抽象方法
}
继承特质 :
class 类 extends 特质1 with 特质2 {
// 重写抽象字段
// 重写抽象方法
}
注意
-
scala中不管是类还是特质, 继承关系用的都是
extends
关键字 -
如果要继承多个特质(trait),则特质名之间使用
with
关键字隔开
代码示例: (类继承单个特质 )
-
创建一个Logger特质,添加
log(msg:String)
方法 -
创建一个ConsoleLogger类,继承Logger特质,实现log方法,打印消息
-
添加main方法,创建ConsoleLogger对象,调用log方法.
package test7
object 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 ConsoleLogger
consoleLogger.log("trait入门:类继承单个特质")
}
}
代码示例:(类继承多个trait )
-
创建一个MessageSender特质,添加
send(msg:String)
方法 -
创建一个MessageReceiver特质,添加
receive()
方法 -
创建一个MessageWorker类, 继承这两个特质, 重写上述的两个方法
-
在main中测试,分别调用send方法、receive方法
package test7
object 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 MessageWorker
messageWorker.send("hello,你好!")
messageWorker.receive()
}
}
代码示例:(object继承trait )
-
创建一个Logger特质,添加
log(msg:String)
方法 -
创建一个Warning特质, 添加
warn(msg:String)
方法 -
创建一个单例对象ConsoleLogger,继承Logger和Warning特质, 重写特质中的抽象方法
-
编写main方法,调用单例对象ConsoleLogger的log和warn方法
package test7
object 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中的成员)
-
定义一个特质Hero, 添加具体字段name(姓名), 抽象字段arms(武器), 具体方法eat(), 抽象方法toWar()
-
定义一个类Generals, 继承Hero特质, 重写其中所有的抽象成员.
-
在main方法中, 创建Generals类的对象, 调用其中的成员.
package test7
object 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 Generals
generals.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 test7
object 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 = 56
println(person)
}
}
样例类中的默认方法
-
apply()方法
-
toString()方法
-
equals()方法
-
hashCode()方法
-
copy()方法
-
unapply()方法
功能详解:
-
apply()方法
-
可以让我们快速地使用类名来创建对象, 省去了new这个关键字
-
例如:
val p = Person()
-
-
toString()方法
-
可以让我们通过输出语句打印对象时, 直接打印该对象的各个属性值.
-
例如:
println(p) 打印的是对象p的各个属性值, 而不是它的地址值
-
-
equals()方法
-
可以让我们直接使用
==
来比较两个样例类对象的所有成员变量值是否相等. -
例如:
p1 == p2 比较的是两个对象的各个属性值是否相等, 而不是比较地址值
-
- hashCode()方法
- 用来获取对象的哈希值的. 即: 同一对象哈希值肯定相同, 不同对象哈希值一般不同.
- 例如:
copy()方法
-
可以用来快速创建一个属性值相同的实例对象,还可以使用带名参数的形式给指定的成员变量赋值.
-
例如:
unapply()方法
-
一般用作
提取器
代码示例:
package test7
object 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修饰的单例对象就叫: 样例对象, 而且它没有主构造器 , 它主要用在两个地方:
-
当做枚举值使用.
枚举: 就是一些固定值, 用来统一项目规范的.
-
作为没有任何参数的消息传递
注意: 这点目前先了解即可, 后续讲解Akka并发编程时会详细讲解.
case object 样例对象名
代码示例:
-
定义特质Sex, 表示性别, 且它只有两个实例(Male: 表示男, Female: 表示女)
-
定义Person类,它有两个成员变量(姓名、性别)
-
在测试类中创建Person类的对象, 并测试.
package test7
object Test7 {
trait Sex
case object Male extends Sex
case object Female extends Sex
case 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 test7
object Test8 {
case class Calculate(a: Int, b: Int) {
def add() = a + b
def sub() = a - b
def mul() = a * b
def 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())
}
}