【建议收藏】|3分钟让你学会Scala Trait 使用

欢迎关注公众号: 【857Hub】 ,带你玩转大数据

Trait 是什么

Scala 是一种强大的静态类型编程语言,其中的 Trait 是一种重要的特性。Trait 可以被看作是一种包含方法和字段定义的模板,可以被其他类或 Trait 继承或混入。在本文中,我们将介绍 Scala Trait 的边界(Boundary)的概念,并展示如何使用它来限制 Trait 的使用范围。

Trait的作用

Trait 可以用来限制 Trait 可以被哪些类或 Trait 继承或混入。通过定义边界,我们可以确保 Trait 只能被特定类型的类或 Trait 使用,从而提高代码的可读性和可维护性。

定义 Trait

在 Scala 中,我们可以使用 extends 关键字来定义 Trait 的边界。下面是一个简单的例子:

scala 复制代码
trait Animal
class Dog extends Animal {
  def bark(): Unit = {
    println("Woof woof!")
  }
}
class Cat extends Animal {
  def meow(): Unit = {
    println("Meow meow!")
  }
}
trait Pet[A <: Animal] {
  def play(): Unit = {
    println("Playing with " + animalType)
  }
  def animalType: String
}
class PetDog extends Pet[Dog] {
  override def animalType: String = "Dog"
}
class PetCat extends Pet[Cat] {
  override def animalType: String = "Cat"
}

在上面的例子中,我们定义了一个名为 Animal 的 Trait,它是所有动物类的基类。然后我们定义了 Dog 和 Cat 两个类,它们都继承自 Animal。 接下来,我们定义了一个 Pet Trait,它的边界为 A <: Animal,表示它只能被继承自 Animal 的类使用。Pet Trait 中有一个 play 方法和一个 animalType 方法。 最后,我们定义了 PetDog 和 PetCat 两个类,它们分别继承自 Pet[Dog] 和 Pet[Cat]。这样,我们就限制了 PetDog 只能与 Dog 类一起使用,而 PetCat 只能与 Cat 类一起使用。

使用 Trait

现在我们可以使用定义好的 Trait 边界来创建对象了。下面是一个简单的示例:

scala 复制代码
object Main extends App {
  val petDog = new PetDog()
  val petCat = new PetCat()
  petDog.play()  // 输出:Playing with Dog
  petCat.play()  // 输出:Playing with Cat
}

边界(Bounds)

Trait的边界指的是Trait可以被哪些类混入。Scala中有三种边界的写法:上界(Upper Bounds)、下界(Lower Bounds)和视图界(View Bounds)。

上界(Upper Bounds)

上界指定了Trait可以被拥有某个特定特质或超类的类混入。使用上界可以限制Trait的使用范围,确保只有满足条件的类才能混入该Trait。

同Trait 代码,这里不做赘述

下界(Lower Bounds)

下界指定了Trait可以被拥有某个特定子类的类混入。使用下界可以确保Trait只能被某个特定的子类混入。

下面是一个简单的例子:

scala 复制代码
trait Animal {
  def eat(): Unit
}
trait Cat extends Animal {
   def eat(): Unit = println("Eat Mouse")
}
trait Dog extends Animal {
   def eat(): Unit = println("Eat Shit")
}
trait Lactation[A >: Cat] extends Animal {
  def eat(): Unit = println("suckle")
}
class Persian extends Cat {
  override def eat(): Unit = println("Persian cats eat cat food")
}
class HaBa extends Dog {
  override def eat(): Unit = println("HaBa to eat bone")
}
object Main extends App {
  val persian: Animal = new Persian()
  val lactation: Animal = new Lactation[Cat] {}
  persian.eat() //输出: Persian cats eat cat food
  lactation.eat() // 输出: suckle
  val lactation1: Animal = new Lactation[Dog] {}  //直接报错: Type Dog does not conform to lower bound Cat of type parameter A
}

视图界(View Bounds)

视图界指定了Trait可以被某个特定隐式转换后的类混入。使用视图界可以实现对不同类型的隐式转换,从而扩展Trait的使用范围。

从Scala 2.10版本开始,视图界已被弃用,推荐使用上界(<:)或隐式参数来替代。

协变(Covariance)

协变是Trait的类型参数声明方式,用于指定Trait的泛型参数可以是Trait本身或者Trait的子类。

假设我们有一个简单的动物园应用程序,其中有一个 Cage 类表示动物笼子,同时有一个 Animal 类表示动物。为了让我们的应用程序更加灵活,我们希望能够创建一个 Cage[+T] 类型的容器类,该类中的类型参数 T 是协变的,这样就可以存放 Animal 对象或其子类的对象。

下面是一个简单的例子:

scala 复制代码
class Animal(val name: String)

class Cat(override val name: String) extends Animal(name)

class Dog(override val name: String) extends Animal(name)

class Cage[+T](val animal: T)

object CovarianceTest {
  def main(args: Array[String]): Unit = {
    val cat: Cat = new Cat("Tom")
    val catCage: Cage[Cat] = new Cage(cat)
    val animalCage: Cage[Animal] = catCage // 协变

    println(animalCage.animal.name) // 输出 Tom
  }
}

在这个代码中,我们定义了一个 Cage[+T] 类型的容器类,该类中的类型参数 T 是协变的。我们创建了一个 catCage 变量来存放 Cat 类型的对象,然后将其转换为一个 animalCage 变量,该变量可以存放 Animal 类型的对象。

在 main 方法中,我们首先创建了一个 Cat 类型的对象 cat,然后用 cat 创建了一个 Cage[Cat] 类型的对象 catCage。接下来,我们将 catCage 赋值给 animalCage,这是因为 Cage 类型是协变的,子类型的笼子可以赋值给父类型的笼子。最后,我们输出了 animalCage.animal.name,可以看到输出的是一个 Cat 类型的对象的名字。

逆变(Contravariance)

逆变是Trait中方法参数类型的一种特殊声明方式。逆变的方法参数类型可以是Trait的超类或者是Trait本身,但不能是Trait的子类。

假设我们希望为我们的动物园应用程序编写一个 Feeder 类来喂养动物。为了让 Feeder 类更加灵活,我们希望能够创建一个 Feeder[-T] 类型的喂养器类,该类中的类型参数 T 是逆变的,这样就可以接收 Animal 对象或其父类的对象。

下面是一个简单的例子:

scala 复制代码
class Animal(val name: String)

class Cat(override val name: String) extends Animal(name)

class Dog(override val name: String) extends Animal(name)

class Feeder[-T] {
  def feed(animal: T): Unit = println(s"Feeding $animal")
}
object CovarianceTest {
  def main(args: Array[String]): Unit = {
      val cat: Cat = new Cat("Garfield")
      val animal: Animal = cat
      val feeder: Feeder[Cat] = new Feeder[Cat] // 创建一个类型为 Feeder[Cat] 的喂养器对象
      val animalFeeder: Feeder[Cat] = new Feeder[Animal]
      // val animalFeeder: Feeder[Animal] = feeder // 错误!不能将协变类型的 Feeder[Animal] 赋值给逆变类型的 Feeder[Cat]
      animalFeeder.feed(cat) // 输出 Feeding Cat(Garfield)
      animalFeeder.feed(cat) // 输出 Feeding Cat(Garfield)
  }
}

在这个代码中,我们定义了一个 Feeder[-T] 类型的喂养器类,该类中的类型参数 T 是逆变的。我们想要创建一个 Feeder[Animal] 类型的喂养器对象,并将其赋值给一个类型为 Feeder[Cat] 的变量 feeder,这是不合法的,因为逆变只允许将父类型的对象赋值给子类型的变量。

总结

Scala中的Trait提供了灵活的边界、逆变和协变的特性,可以根据需求限制Trait的使用范围、参数类型和泛型参数类型。通过合理使用边界、逆变和协变,可以使代码更加灵活和可复用。

以上是关于Scala Trait边界、逆变和协变的介绍,希望对你有所帮助。

相关推荐
用户02738518402614 小时前
【Android】Binder 原理初探:理解 Android 进程通信机制
程序员·源码
资源分享交流1 天前
智能课堂课程系统源码 – 多端自适应_支持讲师课程
源码
萧霍3 天前
判断两个对象是相等的
scala
Tang10244 天前
Android Koltin 图片加载库 Coil 的核心原理
源码
程序员小羊!5 天前
Flink(用Scala版本写Word Count 出现假报错情况解决方案)假报错,一直显示红色报错
flink·word·scala
没有bug.的程序员5 天前
Spring Boot Actuator 监控机制解析
java·前端·spring boot·spring·源码
shenshizhong7 天前
鸿蒙HDF框架源码分析
前端·源码·harmonyos
17318 天前
scala中访问控制与方法重写
scala
谷哥的小弟8 天前
Spring Framework源码解析——TaskExecutor
spring·源码
张较瘦_10 天前
[论文阅读] 从 5MB 到 1.6GB 数据:Java/Scala/Python 在 Spark 中的性能表现全解析
java·python·scala