用一个例子告诉你 什么是Scala中的apply方法和unapply方法

1. 说明

在scala的单例对象中,经常会定义下面两个方法

apply方法 :
         apply 一般作为 工厂方法来使用,将接收到的参数封装到对象实例中,并返回这个对象
         也经常称它为`注入`方法


unapply方法 :
          unapply 一般作为提取器使用,从给定的对象中 获取该对象的属性值
          也经常称它为`提取`方法

    /*
    * 1.实现 Function2特质(可选)
    *     在声明对象时,可以通过混入 Function2[参数1类型, 参数2类型, 返回值类型] 特质
    *     并 实现Function2中的apply方法
    * Function2[参数1类型, 参数2类型, 返回值类型] 等于 ((参数1类型, 参数2类型) => 返回值类型)
    * 
    * 2.要想 被match表达式识别 unapply方法,unapply的返回值类型必须是 Option[T] 或 Boolean
    * 
    * */
    //object EMail extends Function2[String, String, String] {
    object EMail extends ((String, String) => String) {
      // 注入方法
      def apply(user: String, domain: String) = s"$user @ $domain"

      // 提取方法
      def unapply(str: String): Option[(String, String)] = {
        val parts: Array[String] = str.split("@")
        if (parts.length == 2) Some(parts(0), parts(1)) else None
      }
    }

2. Scala编译器对 apply、unapply做的隐式转换

apply: 
      当 obj(参数列表) 编译器自动插入 obj.apply(参数列表)
unapply:
      当 obj(参数列表) 作为match表达式 的匹配模式时,编译器自动插入 obj.unapply(参数列表)

    object cPerson {
      def apply(s:String): String = "new String"
      def unapply(arg: String): Option[String] = Some("abc")
    }

    def matchVal(x:Any) = x match {
      case cPerson(a) => println(s"case1 : $a")
      case _ => println(s"case2")
    }

    matchVal("123")
    cPerson("123")

编译器 对 cPerson("123")  做的隐式转换

cPerson("123") 作为模式时,编译器做的隐式转换


3.  什么是提取器?

  在Scala中,将拥有unapply的成员方法的单例对象 称为提取器


4. 提取器在 match表达式中怎样使用?

在 match表达式中 提取器可以被作为一种模式来使用,常称为`提取器模式`
经常用提取器来拆解 被匹配的对象

4.1 提取器 在match表达式中的运行原理

如果case分支的 模式类型为提取器模式
   会先判断 判断对象的类型 是否和 提取器的参数类型 是否一致
   如果一致
       调用提取器,返回提取的结果
           对提取的结果有两种处理方式
               1.精准匹配
                    case 提取器(100,'a') 
                    返回的结果 Option(Int,Char) 与 (100,'a') 做比对
               2.绑定变量
                    case 提取器(x,y)
                    返回的结果 Option(Int,Char) 绑定在 x,y 上
                   
   如果不一致
       进入下一个分支

  test("提取器 在match表达式中的运行原理") {

    // 提取器: 提取 Int值对应的 字符
    object IntExtr {
      def unapply(i: Int): Option[(Int, Char)] = {
        println("unapply runing...")
        if (i > 0) Some((i, i.toChar)) else None
      }
    }

    def matchInt(i: Any) = i match {
      /*
      * TODO case1分支
      *    判断参数i 是否和IntExtr.unapply方法的 参数类型一致
      *        true: 调用 IntExtr.unapply(i) 并返回结果
      *              判断 返回结果 是否和 (100, 'd')一致
      *
      * */
      case IntExtr(100, 'd') => println("精准匹配")
      /*
      * TODO case2分支
      *    判断参数i 是否和IntExtr.unapply 参数类型一致
      *       true: 调用 IntExtr.unapply(i) 并返回结果
      *             返回结果 并将绑定到变量 x,y 上
      * */
      case IntExtr(x, y) => println(s"$x : $y")
      case _ => println("no match")
    }

    println("-----matchInt(100)----------------")
    matchInt(100)
    println("-----matchInt(\"10\")----------------")
    matchInt("10")
    println("-----matchInt(-1)----------------")
    matchInt(-1)
    println("-----matchInt(99)----------------")
    matchInt(99)
    
  }


4.2  提取0个或1个变量模式

unapply方法值为 Boolean或者Option 时,才能被match表达式识别

// 只做逻辑判断,不能提取变量
def unapply(s: String): Boolean

// 可以将 提取的结果 绑定到 模式设置的变量上
def unapply(s: String): Option[(参数类型1,参数类型2)]

  test("示例: 提取电子邮箱地址") {

    // EMail提取器: 用来将 xxx@yyy.com 拆分 (xxx,yyy.com)
    object EMail {
      // 注入方法
      //def apply(user: String, domain: String) = s"$user @ $domain"

      // 提取方法
      def unapply(str: String): Option[(String, String)] = {
        val parts: Array[String] = str.split("@")
        if (parts.length == 2) Some(parts(0), parts(1)) else None
      }
    }

    /*
    * TODO 场景1: 判断给定字符串 是否符合 电子邮箱格式
    *       如果符合 获取用户名和域名部分
    *       如果不符合 给出提示信息
    * */
    def matchEMail(x: Any) = x match {
      case EMail(user, domain) => println(s"$user AT $domain")
      case _ => println("not an email address")
    }

    matchEMail("zhangfei@baidu.com") // zhangfei AT baidu.com
    matchEMail("zhangfeishuguo.com") // not an email address


    // Twice提取器: 判断单词是否为叠词,并返回去重后的单词
    object Twice {
      def unapply(s: String): Option[String] = {
        val length = s.length / 2
        val half = s.substring(0, length)
        if (half == s.substring(length)) Some(half) else None
      }
    }

    /*
    * TODO 场景2: 判断给定字符串 是否符合 电子邮箱格式
    *            如果符合 再判断 用户名是否为叠词
    *       如果符合 获取去重后用户名和域名部分
    *       如果不符合 给出提示信息
    * */
    def matchEMailTwice(x: Any) = x match {
      case EMail(Twice(user), domain) => println(s"$user AT $domain")
      case EMail(user, domain) => println(s"$user is not reduplicated word")
      case _ => println("not an email address")
    }

    matchEMailTwice("didi@baidu.com") // di AT baidu.com
    matchEMailTwice("dida@baidu.com") // dida is not reduplicated word
    matchEMailTwice("zhangfeishuguo.com") // not an email address

    // 提取器: 用来判断单词是否为 大写
    object UpperCase {
      def unapply(s: String): Boolean = s.toUpperCase() == s
    }

    /*
    * TODO 场景3: 判断给定字符串 是否符合 电子邮箱格式
    *            如果符合 再判断 用户名是否为叠词
    *            如果符合 再判断 用户名是否为大写
    *       如果符合 获取去重后用户名和域名部分
    *       如果不符合 给出提示信息
    * */
    def matchEMailTwiceUpperCase(x: Any) = x match {
      case EMail(Twice(x@UpperCase()), domain) => println(s"match: $x in domain $domain")
      case EMail(Twice(user), domain) => println(s"$user is not UpperCase")
      case EMail(user, domain) => println(s"$user is not reduplicated word")
      case _ => println("not an email address")
    }

    matchEMailTwiceUpperCase("DIDI@baidu.com") // match: DI in domain baidu.com
    matchEMailTwiceUpperCase("didi@baidu.com") // di is not UpperCase
    matchEMailTwiceUpperCase("dida@baidu.com") // dida is not reduplicated word
    matchEMailTwiceUpperCase("zhangfeishuguo.com") // not an email address


  }

4.3 提取可变长度参数的模式

Scala允许我们定义另一个不同的提取方法专门处理变长参数匹配,这个方法叫作unapplySeq
unapplySeq的结果类型必须符合Option[Seq[T]]的要求,其中元素类型T可以为任意类型

  test("提取可变长度参数的模式") {
    // 提取器: 将字符串 按.分割 返回切割后的对象
    object Domain {
      def unapplySeq(s: String): Option[Seq[String]] =
        Some(s.split("\\.").toSeq)
    }

    def matchDomain(s: String) = s match {
      case Domain("baidu", "com") => println("baidu.com")
      case Domain(x, y, z) => println(s"$x.$y.$z")
      case Domain("com",_*) => println(s"com.*")
      case _ => println("no match")
    }

    matchDomain("baidu.com") // baidu.com
    matchDomain("didi.baidu.com") // didi.baidu.com
    matchDomain("com.cn.deu.org") // com.*
  }

思考: 序列模式的实现原理

序列模式示例:
  case List(1, 2, 3) => println("case1: " + x) // 精准匹配
  case List(_, _, _) => println("case2: " + x) // 按 list 长度匹配
  case List(0, _*) => println("case3: " + x) // 按 list 开头元素匹配

我们之所以 能在match表达式中 匹配List或者Array的实例,是因为 Scala.List 的伴生对象 实现了 unapplySeq方法
        val someInts: Option[List[Int]] = List.unapplySeq(1 :: 2 :: Nil)


5. 对比提取器和样例类 

样例类的缺点:
    1.它们将数据的具体表现类型暴露给了使用方。这意味着构造方法模式中使用的类名跟选择器对象的具体表现类型相关
      也就是说 : 匹配变量 必须是 样例类的类型或者子类型
    2.样例类的apply方法和unapply方法逻辑是固定的

提取器打破了数据表现和模式之间的关联,提取器支持的模式跟被选择的对象的数据类型没有任何关系。这个性质被称作表现独立
    也就是说 : 匹配变量 提取器的类型没有强相关,只和提取器的参数类型一致即可

样例类的优点:
    1.使用简单,需要些的代码量还要少
    2.效率比提取器高高效(scala编译器对样例类的优化较好)

  test("对比提取器和样例类") {
    /*
    * TODO : 提取器
    *    1. unapply 逻辑自由
    *    2. match表达式的 匹配变量类型 只需和 unapply参数类型一致
    * */
    object EMailExtr {
      // 提取方法
      def unapply(str: String): Option[(String, String)] = {
        val parts: Array[String] = str.split("@")
        if (parts.length == 2) Some(parts(0), parts(1)) else None
      }
    }

    /*
    * TODO : 样例类
    *   1. unapply 逻辑固定,不能修改
    *   2. match表达式的 匹配变量类型 必须是 样例类的类型或者子类型
    *   3. 效率比提取器高
    *
    * */
    case class EMailClass(user: String, domain: String)

    // match表达式 + 提取器
    def matchEMailExtr(x: Any) = x match {
      case EMailExtr(user, domain) => println(s"$user AT $domain")
      case _ => println("not an email address")
    }

    matchEMailExtr("zhangfei@baidu.com") // zhangfei AT baidu.com
    matchEMailExtr("zhangfeishuguo.com") // not an email address

    def matchEMailClass(x: Any) = x match {
      case EMailClass(user, domain) => println(s"$user AT $domain")
      case _ => println("not an email address")
    }

    matchEMailClass("zhangfei@baidu.com") // not an email address
    matchEMailClass(EMailClass("zhangfei", "baidu.com")) // zhangfei AT baidu.com
    matchEMailClass("zhangfeishuguo.com") // not an email address
  }

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值