美文网首页
scala implicit (隐式转换函数、隐式类、隐式参数、

scala implicit (隐式转换函数、隐式类、隐式参数、

作者: scandly | 来源:发表于2018-08-22 10:10 被阅读0次

    implicit 是scala一个关键字,使scala更灵活和容易扩展。

    1.隐式转换函数

    implicit def int2str(x:Int):String = x.toString

    2.隐式类

    implicit class Box(x: Int) {   

    }

    3.隐式参数

    def compare[T](x:T,y:T)(implicit ordered: Ordering[T]):Int = {

      ordered.compare(x,y)

    }

    4.隐式值

    implicit val x: Int = 0

    5.隐式对象

    implicit object obj {

    }

    6.context bound

    def compare2[T: Ordering](x: T, y: T) = {

      val ord = implicitly[Ordering[T]]

      ord.compare(x, y)

    }

    一、隐式转换函数、Overview

    首先来看隐式转换函数。

    implicit def int2str(x:Int):String = x.toString1

    声明了一个隐式函数int2str。它告诉编译器,这个函数是一个隐式转换函数,把Int类型的值转换String类型的值。

    如果在进行一个对Int类型的操作时不合法,编译器会在当前作用域寻找合适的隐式转换,来尝试使这种操作合法。隐式转换发生在这两种情景:

    e是一个S类型的表达式,而需要的却是T类型,编译器会寻找S=>T的隐式转换

    e是一个S类型的表达式,使用点号访问e.m时,m不是类型S的成员,编译器会寻找合适的隐式转换使e.m合法

    隐式转换的用途就是扩展已有的类,在不修改原有类的基础上为其添加新的方法、成员。

    10.concat("hello")10.length

    接受String类型的函数也可以接受Int类型:

    def hi(x:String) = println("hi"+x)

    hi(123)

    需要注意: 

    1.对于隐式转换函数,编译器最关心的是它的类型签名,即它将哪一种类型转换到另一种类型,也就是说它应该接受只一个参数,对于接受多参数的隐式函数来说就没有隐式转换的功能了。

    implicit def int2str(x:Int):String = x.toString // 正确implicit def int2str(x:Int,y:Int):String = x.toString // 错误

    2.不支持嵌套的隐式转换

    class A{

    def hi = println("hi")

    }

    implicit def int2str(x:Int):String = x.toString

    implicit def str2A(x:Int,y:Int):A = new A"str".hi  // 正确1.hi      // 错误

    3.不能存在二义性,即同一个作用域不能定义两个相同类型的隐式转换函数,这样编译器将无法决定使用哪个转换

    /* 错误-- */implicit def int2str(x:Int):String = x.toString

    implicit def anotherInt2str(x:Int):A = x.toString/* --错误 */

    4.代码能够在不使用隐式转换的前提下能编译通过,就不会进行隐式转换

    二、隐式类

    前面提到,隐式转换最重要的应用是扩展已存在的类,它的功能和c#中的扩展方法很类似。比如我们想对已有的Int类型添加一个sayhi的方法,可以这样做:

    class SayhiImpl(ivalue:Int) {

      val value:Int = ivalue

      def sayhi = println(s"Hi $value!")

    }

    implicit def int2Sayhi(x:Int) = new SayhiImpl(x)

    那么调用123.sayhi,将会输出:Hi 123!。

    即我们先实现一个支持sayhi方法的类,再写一个隐式转换函数,使得Int类也支持sayhi。但是这种写法过于啰嗦了,可以使用隐式类实现等价的功能:

    implicit class SayhiImpl(ivalue:Int) {

      val value:Int = ivalue

      def sayhi = println(s"Hi $value!")

    }123.sayhi  //合法

    隐式类就是在类定义前加一个implicit关键字,这表示它的构造函数是一个隐式转换函数,能够将参数的类型转换成自己的类型,在这里就是构造函数SayhiImpl(ivalue:Int)定义了Int到SayhiImpl的隐式转换。

    在使用隐式类时需要注意以下限制条件

    1.只能在别的trait/类/对象内部定义。

        object Helpers {

          implicit class RichInt(x: Int) // 正确!

        }

        implicit class RichDouble(x: Double) // 错误!

    2.构造函数只能携带一个非隐式参数。

    implicit class RichDate(date: java.util.Date) // 正确!

    implicit class Indexer[T](collecton: Seq[T], index: Int) // 错误!

    implicit class Indexer[T](collecton: Seq[T])(implicit index: Index) // 正确

    虽然我们可以创建带有多个非隐式参数的隐式类,但这些类无法用于隐式转换。

    3.implict关键字不能用于case类

    implicit case class Baz(x: Int) // 错误!

    三、隐式参数

    看最开始的例子:

    def compare[T](x:T,y:T)(implicit ordered: Ordering[T]):Int = {

      ordered.compare(x,y)

    }

    在函数定义的时候,支持在最后一组参数使用 implicit,表明这是一组隐式参数。在调用该函数的时候,可以不用传递隐式参数,而编译器会自动寻找一个implict标记过的合适的值作为该参数。

    例如上面的函数,调用compare时不需要显式提供ordered,而只需要直接compare(1,2)这样使用即可。

    再举一个例子:

    object Test{

        trait Adder[T] {

          def add(x:T,y:T):T

        }

        implicit val a = new Adder[Int] {

          override def add(x: Int, y: Int): Int = x+y

        }

        def addTest(x:Int,y:Int)(implicit adder: Adder[Int]) = {

          adder.add(x,y)

        }

      addTest(1,2)      // 正确, = 3  addTest(1,2)(a)  // 正确, = 3  addTest(1,2)(new Adder[Int] {

          override def add(x: Int, y: Int): Int = x-y

        })  // 同样正确, = -1}

    Adder是一个trait,它定义了add抽象方法要求子类必须实现。

    addTest函数拥有一个Adder[Int]类型的隐式参数。

    在当前作用域里存在一个Adder[Int]类型的隐式值implicit val a。

    在调用addTest时,编译器可以找到implicit标记过的a,所以我们不必传递隐式参数而是直接调用addTest(1,2)。而如果你想要传递隐式参数的话,你也可以自定义一个传给它,像后两个调用所做的一样。

    四、隐式值和隐式对象

    最开始的示例代码有,

    隐式值:

    implicit val x: Int = 0

    隐式对象:

    implicit object obj {

    }

    上面提到过,在调用含有隐式参数的函数时,编译器会自动寻找合适的隐式值当做隐式参数,而只有用implict标记过的值、对象、函数才能被找到。

    例如自动寻找隐式对象:

        implicit object Obj {

          def hello(s:String) = println(s"Hello $s!")

        }

        def test2(s:String)(implicit o: Obj.type ) = {

          o.hello(s)

        }

      test2("world")  // 输出Hello world!

    自动寻找隐式函数:

        implicit def int2str(x: Int): String = x.toString

        def test1(x: Int, func: String => Unit)

                (implicit helper: Int => String) = {

          func("\"" + helper(x) + "\"")

        }

        test1(12, println) // 打印出"12"

    五、context bound

    最后来看隐式作为泛型类型的限制:

    def compare2[T: Ordering](x: T, y: T) = {

      val ord = implicitly[Ordering[T]]

      ord.compare(x, y)

    }

    上面compare2是一个泛型函数,其有一个类型参数T,在这里T:Ordering对T类型做出了限制,要求必须存在一个Ordering[T]类型的隐式值,这种限制就叫做context bound。

    这其实是隐式参数的语法糖,它等价于:

    def compare2[T](x: T, y: T)(implicit ord:Ordering[T]) = {

      ord.compare(x, y)

    }

    注意到前面的函数体里用到了implicitly函数。这是因为在使用[T: Ordering]这样的类型限制时,我们没有能接触到具体的Ordering[T]类型的隐式值ord,这时候调用implicitly函数,就可以拿到这个隐式值,进而进行下一步操作了。没有这个函数的话,你需要这样写:

    def compare2[T: Ordering](x: T, y: T) = {

      def helper(implicit ord:Ordering[T]) == ord.compare(x, y)

      helper

    }

    隐式解析机制

    最后加一点比较深入一点的内容,看一下隐式值的寻找机制

    即编译器是如何查找到缺失信息的,解析具有以下两种规则:

    1 .首先会在当前代码作用域下查找隐式实体(隐式方法 隐式类 隐式对象) 

    2.如果第一条规则查找隐式实体失败,会继续在隐式参数的类型的作用域里查找

    类型的作用域是指与该类型相关联的全部伴生模块,一个隐式实体的类型T它的查找范围如下:

    (1)如果T被定义为T with A with B with C,那么A,B,C都是T的部分,在T的隐式解析过程中,它们的伴生对象都会被搜索

    (2)如果T是参数化类型,那么类型参数和与类型参数相关联的部分都算作T的部分,比如List[String]的隐式搜索会搜索List的 

    伴生对象和String的伴生对象

    (3) 如果T是一个单例类型p.T,即T是属于某个p对象内,那么这个p对象也会被搜索

    (4) 如果T是个类型注入S#T,那么S和T都会被搜索

    相关文章

      网友评论

          本文标题:scala implicit (隐式转换函数、隐式类、隐式参数、

          本文链接:https://www.haomeiwen.com/subject/suseiftx.html