Scala中的trait

22 阅读3分钟

(一)trait的加载顺序

一个类实现了多个特质之后,所涉及的多个构造器的执行顺序如何确定?

先执行父类中的构造器,再执行子类的构造器;如果有多个父类,则按照从左到右的顺序调用。

image.png

/*
    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 多继承的关键特性:

  1. 多继承:一个类可以继承多个 trait
  2. 混入组合:使用 with 关键字连接多个 trait
  3. 代码复用:trait 可以提供具体方法的默认实现
  4. 强制实现:trait 中的抽象方法必须在类中实现
  5. 线性化:Scala 使用线性化算法解决钻石继承问题

trait 与抽象类的区别:

特性trait抽象类
多继承✅ 支持❌ 不支持
构造参数❌ 不能有✅ 可以有
实例化❌ 不能❌ 不能
代码复用✅ 很好✅ 好

trait 是 Scala 实现多继承的主要机制,非常灵活和强大!

总结区分方法:

特征具体属性抽象属性
初始值✅ 有初始值❌ 没有初始值
实现状态✅ 已实现❌ 只有声明
子类要求⚠️ 可选择重写🔴 必须实现
语法val name: Type = valueval 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 支持多继承
  • 两者都可以有抽象和具体成员