zoukankan      html  css  js  c++  java
  • 特质trait

    特质(trait)是scala里代码服用的基础单元。特质封装了方法和字段的定义,并可以通过“混入”到类中重用它们。与类的继承时每个类都只能继承唯一的超类不同,类可以混入任意多个特质。特质的定义除了使用关键字trait之外,与类定义无异,如代码1-1

    代码1-1

    trait Bird {
      def fly = println("鸟飞翔")
      def singing
    }
    

     这个特质名为Fish,它没有声明超类,因此和类一样,有个默认的超类AnyRef。它定义了一个具体方法fly,也定义了一个抽象方法singing,等待被混入的类实现。一旦特质被定义了,就可以使用extends或with关键字,把它混入到类中,scala的混入特质并不是继承它们,将在本文的后面说明。

    模拟一个场景,一个机器人继承了人类,人会说话,而机器人想要继续继承鱼的游泳、和鸟的飞翔和唱歌,需要用到特质,如代码1-2,Rebot类最先开始继承Person类,接着混入Fish特质和Bird特质,因此,机器人除了能工作,还能模拟鸟飞翔

    代码1-2

    scala> class Person {
      def say = println("人说话")
    }
    
    trait Fish {
      def swim = println("鱼游泳")
    }
    
    trait Bird {
      def fly = println("鸟飞翔")
      def singing
    }
    
    class Rebot extends Person with Fish with Bird {
      def work = println("机器人工作")
      def singing = println("机器人唱歌")
    }
    defined class Person
    defined trait Fish
    defined trait Bird
    defined class Rebot
    
    scala> val rebot = new Rebot()
    rebot: Rebot = Rebot@3e44f2a5

    scala> rebot.work
    机器人工作 scala> rebot.fly 鸟飞翔 scala> rebot.singing 机器人唱歌 scala> val fish: Fish = rebot fish: Fish = Rebot@3e44f2a5 scala> fish.swim 鱼游泳

     另外,因为Rebot这个类混入了Fish特质,所以可以用Fish的变量去接收Rebot对象,特质有点类似Java的抽象类,可以声明抽象方法和具体方法,但是Java的一个类只能继承一个抽象类,而scala一个类却可以混入多个特质,另外,特质不能带有任何类参数。代码1-3是合法的,而代码1-4则会报错

    代码1-3

    class Cat(color: String, age: Int)
    

     代码1-4

    trait Cat(color: String, age: Int)
    

     如果想在特质里加入参数时,可如代码1-5这样做

    代码1-5

    scala> trait Cat {
      val color: String
      val age: Int
      def cry = println("小猫喵喵叫")
      def printColor = println("小猫的颜色是" + color)
      def printAge = println("小猫的年龄是" + age + "岁")
    }
    defined trait Cat

    scala> val cat = new {
          val color = "白色"
          val age = 1
        } with Cat
    cat: Cat = $anon$1@6bea52d4

    scala> cat.printColor
    小猫的颜色是白色

    scala> cat.printAge
    小猫的年龄是1岁

    scala> cat.cry
    小猫喵喵叫

    Ordered特质

    对象的比较是程序里常见的操作,比方定义一个有理数对象,用户可能调用<或者>=来判断两个对象之间的大小关系,如果没有Ordered特质之前,我们一般会像代码1-6这样做,Rational类是有理数类,类参数n代表分子,类参数d代表分母,私有成员g是n和d的最大公约数,我们会定义操作符来判断两个有理数对象的大小关系

    代码1-6

    scala> class Rational(n: Int, d: Int) {
      require(d != 0)
      private val g = gcd(n, d)
      val number = n / g
      val denom = d / g
      override def toString = number + "/" + denom
      private def gcd(a: Int, b: Int): Int = if (b == 0) a else gcd(b, a % b)
      def <(that: Rational) = number * that.denom < that.number * denom
      def >(that: Rational) = that < this
      def ==(that: Rational) = (number == that.number && denom == that.denom)
      def <=(that: Rational) = this < that || this == that
      def >=(that: Rational) = this > that || this == that
    }
    defined class Rational
    
    scala> val x = new Rational(1, 2)
    x: Rational = 1/2
    
    scala> val y = new Rational(1, 3)
    y: Rational = 1/3
    
    scala> x > y
    res11: Boolean = true
    

     通过定义符号,我们可以比较对象间的大小关系,但scala专门提供了一个特质解决这样的问题,这个特质就是Ordered,Ordered特质让你仅仅只实现一个方法compare,使你的类可以使用>、<、>=、<=全套的比较方法,如代码1-7

    代码1-7

    scala> class Rational(n: Int, d: Int) extends Ordered[Rational] {
      require(d != 0)
      private val g = gcd(n, d)
      val number = n / g
      val denom = d / g
      override def toString = number + "/" + denom
      private def gcd(a: Int, b: Int): Int = if (b == 0) a else gcd(b, a % b)
      def compare(that: Rational) = number * that.denom - that.number * denom
    }
    defined class Rational
    
    scala> val a = new Rational(1, 3)
    a: Rational = 1/3
    
    scala> val b = new Rational(1, 5)
    b: Rational = 1/5
    
    scala> a > b
    res12: Boolean = true
    
    scala> val c = new Rational(1, 2)
    c: Rational = 1/2
    
    scala> val d = new Rational(2, 5)
    d: Rational = 2/5
    
    scala> c < d
    res13: Boolean = false
    
    scala> val e = new Rational(1, 3)
    e: Rational = 1/3
    
    scala> val f = new Rational(1, 5)
    f: Rational = 1/5
    
    scala> e >= f
    res14: Boolean = true
    

     compare主要返回两个有理数对象做计算最后的结果,如果结果大于0则代表前一个对象大于后一个对象,如果结果小于0则代表前一个对象小于后一个对象,如果结果等于0代表两个对象相等

    特质用来做可堆叠的改变

    举个例子,思考一下对一个整数队列堆叠改动。队列有两种操作:put,把整数放入队列,和get,从队列取出整数,队列是先进先出的,因此get应该依整数进入队列时的顺序把它们取出来。

    假设有一个类实现了这样的队列,你可以定义特质执行如下的改动:

    • Doubling:把所有放入到队列的数字加倍。
    • Incrementing:把所有放入到队列的数字增值
    • Filtering:从队列中过滤掉负数

    这三种特质代表了改动,因为它们改变了原始队列的行为而并非定义了全新的队列。这三种同样也是可堆叠的。

    代码1-8为抽象的IntQueue类,put方法把整数添加到队列中,get方法返回并移除开头的整数,length返回队列的长度

    abstract class IntQueue {
      def get(): Int
      def put(x: Int)
      def length(): Int
    }
    

     代码1-9的BasicIntQueue类是抽象类IntQueue的实现

    代码1-9

    class BasicIntQueue extends IntQueue {
      private val buf = new scala.collection.mutable.ArrayBuffer[Int]
      def get() = buf.remove(0)
      def put(x: Int) = buf += x
      def length() = buf.length
    }
    

    代码1-10是运行时的样子:

     代码1-10

    scala> val queue = new BasicIntQueue
    queue: BasicIntQueue = BasicIntQueue@4fdfa676
    
    scala> queue.put(-1)
    
    scala> queue.put(2)
    
    scala> queue.put(3)
    
    scala> queue.length()
    res3: Int = 3
    
    scala> queue.get()
    res4: Int = -1
    
    scala> queue.get()
    res5: Int = 2
    
    scala> queue.get()
    res6: Int = 3
    

     现在用特质改变它的行为,代码1-11展示了过滤掉负数,Filtering做了两件事情,第一件是它定义了超类IntQueue,这个定义意味着它只能混入扩展了IntQueue,因此可以把Filtering混入到BasicIntQueue,第二件事情是特质在声明为抽象的方法中有一个super的调用。这种调用对于普通的类来说是非法的,执行时必然失败。然而对于特质来说,则能调用成功。因为特质的super调用是动态绑定的。特质Filtering的super调用将直到被混入另一个特质或类之后,有了具体的方法定义时才工作。

    这种安排对于实现可堆叠改动的特质来说是常常要用到的,为了告诉编译器你的目的,你必须在这种方法打上abstract override的标志。这种标识符的组合仅在特质成员的定义中被认可,在类中不行,它意味着特质碧玺混入某个具有期待方法的具体定义的类中。

    代码1-11

    trait Filtering extends IntQueue {
      abstract override def put(x: Int) = if (x >= 0) super.put(x)
    }
    

     代码1-12中,queue变量只是简单地指明了一个类并混入一个特质,可以用这样的形式来替代命名类,联系代码1-11和代码1-12,我们将-1、2、3依次放入队列,但最后返回的队列长度只有2,因为-1被过滤掉了

    代码1-12

    scala> val queue = new BasicIntQueue with Filtering
    queue: BasicIntQueue with Filtering = $anon$1@59221b97
    
    scala> queue.put(-1)
    
    scala> queue.put(2)
    
    scala> queue.put(3)
    
    scala> queue.length()
    res3: Int = 2
    
    scala> queue.get()
    res4: Int = 2
    
    scala> queue.get()
    res5: Int = 3
    

     我们再加入两个特质,如代码1-13

    代码1-13

    trait Doubling extends IntQueue {
      abstract override def put(x: Int) = super.put(2 * x)
    }
    
    trait Incrementing extends IntQueue {
      abstract override def put(x: Int) = super.put(x + 1)
    }
    

     代码1-14,我们可以看到,queue最长的长度为3,然而我们却放入了4个整数,两个负数两个正数,并且返回的数跟我们原先放入的数不同,越靠近右侧的特质越先起作用。Incrementing最先调用,对所有放入的数加1,接着就是Filtering,我们放入的-10+1后为-9,依旧小于0,所有我们放入的整数只有-1、2、3能到达Doubling,在到达Doubling,3个数经过前两个特质的转化后,变为0、3、4,经过Doubling后,每个数乘以2,为0、6、8,最后放入队列的就是0、6、8。

    代码1-14

    scala> val queue = new BasicIntQueue with Doubling with Filtering with Incrementing
    queue: BasicIntQueue with Doubling with Filtering with Incrementing = $anon$1@131fcb6f
    
    scala> queue.put(-10)
    
    scala> queue.put(-1)
    
    scala> queue.put(2)
    
    scala> queue.put(3)
    
    scala> queue.length()
    res10: Int = 3
    
    scala> queue.get()
    res11: Int = 0
    
    scala> queue.get()
    res12: Int = 6
    
    scala> queue.get()
    res13: Int = 8
    

     特质是一种继承多个类似于类的结构的方式,但是它与许多语言中的多重继承有很重要的差别。其中的一个尤为重要:super的解释。对于多重继承来说,super调用导致的方法调用可以在调用发生的地方明确决定。而对于特质来说,方法调用是由类和被混入到类的特质的线性化决定的。

  • 相关阅读:
    Android中通过进程注入技术修改系统返回的Mac地址 分类: Android 2014-12-15 21:11 2866人阅读 评论(10) 收藏
    OC学习篇之---总结和学习目录 分类: IOS 2014-12-14 21:30 2623人阅读 评论(2) 收藏
    OC学习篇之---单例模式 分类: IOS 2014-12-14 20:42 1498人阅读 评论(2) 收藏
    OC学习篇之---对象的拷贝 分类: IOS 2014-12-14 18:02 2128人阅读 评论(0) 收藏
    OC学习篇之---循环引用问题 分类: IOS 2014-12-14 17:24 2184人阅读 评论(0) 收藏
    OC学习篇之---数组对象的引用计数问题和自动释放池的概念 分类: IOS 2014-12-14 17:04 1415人阅读 评论(0) 收藏
    OC学习篇之---@property和@synthesize的使用 分类: IOS 2014-12-14 16:45 3112人阅读 评论(0) 收藏
    OC学习篇之---内存管理介绍和使用 分类: IOS 2014-12-14 16:16 1510人阅读 评论(1) 收藏
    OC学习篇之---谓词(NSPredicate) 分类: IOS 2014-12-14 10:39 2078人阅读 评论(0) 收藏
    OC学习篇之---通知(NSNotificationCenter) 2014-12-14 10:20 2430人阅读 评论(0) 收藏
  • 原文地址:https://www.cnblogs.com/baoliyan/p/6790049.html
Copyright © 2011-2022 走看看