泛型基础
我们利用[]来说明泛型。给出代码实例:
package example
class Stack[A] {
private var elements: List[A] = Nil
def push(elem: A) =
elements = elem :: elements
def peek() = elements.head
def pop(): A = {
val top = peek
elements = elements.tail
top
}
}
object MyExample {
def main(args: Array[String]): Unit = {
val st = new Stack[Int]
st.push(10)
st.push(20)
println(st.peek)
st.pop()
println(st.peek)
}
}
代码输出:
20
10
型变
协变
看下定义:
如果
B是A的类,那么List[B]就是List[A]的子类。在泛型中,我们需要定义为List[+A]。
直接给出代码实例:
package example
abstract class Animal
class Cat(val catName: String) extends Animal
class Dog(val dogName: String) extends Animal
// 这里声明为协变
abstract class AnimalPrinter[+Animal] {
def print(): Unit
}
class CatPrinter(name: String) extends AnimalPrinter[Cat] {
private val cat = new Cat(name)
override def print() = println(cat.catName)
}
class DogPrinter(name: String) extends AnimalPrinter[Dog] {
private val dog = new Dog(name)
override def print() = println(dog.dogName)
}
object MyExample {
// 返回值这里,前面我们声明为协变类型了,因此这里可以直接返回,否则会报错
def getPrinter(n: Int, name: String): AnimalPrinter[Animal] = {
if (n < 0) {
new CatPrinter(name)
} else {
new DogPrinter(name)
}
}
def main(args: Array[String]): Unit = {
val catPrinter = getPrinter(-1, "foo-cat")
val dogPrinter = getPrinter(1, "foo-dog")
catPrinter.print()
dogPrinter.print()
}
}
代码输出:
foo-cat
foo-dog
我们在代码中,把AnimalPrinter声明为Animal协变的泛型类型,因此只要其他类A集成了AnimalPrinter而且泛型也是Animal的子类,那么A就是AnimalPrinter[Animal]的子类,面向对象的那些法则都可以适用。协变还是比较容易理解的。
逆变
看下定义:
逆变正好和协变相反,我们需要看下适用应用来理解对应的场景:
package example
abstract class Fruit {
def getPrice(): Double
def getName(): String
}
class Apple(name: String, var price: Double) extends Fruit {
override def getName(): String = name
override def getPrice(): Double = price
override def toString(): String = s"[name: $name, price $price]"
}
// 这里声明为逆变
trait Judger[-T] {
def judge(t: T): Boolean
}
class PriceJudger(val price) extends Judger[Fruit] {
override def judge(fruit: Fruit): Boolean = fruit.getPrice() < price
}
class AppleBasket {
private var apples: List[Apple] = Nil
def addApple(apple: Apple): Unit = {
apples = apple :: apples
}
def getCheapApples(judger: Judger[Apple]): List[Apple] = {
apples.filter(judger.judge(_)).toList
}
}
object MyExample {
def main(args: Array[String]): Unit = {
val appleBasket = new AppleBasket
appleBasket.addApple(new Apple("a0", 5.2))
appleBasket.addApple(new Apple("a1", 11.8))
appleBasket.addApple(new Apple("a2", 7.9))
val priceJudger = new PriceJudger(10.0)
// 这里用到了逆变的特性,priceJudger反而是Judger[Fruit]的子类的了
val cheapApples = appleBasket.getCheapApples(priceJudger)
println(cheapApples.toString)
}
}
代码输出:
List([name: a2, price 7.9], [name: a0, price 5.2])
我们说明一下代码的意义:
-
Fruit作为抽象水果类型,拥有抽象方法getName和getPrice。Apple是具体化的水果class。 -
Judge是一个泛型的trait类型,拥有judge抽象方法方法,一般用于判断某个类型的实例是否满足要求。这里,我们具体化了一个PriceTrait类,该类可以判断所有种类的水果,价格是否符合预期。 -
AppleBasket是一个苹果集合,我们有addApple和getCheapApples方法。后者是获取价格低于10的苹果种类List。对于AppleBasket实现者来说,我们需要的就是针对Apple判断价格类型的Judger即可,因此参数是Judger[Apple]类型 -
但是,站在系统架构角度来看,价格
Judger只需要有一个class即可,然后根据实际情况设置阈值
综上所述,我们给Judger设置为协变的泛型,这样Judger[Fruit]是Judger[Apple]的子类的了,自然就可以了完成上述代码中的赋值了。
协变一般是为了各类架构设计者和具体代码实现者的一种方案,然后简化写的代码量。
类型上下界
上界
规定泛型类型中,必须是某个类或者它的子类。直接给出代码:
package example
abstract class Animal
class Cat extends Animal
class Dog extends Animal
class Apple
class AnimalContainer[T <: Animal] {
private var animals: List[T] = Nil
def addAnimal(animal: T) = animals = animal :: animals
}
object MyExample {
def main(args: Array[String]): Unit = {
val catContainer = new AnimalContainer[Cat]
val dogContainer = new AnimalContainer[Dog]
// 错误的,必须是Animal子类
// val appleContainer = new AnimalContainer[Apple]
}
}
下界
和上界相反,必须是某个类或者该类的父类,直接给出代码实例:
package example
abstract class Animal
class Dog extends Animal
class SmallDog extends Dog
class MiddleDog extends SmallDog
class BigDog extends MiddleDog
class DogContainer[T >: MiddleDog] {
}
object MyExample {
def main(args: Array[String]): Unit = {
val dc = new DogContainer[MiddleDog]
val dc1 = new DogContainer[SmallDog]
// 编译错误,被类型限制了
// val dc2 = new DogContainer[BigDog]
}
}