【图文详细 】Scala——泛型

版权声明:版权声明:本文为博主原创文章,转载请附上博文链接! https://blog.csdn.net/qq_42246689/article/details/85231848

3、Scala 泛型 

3.1、Scala 泛型基础 

泛型用于指定方法或类可以接受任意类型参数,参数在实际使用时才被确定,泛型可以有效 地增强程序的适用性,使用泛型可以使得类或方法具有更强的通用性。泛型的典型应用场景 是集合及集合中的方法参数,可以说同 Java 一样,Scala 中泛型无处不在,具体可查看 Scala 的 API 
 
泛型类:指定类可以接受任意类型参数。

泛型方法:指定方法可以接受任意类型参数。 

package com.mazh.scala.day3 
 
class Person[T](var name:T) 
class Student[T,S](name:T,var age:S) extends Person(name) 
 
/** 
  * 作者: 李涛  https://blog.csdn.net/qq_42246689 
  */ 
object GenericTypeTest { 
  def main(args: Array[String]): Unit = { 
    println (new Student[String,Int]("黄渤",33).name) 
  } 
} 
 

3.2、Scala 类型变量界定 

类型变量界定是指在泛型的基础上,对泛型的范围进行进一步的界定,从而缩小泛型的具体

范围 比如下面的代码编译不通过:

class GenericTypeTest2 { 
  def compare[T](first:T,second:T) = { 
    if (first.compareTo(second)>0) 
      first 
    else 
      second 
  } 
} 
 
object GenericTypeTest2{ 
  def main(args: Array[String]): Unit = { 
    val tvb = new GenericTypeTest2 
    println (tvb.compare("A", "B")) 
  } 
} 

代码为什么编译不通过,是因为:泛型 T 并不一定具备 compareTo 方法 
 

如果想编译通过,请做如下更改: 

class GenericTypeTest2 { 
  def compare[T <: Comparable[T]](first:T,second:T)={ 
    if (first.compareTo(second)>0) 
      first 
    else 
      second 
  } 
} 
 
object GenericTypeTest2{ 
  def main(args: Array[String]): Unit = { 
    val tvb=new GenericTypeTest2 
    println (tvb.compare("A", "B")) 
 } 
} 

代码中改动的地方:

T <: Comparable[T]

这是什么意思呢?

compareTo 方法中如果输入的类型处于 Comparable 类对应继承层次结构中,则是合法的, 否则的话编译会报错 

3.3、Scala 视图界定

上面讲的类型变量界定建立在类继承层次结构的基础上,但有时候这种限定不能满足实际要 求,如果希望跨越类继承层次结构时,可以使用视图界定来实现的,其后面的原理是通过隐 式转换来实现。 
 
隐含参数和方法也可以定义隐式转换,称作视图。视图的绑定从另一个角度看就是 implicit 的转换。主要用在两个场合: 、当一个 T 类型的变量 t 要装换成 A 类型时 、当一个类型 T 的变量 t 无法拥有 A 类型的 a 方法或变量时  其实视图的绑定是为了更方便的使用隐式装换 视图界定利用<%符号来实现 
 
如下:代码的执行会抛出异常

object GenericTypeTest3{ 
 
  def main(args: Array[String]): Unit = { 
    // 这是合法的语句 
    val s= Student11 ("john","170") 
    // 下面这条语句不合法,这是因为 ,Int 类型没有实现 Comparable 接口 
    val s2= Student11 ("john",170) 
  } 
} 

如果想通过,那么要使用视图界定的技术 更改之后的代码: 

package com.mazh.scala.day3 
 
case class Student11[T,S <% Comparable[S]](var name:T,var height:S) 
object GenericTypeTest3{ 
 
  def main(args: Array[String]): Unit = { 
    // 这是合法的语句 
    val s= Student11 ("john","170") 
    // Int 类型 的变量经过 隐式 转换成了 R ichInt 类型, R ichInt 类型是实现了 C omparable 接口的 
    val s2= Student11 ("john",170) 
  } 
} 

改动的代码:

case class Student11[T, S <% Comparable[S]](var name:T, var height:S) 
 
能运行的原因:

利用<%符号对泛型 S 进行限定,它的意思是 S 可以是 Comparable 类继承层次结构中实现了 Comparable 接口的类,也可以是能够经过隐式转换得到的实现了 Comparable 接口的类。 
 
上面改动的语句在视图界定中是合法的,因为 Int 类型此时会隐式转换为 RichInt 类,而 RichInt 类属于 Comparable 继承层次结构。Int 类会隐式转换成 RichInt 类,RichInt 并不是直 接实现 Comparable 口,而是通过 ScalaNumberProxy 类将 Comparable 中的方法继承过来。 

3.4、Scala 上界下界 

上界、下界介绍  在指定泛型类型时,有时需要界定泛型类型的范围,而不是接收任意类型。比如,要求某个 泛型类型,必须是某个类的子类,这样在程序中就可以放心的调用父类的方法,程序才能正 常的使用与运行。此时,就可以使用上下边界 Bounds 的特性;  Scala 的上下边界特性允许泛型类型是某个类的子类,或者是某个类的父类; 
1、U >: T 这是类型下界的定义,也就是 U 必须是类型 T 的父类(或本身,自己也可以认为是自己的父 类)。 
2、S <: T 这是类型上界的定义,也就是 S 必须是类型 T 的子类(或本身,自己也可以认为是自己的子 类)。 

3.4.1、上界 
在讲解类型变量绑定的内容中,咱们写过这么一段代码:

class GenericTypeTest2 { 
  def compare[T <: Comparable[T]](first:T,second:T)={ 
    if (first.compareTo(second)>0) 
      first 
    else 
      second 
  } }

标红的地方,其实就是上界,因为它限定了继承层次结构中最顶层的类,例如 T <: Comparable[T] 表示泛型 T 的类型的最顶层类是 Comparable,所有输入是 Comparable 的子类都是合法的,其 它的都是非法的,因为被称为上界 

3.4.2、下界 

当然,除了上界之外,还有个非常重要的内容就是下界,下界通过>:符号来标识,比如: 

class A 
class B extends A 
class C extends B 
class D extends C 

如果代码中这么写:opt[T >: C]

那么表示 T 的类型只能是 A,B,C 了。不能是 D,其实就是限制了最底层的类型是什么。在类 的继承结构体系中,从上到下,只能到类型 C 为止 
 
下界的作用主要是保证类型安全 
 
实例代码:

object GenericTypeTest4 { 
 
  def getIDCard[R >: Son](person:R): Unit ={
     println("好吧,他的身份证就交给你保管了"); 
  } 
 
  def main(args: Array[String]): Unit = { 
 
// getIDCard [ T ] (t:T) 前面 这个 T 表示方法中 的参数 类型 被固定下来。 
// 在 定义 的时候还不知道这个 T 类型 到底应该是什么,但是 调用 的时候,被确定 下来 是某种类型 
// 但是 , 参数 中的类型,无论如何都可以是 T 的子类 类型,这属于多态范畴
 
    getIDCard[GranderFather](new GranderFather) 
    getIDCard[GranderFather](new Father) 
    getIDCard[Father](new Father) 
    getIDCard[Son](new Son) 
 
    // 这句代码会报错
     getIDCard[Tongzhuo](new Tongzhuo) 
  } 
} 
 
class GranderFather 
class Father extends GranderFather 
class Son extends Father 
class Tongzhuo

3.5、Scala 逆变和协变 


3.5.1、协变 
协变定义形式如:

trait List[+T]{}

当类型 B 是类型 A 的子类型时,则 List[B]也可以认为是 List[A}的子类型,即 List[B]可以泛化 为 List[A]。

也就是被参数化类型的泛化方向与参数类型的方向是一致的,所以称为协变 (covariance) 

首先,Java 中不存在协变: 

public class TypeTest { 
  
 public static void main(String[] args) { 
   
  java.util.List<String> s1 = new LinkedList<String>(); 
  java.util.List<Object> s2 = new LinkedList<Object>(); 
  /**
   * 下面这条语句会报错 
   * Type mismatch: cannot convert from List<String> to List<Object> 
   */ 
  s2 = s1; 
 } 
} 

然在类层次结构上看,String 是 Object 类的子类,但 List<String>并不是的 List<Object>子类, 也就是说它不是协变的。Java 的灵活性就这么差吗?其实 Java 不提供协变和逆变这种特性 是有其道理的,这是因为协变和逆变会破坏类型安全。假设上面的代码是合法的,我们此时 完全可以 s2.add(new Person(“xuzheng”)往集合中添加 Person 对象,但此时我们知道,s2 已 经指向了 s1,而 s1 里面的元素类型是 String 类型,这时其类型安全就被破坏了,从这个角 度来看,Java 不提供协变和逆变是有其合理性的。 
 
那为什么 Java 不支持,而 Scala 支持呢? 
 
先来看一段 Scala 代码:Scala 比 Java 更灵活,当不指定逆变和协变时,和 Java 是一样的。 代码如下:
 

package com.mazh.scala.day3.gao 
 
object XieBianTest { 
  def main(args: Array[String]): Unit = {
     val list1:MyList[String]= new MyList[String]("黄",null)
     val list2:MyList[String]= new MyList[String]("黄",new MyList[String]("黄",null)) 
  } 
} 
class MyList[T](val head: T, val tail: MyList[T]){} 

3.5.2、逆变 
逆变定义形式如:trait List[-T]{} 当类型 B 是类型 A 的子类型,

则 Queue[A]反过来可以认为是 Queue[B}的子类型。

也就是被 参数化类型的泛化方向与参数类型的方向是相反的,所以称为逆变(contravariance) 
 

猜你喜欢

转载自blog.csdn.net/qq_42246689/article/details/85231848