欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

scala 上界 <:, 下界>:, 视界 <%, 边界 :, 协变 +T, 逆变-T

程序员文章站 2022-06-06 23:19:33
...

概述

上界 (<:) 下界(>:) 是类型约束范畴,用来约束对象的子类是谁,父类是谁
协变 (+T) 逆变(-T)是泛型范畴,主要用于集合类型变量赋值。
视界 (<%) 边界( :)是隐式调用的简写。

上界 (<:) 下界(>:)

上界:要求对象必须是某一个对象的子类,因为子类会继承父类的属性和方法,所以父类的方法和属性这路都可以用。

  trait A {
    def func()
  }
  class B extends A {
    override def func(): Unit = {
      println("i am b")
    }
  }

  class C extends A {
    override def func(): Unit = {
      println("i am c")
    }
  }
  
  def test[T <: A](obj: T): Unit = {
    obj.func()
  }
  
  test(new B()) //  i am b
  test(new C()) //  i am c

下界:要求对象必须是某一个对象的父类。主要用在已知某一个具体类型,但是要给它赋值给另一个变量,该变量必须是具体类型的父类型才行。

举例:
对于下面的例子,我们要把元素B放到一个集合中,这个集合必须是类型B父类容器才行。这里就是B是已知类型,集合类型未知。

  trait A {
    def func()
  }

  class B extends A {
    override def func(): Unit = {
      println("i am b")
    }
  }
  def add[T >: B](list: collection.mutable.Set[T], elem: B): collection.mutable.Set[T] = {
    list.add(elem)
    list
  }

协变 (+T) 逆变(-T)

逆变协变指的是类型T和包装类型直接的关系。
有两个类如下所示:

  trait A {
    def func()
  }

  class B extends A {
    override def func(): Unit = {
      println("i am b")
    }
  }

假设有个包装类型F[T]

 class F[T]

现在我们生产一个F[B]实例,然后赋值给F[A]类型的变量.就会报错。因为F[B]并不是F[A]的子类。

 val a:F[A] = new F[B]() // error

所以这个时候才会需要逆变和协变。
协变:子类的包装类型也是父类包装类型的子类

  class F[+T] // 协变
  val a: F[A] = new F[B]() // 正确

逆变:子类的包装类型是父类包装类型的父类

  class G[-T] // 逆变
  val b: G[B] = new G[A]() // 正确

一个比较经典的例子是:scala 函数的定义.scala中所有的函数,参数都是逆变的,返回值都是协变的。 trait Function[-T1,+T2].也就是说一个函数要赋值给一个函数类型变量。这个函数的参数必须是函数变量参数的父类,返回值必须是函数变量返回值的子类。

    trait A {
      def func()
    }

    class B extends A {
      override def func(): Unit = {
        println("i am b")
      }
    }

    class C extends B {}
    val func: B => B = (a: A) => { new C() } // 把 A=>C类型的函数赋值给B=>B类型的变量

视界 <%, 边界 :

视图和边界看到的比较多,但是功能比较强大,也容易被误解。比如 ( : )就经常会被误以为是上界和下届的一种。

视界 <%

视界只是一种隐式类型函数的简写。如果我们需要一个Fruits类型的隐式参数,但是我们只有一个 T 类型,这时就可以用视界。他要求想使用这个函数,必须提供一个由T => Fruits的隐式调用。 比如下面:


scala> case class Fruits(name: String)
defined class Fruits

scala> def getFruits[T <% Fruits](value: T): Fruits = value
getFruits: [T](value: T)(implicit evidence$1: T => Fruits)Fruits

等价于

scala> def getFruits[T](value: T)(implicit evidence: T => Fruits): Fruits = evidence(value)
getFruits: [T](value: T)(implicit evidence: T => Fruits)Fruits

边界 :

边界相对来将应用场景比较多一些。下面有个函数anothr,需要一个隐式参数。我们在func中调用了这个方法,但是我们并没有提供隐式参数。这就和视图有点类似了。在视图中要求传入一个隐式函数,但这里是要求传入一个隐式类型。

class F[T]

def another[T](elem: T)(implicit f: F[T]) = {
  // do nothing
}

def func[T: F](elem: T): Unit = {
  another(elem)
}

scala>  def func[T: F](elem: T): Unit = {
   			another(elem)
 		}
 		
func: [T](elem: T)(implicit evidence$1: F[T])Unit
// 使用
scala> func(1)(new F[Int]())
或则
scala> func(1)(new F()) //自行推断

有一个比较经典的例子,利用这个方法可以实现隐式泛型

import scala.reflect.runtime.universe._

typeOf函数需要传入一个隐式类型TypeTag[T]
// def typeOf[T](implicit ttag: TypeTag[T]): Type = ttag.tpe

def getTypeTag[T: TypeTag](obj: T) = typeOf[T] 

// 我们调用的时候也没有传入隐式类型,因为scala运行环境中会自定存储。
getTypeTag(1)
相关标签: Scala学习指南