美文网首页
Scala School-1-基础(续)

Scala School-1-基础(续)

作者: 匿称也不行 | 来源:发表于2019-01-18 01:24 被阅读8次

    http://twitter.github.io/scala_school/zh_cn/basics2.html

    • apply方法

    apply方法给了类或者对象一个很方便的语法糖,我们在实例化对象时,看起来就像是在使用一个方法,比如:

    scala> class Foo {}
    defined class Foo
    
    scala> object FooMaker {
         |   def apply() = new Foo // 伴生类中定义apply
         | }
    defined module FooMaker
    
    scala> val newFoo = FooMaker() // 不需要new了,直接使用半生对象中的方法就能实例化
    newFoo: Foo = Foo@5b83f762
    

    或者这样:

    scala> class Bar {
         |   def apply() = 0 // 类中定义apply
         | }
    defined class Bar
    
    scala> val bar = new Bar
    bar: Bar = Bar@47711479
    
    scala> bar() // 可以直接使用,而不需要bar.apply
    res8: Int = 0
    

    apply是一种很方便的语法糖。

    • 单例对象

    在刚才的例子中,注意到Foo和FooMaker是不一样的名字,因此,如果希望直接用:

    Foo()
    

    这是不可能的,只能用FooMaker()。那如果希望更方便的话,将单例对象直接命名为类的名字就可以了。此时,单例对象也叫做该类的伴生对象。

    class Foo {}
    object Foo{
      def apply()=new Foo
    }
    Foo()
    

    伴生对象

    • 函数即对象
      这里的extend表明了继承关系,继承了一个叫做Fuction1[Int, Int]的东西。
    scala> object addOne extends Function1[Int, Int] {
         |   def apply(m: Int): Int = m + 1
         | }
    defined object addOne
    
    scala> addOne(1)
    res2: Int = 2
    

    这个Function1是一个trait,它有一个很方便的apply方法,如上所述。

    那么同样的,还可以这么做:

    case class TestFunc1() extends Function1[Int, Int]{
      def apply(i: Int): Int = i
    }
    scala> TestFunc1
    res45: TestFunc1.type = TestFunc1
    scala> TestFunc1()
    res46: TestFunc1 = <function1>
    scala> TestFunc1()(11)
    res47: Int = 11
    

    这里就要来解释一下到底这是在干什么了。我们还是以数学的方式比较。我们先看一下trait Function1是什么。

    trait Function1[-T, +R] extends AnyRef { self =>
      def apply(v1: T1): R
    
      ...
    }
    

    这个特质,表达的是一种单参数函数关系。就如同一个数学函数,

    Anything => Anything
    

    表达了一种对应关系,这是一种一元对应关系,比如:
    y = x + 1, 一元一次函数;
    y = x^2 + x + 1,一元二次函数;
    这都是一元对应关系。
    同时,因为这种关系并没有对定义域和值域有任何要求,表达的是一种抽象关系,于是我们用trait来表达。同时应该记得,trait也是用来给类增加一些特性的,比如之前说的,很多动物都能发声。Function1提供了一个特性是apply()。它的好处是,可以直接使用SomeClass(input)。
    这个特性反映在数学上,就是针对这种抽象的对应,如果我们希望将其落实到具体的函数上怎么办?落实,也就是将trait的抽象,反映到具体的类/对象上。
    trait有两个部分是可以具体化的。
    首先,Function1使用的是泛型,这个类型首先可以具体化。数学表达就是

    Int => Int
    

    我们希望得到抽象结构的子集,只考虑Int的情况。这个的解决办法是

    //数学上依然抽象的,这样的情况就是没指定类型的Function1;类形上有指定,为泛型
    abstract class TestG[K, V] extends Function1[K, V]
    //数学上抽象,因为没有定义函数本身
    abstract class TestAbs extends Function1[Int, Int]
    trait TestAbsT extends Function1[Int, Int]
    

    为什么这样?我们不能使用case class/class,因为Function1还有另外一个抽象的部分,apply方法。只有这个方法具体化了,我们才能将其实例化。反映到数学上,它的表达就是

    Int => Int
    y = x^2 + 2x + 1
    

    我们希望得到Int函数的子集,一个具体的一元二次函数。
    这个很具体了,我们于是可以

    case class TestIntFunc() extends TestAbs {
      def apply(i: Int): Int = i*i + 2 * i + 1
    }
    case class TestBi() extends Function1[Int, Int] {
      def apply(x: Int): Int = x*x + 2 * x + 1
    }
    

    这样,我们就得到了一个定义域/值域为[Int, Int],且具体的一元二次函数。小结一下这些概念的对应。

    定义域/值域不固定,抽象函数,Function1
    定义域/值域固定,抽象函数,继承自Function1的trait/abstract
    定义域/值域固定,具体函数,class/case class
    

    这里还有一个很容易混淆的东西,apply。
    我们知道case class是有伴随的object,定义了很方便的apply,让其可以当作CaseClass(item),类似函数一样使用,直接得到某个实例,
    而Trait也定义了一个apply。这两个apply有什么区别?联系?

    class TestBiClass extends Function1[Int, Int] {
      def apply(x: Int): Int = x*x + 2 * x + 1
    }
    

    case class的apply,是帮助其实例化从而建立一个新的obj,即省略了new CaseClass这一步的;
    Function1的apply,是帮助函数自动运行某种计算的,即省略了NewedClass.apply()而直接使用NewedClass()的。
    所以apply这个方法是一个特殊的方法,所有的类都可以建立自己的apply方法,从而省略.apply而直接使用SomeClass()。

    class TestApply{
      def apply(x: Int): Int = x*x + 2 * x + 1
    }
    

    当apply作用于case class时,这种省略是为了节约new这个步骤,直接得到obj。
    当apply作用于class,尤其是某种函数形成的class时,这种省略是为了进行某种运算,直接得到结果。
    同时应该知道,case class的apply,位于其伴随obj中,是自动生成的。class的apply则位于类的内部。那如果我们给class也加一个伴随obj,并添加apply方法,是不是就可以和case class一样了?当然!见前文的单例对象/伴生对象。
    接下来,我们从一个class到instance来看看发生了什么。

    class TestBiClass extends Function1[Int, Int] {
      def apply(x: Int): Int = x*x + 2 * x + 1
    }
    //这样是不行的,因为没有伴生对象
    scala> TestBiClass
    <console>:25: error: not found: value TestBiClass
           TestBiClass
    //生成了一个TestBiClass实例
    scala> val tb = new TestBiClass
    //这个实例是一个类型为Function1的函数。如果从对象角度理解,这是一个类型为TestBiClass的实例对象,它有一个方法为apply。
    res69: TestBiClass = <function1>
    //我们可以使用apply方法,就如所有其他的class的对象一样。实例对象使用类的方法。
    scala> tb.apply(2)
    res70: Int = 9
    //但因为apply方法的特性,我们可以省略掉它,直接使用
    scala> tb(2)
    res71: Int = 9
    //单纯从函数的角度理解,tb是一个函数,函数如何使用?f(x) = x^2+2x+1,f函数构建在定义域x之上。
    //那么要使用它,就是f(x)。TestBiClass是一个函数,定义在x这个整数类型之上,要用它就是TestBiClass(x)。
    //这就是另外一层含义了。
    scala> tb(2)
    res71: Int = 9
    

    同样的例子也可以用在case class身上。

    case class TestBi() extends Function1[Int, Int] {
      def apply(x: Int): Int = x*x + 2 * x + 1
    }
    //告诉我们TestBi是一个类(型)
    scala> TestBi
    res72: TestBi.type = TestBi
    //case class自带的apply帮我们完成了实例化,我们得到了一个TestBi实例。这个实例是一个Function1[Int, Int]类型的函数
    //理论上来说已经精确到Function1[Int, Int]上了。Function1[T, R]算是泛型的trait,这里具体化了。
    scala> TestBi()
    res73: TestBi = <function1>
    //这是一个类型为TestBi的类的实例化对象,因此我们可以使用该类的方法,有继承自Function1的apply方法。
    scala> TestBi().apply(2)
    res74: Int = 9
    //同样,因为apply方法的特性,我们可以省略掉它,直接使用
    scala> TestBi()(2)
    res75: Int = 9
    //这里关于对象和函数的具体解释和上面的例子是一样的。
    //如果我们查看一下TestBi()这个实例,我们就知道其真实的父类是什么
    scala> TestBi().isInstanceOf[Function1[Int, Int]]
    res78: Boolean = true
    scala> TestBi().isInstanceOf[TestBi]
    res79: Boolean = true
    
    • 异常

    这里可以看到,首先try-catch-finally,这个结构是面向表达式的,也就是说,它本身是有一个结果的,在这里是Int。
    同时,finally在最后被调用,且不是表达式的一部分,也就是说其结果并不会做为值被传递return。

    val result: Int = try {
      remoteCalculatorService.add(1, 2)
    } catch {
      case e: ServerIsDownException => {
        log.error(e, "the remote calculator service is unavailable. should have kept your trusty HP.")
        0
      }
    } finally {
      remoteCalculatorService.close()
    }
    

    相关文章

      网友评论

          本文标题:Scala School-1-基础(续)

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