(一)trait的加载顺序
一个类实现了多个特质之后,所涉及的多个构造器的执行顺序如何确定?
先执行父类中的构造器,再执行子类的构造器;如果有多个父类,则按照从左到右的顺序调用。
/*
trait: 实现多继承
*/
object class03 {
trait BeautifulEye {
val eye: String = "眼睛漂亮" // 具体属性 - 有具体值
val name: String // 抽象属性 - 没有具体值
}
trait Tall {
val height: String = "大高个"
def run(): Unit = { // 具体方法 - 有方法体
println("run....")
}
def jump(): Unit // 抽象方法 - 没有方法体
}
// 继承 with
class Child extends BeautifulEye with Tall {
val name: String = "小花" // 实现抽象属性
def jump(): Unit = { // 实现抽象方法
println(s"${name}, jump....")
}
}
def main(args: Array[String]): Unit = {
val child = new Child()
println(child.eye) // 具体属性
println(child.height) // 具体属性
println(child.name) // 实现的抽象属性
child.run() // 具体方法
child.jump() // 实现的抽象方法
}
}
trait 多继承的关键特性:
- 多继承:一个类可以继承多个 trait
- 混入组合:使用
with关键字连接多个 trait - 代码复用:trait 可以提供具体方法的默认实现
- 强制实现:trait 中的抽象方法必须在类中实现
- 线性化:Scala 使用线性化算法解决钻石继承问题
trait 与抽象类的区别:
| 特性 | trait | 抽象类 |
|---|---|---|
| 多继承 | ✅ 支持 | ❌ 不支持 |
| 构造参数 | ❌ 不能有 | ✅ 可以有 |
| 实例化 | ❌ 不能 | ❌ 不能 |
| 代码复用 | ✅ 很好 | ✅ 好 |
trait 是 Scala 实现多继承的主要机制,非常灵活和强大!
总结区分方法:
| 特征 | 具体属性 | 抽象属性 |
|---|---|---|
| 初始值 | ✅ 有初始值 | ❌ 没有初始值 |
| 实现状态 | ✅ 已实现 | ❌ 只有声明 |
| 子类要求 | ⚠️ 可选择重写 | 🔴 必须实现 |
| 语法 | val name: Type = value | val name: Type |
| override | 可选使用 | 必须使用 |
简单记忆:有等号(=)和值的是具体属性,只有类型声明的是抽象属性。
子类要求
- 必须实现所有抽象属性和抽象方法
- 可以选择重写具体属性和具体方法
- 使用
override关键字重写
(二)空指针异常
1. 什么是空指针异常
object NullPointerExample {
def main(args: Array[String]): Unit = {
// 可能引发空指针异常的情况
// 1. 访问null对象的成员
val str: String = null
// println(str.length) // 运行时报错: NullPointerException
// 2. 调用null对象的方法
val list: List[Int] = null
// println(list.head) // NullPointerException
// 3. 数组元素为null
val array = Array[String]("a", null, "c")
// println(array(1).length) // NullPointerException
}
}
2. Scala 的解决方案:Option 类型
object OptionSolution {
def main(args: Array[String]): Unit = {
// Option 类型:Some(value) 或 None
val someValue: Option[String] = Some("Hello")
val noValue: Option[String] = None
// 安全访问方式
println(someValue.getOrElse("默认值")) // Hello
println(noValue.getOrElse("默认值")) // 默认值
// 模式匹配
someValue match {
case Some(value) => println(s"有值: $value")
case None => println("没有值")
}
noValue match {
case Some(value) => println(s"有值: $value")
case None => println("没有值") // 执行这里
}
// map 操作
val result1 = someValue.map(_.length) // Some(5)
val result2 = noValue.map(_.length) // None
println(result1) // Some(5)
println(result2) // None
}
}
3. 实际应用示例
object SafeProgramming {
case class User(name: String, age: Option[Int], email: Option[String])
def getUserName(user: Option[User]): String = {
user.map(_.name).getOrElse("未知用户")
}
def getUserAge(user: Option[User]): Int = {
user.flatMap(_.age).getOrElse(0)
}
def sendEmail(user: Option[User]): Unit = {
user.flatMap(_.email) match {
case Some(email) => println(s"发送邮件到: $email")
case None => println("没有邮箱地址")
}
}
def main(args: Array[String]): Unit = {
val user1 = Some(User("张三", Some(25), Some("zhang@example.com")))
val user2 = Some(User("李四", None, None))
val user3: Option[User] = None
println(getUserName(user1)) // 张三
println(getUserName(user2)) // 李四
println(getUserName(user3)) // 未知用户
println(getUserAge(user1)) // 25
println(getUserAge(user2)) // 0
println(getUserAge(user3)) // 0
sendEmail(user1) // 发送邮件到: zhang@example.com
sendEmail(user2) // 没有邮箱地址
sendEmail(user3) // 没有邮箱地址
}
}
(三)trait 与类的区别
1. 基本区别对比
object TraitVsClass {
// 类 - 可以被实例化
class Animal(val name: String) {
def eat(): Unit = println(s"$name 在吃东西")
}
// 特质 - 不能被实例化
trait Flyable {
def fly(): Unit
}
trait Swimmable {
def swim(): Unit = println("游泳")
}
// 类继承特质
class Bird(name: String) extends Animal(name) with Flyable with Swimmable {
override def fly(): Unit = println(s"$name 在飞翔")
}
class Fish(name: String) extends Animal(name) with Swimmable {
// 使用默认的swim实现
}
}
2. 详细区别表格
| 特性 | 类 (Class) | 特质 (Trait) |
|---|---|---|
| 实例化 | ✅ 可以实例化 | ❌ 不能实例化 |
| 多继承 | ❌ 不支持 | ✅ 支持多继承 |
| 构造参数 | ✅ 可以有 | ❌ 不能有 |
| 抽象成员 | ✅ 可以有 | ✅ 可以有 |
| 具体成员 | ✅ 可以有 | ✅ 可以有 |
| 线性化 | 单继承链 | 复杂的线性化 |
| 用途 | 创建对象 | 定义接口和混入功能 |
3. 实际代码对比
object DetailedComparison {
// === 类示例 ===
class Person(val name: String, val age: Int) { // 有构造参数
def introduce(): String = s"我是$name,今年$age岁"
// 可以实例化
// val p = new Person("张三", 25)
}
// === 特质示例 ===
trait Speaker {
// 不能有构造参数
// trait Speaker(name: String) // 错误!
// 可以有抽象方法
def speak(): Unit
// 可以有具体方法
def greet(): Unit = {
println("你好!")
}
// 可以有具体字段
val language: String = "中文"
// 可以有抽象字段
val volume: Int
}
trait Listener {
def listen(): Unit = {
println("认真倾听")
}
}
// === 类继承特质 ===
class Student(name: String, age: Int, val major: String)
extends Person(name, age) with Speaker with Listener {
// 必须实现抽象成员
override def speak(): Unit = {
println(s"$name 在讲述$major专业的知识")
}
override val volume: Int = 8
}
class Teacher(name: String, age: Int, val subject: String)
extends Person(name, age) with Speaker {
override def speak(): Unit = {
println(s"$name 老师在教授$subject")
}
override val volume: Int = 10
// 重写具体方法
override def greet(): Unit = {
println("同学们好!")
}
}
def main(args: Array[String]): Unit = {
val student = new Student("李四", 20, "计算机科学")
val teacher = new Teacher("王老师", 35, "数学")
println("=== 学生 ===")
println(student.introduce())
student.speak()
student.greet()
student.listen()
println(s"语言: ${student.language}, 音量: ${student.volume}")
println("\n=== 老师 ===")
println(teacher.introduce())
teacher.speak()
teacher.greet()
println(s"语言: ${teacher.language}, 音量: ${teacher.volume}")
// 多态演示
println("\n=== 多态演示 ===")
val speakers: List[Speaker] = List(student, teacher)
speakers.foreach { speaker =>
speaker.speak()
speaker.greet()
}
}
}
4. 线性化示例
object LinearizationExample {
trait A {
def msg: String = "来自A"
}
trait B extends A {
override def msg: String = "来自B -> " + super.msg
}
trait C extends A {
override def msg: String = "来自C -> " + super.msg
}
class D extends B with C {
override def msg: String = "来自D -> " + super.msg
}
class E extends C with B {
override def msg: String = "来自E -> " + super.msg
}
def main(args: Array[String]): Unit = {
val d = new D()
val e = new E()
println(d.msg) // 来自D -> 来自C -> 来自B -> 来自A
println(e.msg) // 来自E -> 来自B -> 来自C -> 来自A
// 线性化顺序:从右到左
// D: D -> C -> B -> A
// E: E -> B -> C -> A
}
}
5. 使用场景总结
object UsageScenarios {
// 场景1:定义接口(使用特质)
trait Database {
def connect(): Unit
def disconnect(): Unit
def query(sql: String): List[String]
}
// 场景2:混入功能(使用特质)
trait Logging {
def log(message: String): Unit = {
println(s"[LOG] $message")
}
}
trait Caching {
private var cache: Map[String, Any] = Map()
def getFromCache(key: String): Option[Any] = cache.get(key)
def putToCache(key: String, value: Any): Unit = {
cache += (key -> value)
}
}
// 场景3:具体实现(使用类)
class MySQLDatabase extends Database with Logging with Caching {
override def connect(): Unit = {
log("连接MySQL数据库")
println("MySQL连接成功")
}
override def disconnect(): Unit = {
log("断开MySQL连接")
println("MySQL断开连接")
}
override def query(sql: String): List[String] = {
log(s"执行查询: $sql")
List("结果1", "结果2", "结果3")
}
}
def main(args: Array[String]): Unit = {
val db = new MySQLDatabase()
db.connect()
db.putToCache("users", List("张三", "李四"))
val results = db.query("SELECT * FROM users")
println(s"查询结果: $results")
db.disconnect()
}
}
总结
空指针异常:Scala 通过 Option 类型提供编译时安全,避免运行时异常。
trait 与类的区别:
- 类用于创建对象,trait 用于定义接口和混入功能
- 类支持构造参数,trait 不支持
- 类不支持多继承,trait 支持多继承
- 两者都可以有抽象和具体成员