Scala:类型参数

泛型类

在类的声明中,定义一些泛型类型,然后在类内部,比如field或者method,就可以使用这些泛型类型。

class Student[T](val localId: T){
      def getSchoolld(hukouId:T) = "S-" + hukouId +"-" +localId
}

Scala自动推断泛型类型特性:直接给使用了泛型类型的field赋值时,Scala会自动进行类型推断

泛型函数

泛型函数,与泛型类类似,可以给某个函数在声明时指定泛型类型,然后在函数体内,多个变量或者返回值之间,就可以使用泛型类型进行声明,从而对某个特殊的变量,或者多个变量,进行强制性的类型限制。

def getCard[T](content: T) = {
    if(content.isInstanceOf[Int]) "card: 001, " + content
    else if(content.isInstanceOf[String]) "card: this is your card, " + content
    else "card: " + content
}

上边界Bounds(<:)

在指定泛型类型的时候,有时,我们需要对泛型类型的范围进行界定,而不是可以是任意的类型。比如,我们可能要求某个泛型类型,它就必须是某个类的子类,这样在程序中就可以放心地调用泛型类型继承的父类的方法,程序才能正常的使用和运行。此时就可以使用上下边界Bounds的特性。

class Person(val name: String) {
  def sayHello = println("Hello, I'm " + name)
  def makeFriends(p: Person) {
    sayHello
    p.sayHello
  }
}

class Student(name: String) extends Person(name)

class Party[T <: Person](p1: T, p2: T) {
  def play = p1.makeFriends(p2)
}

Scala的上下边界特性允许泛型类型必须是某个类的子类,或者必须是某个类的父类

下边界Bounds(>:)

除了指定泛型类型的上边界,还可以指定下边界,即指定泛型类型必须是某个类的父类

class Father(val name: String) 
class Child(name: String) extends Father(name)

def getIDCard[R >: Child](person: R) {
  if (person.getClass == classOf[Child]) println("please tell us your parents' names.")
  else if (person.getClass == classOf[Father]) println("sign your name for your child's id card.")
  else println("sorry, you are not allowed to get id card.")
}

View Bounds(<%)

上下边界Bounds,虽然可以让一种泛型类型,支持有父子关系的多种类型。但是,在某个类与上下边界Bounds指定的父子类型范围内的类都没有任何关系,则默认是肯定不能接受的。

class Person(val name: String) {
  def sayHello = println("Hello, I'm " + name)
  def makeFriends(p: Person) {
    sayHello
    p.sayHello
  }
}

class Student(name: String) extends Person(name)

class Dog(val name: String) { def sayHello = println("Wang, Wang, I'm " + name) }

implicit def dog2person(obj: Object): Person = if(obj.isInstanceOf[Dog]) {val _dog = obj.asInstanceOf[Dog]; new Person(_dog.name) } else Nil

class Party[T <% Person](p1: T, p2: T)

Context Bounds

Context Bounds是一种特殊的Bounds,它会根据泛型类型的声明,比如“T: 类型”要求必须存在一个类型为“类型[T]”的隐式值
Context Bounds之所以叫Context,是因为它基于的是一种全局的上下文,需要使用到上下文中的隐式值以及注入。

class Calculator[T: Ordering] (val number1: T, val number2: T) {
  def max(implicit order: Ordering[T]) = if(order.compare(number1, number2) > 0) number1 else number2
}

Manifest Context Bounds

在Scala中,如果要实例化一个泛型数组,就必须使用Manifest Context Bounds。也就是说,如果数组元素类型为T的话,需要为类或者函数定义[T: Manifest]泛型类型,这样才能实例化Array[T]这种泛型数组。

class Meat(val name: String)
class Vegetable(val name: String)

def packageFood[T: Manifest] (food: T*) = {
  val foodPackage = new Array[T](food.length)
  for(i <- 0 until food.length) {
       foodPackage(i) = food(i)
    }
  foodPackage 
}

协变和逆变

协变定义形式如:trait List[+T] {} 。
当类型S是类型A的子类型时,则List[S]也可以认为是List[A}的子类型,即List[S]可以泛化为List[A]。也就是被参数化类型的泛化方向与参数类型的方向是一致的,所以称为协变(covariance)。

class Master

class Professional extends Master

class Card[+T] (val name: String)

def enterMeet(card: Card[Master]) {
  println("welcome to have this meeting!")
}

逆变定义形式如:trait List[-T] {}
当类型S是类型A的子类型,则Queue[A]反过来可以认为是Queue[S}的子类型。也就是被参数化类型的泛化方向与参数类型的方向是相反的,所以称为逆变(contravariance)

扫描二维码关注公众号,回复: 4863535 查看本文章
class Card[-T] (val name: String)
def enterMeet(card: Card[Professional]) {
  println("welcome to have this meeting!")
}

协变点和逆变点

遇见一个这样的问题
在这里插入图片描述
而这样不会出现问题
在这里插入图片描述

首先,我们假设 class In[+A]{ def fun(x:A){} } 可以通过编译,那么对于 In[AnyRef] 和 In[String] 这两个父子类型来说,fun方法分别对应:

父类型 In[AnyRef] 中的方法 fun(x: AnyRef){}

子类型 In[String] 中的方法 fun(x: String){}

根据里氏替换原则,所有使用父类型对象的地方都可以换成子类型对象。现在问题就来了,假设这样使用了父类:

father.fun(notString)

现在替换为子类:

child.fun(notString) // 失败,非String类型,不接受

之前父类型的 fun 可以接收 AnyRef类型的参数,是一个更广的范围。而子类型的 fun 却只能接收String这样更窄的范围。显然这不符合里氏替换原则了,因为父类做的事情,子类不能完全胜任,只能部分满足,是无法代替父类型的。

所以要想符合里氏替换,子类型中的fun函数参数类型必须是父类型中函数参数的超类(至少跟父类型中的参数类型一致),这样才能满足父类中fun方法可以做的事情,子类中fun方法也都可以做

正是因为需要符合里氏替换法则,方法中的参数类型声明时必须符合逆变(或不变),以让子类方法可以接收更大的范围的参数(处理能力增强);而不能声明为协变,子类方法可接收的范围是父类中参数类型的子集(处理能力减弱)。

方法参数的位置称为做逆变点(contravariant position)。

所以上面声明类型参数A是协变的,用在方法参数中时会编译报错,声明A是逆变(或不变)时则符合。

现在看看什么是协变点(covariant position),还用上面的例子,稍微修改一下:
在这里插入图片描述
在这里插入图片描述

同样用里氏替换法则来套:

父类型 In[AnyRef] 中的方法 fun() 得到结果 AnyRef

子类型 In[String] 中的方法 fun() 得到结果 String

子类方法得到的结果比父类更“具象”一些,也就是说子类方法的处理能力更强一些。如果结果类型是逆变的,那子类方法的处理能力是减弱的,不符合里氏替换。

方法返回值的位置称为协变点(covariant position)

A类型声明协变(或不变),编译时符合要求;声明逆变则报错。

Existential Type

在Scala里,有一种特殊的类型参数,就是Existential Type,存在性类型。

Array[T] forSome { type T }
Array[_]

猜你喜欢

转载自blog.csdn.net/jiaojiao521765146514/article/details/84768565