Scala继承和特质

继承

  • 继承是类的扩展
    • extends 是Scala中实现继承的保留字
    • 子类能够重写超类的成员(具有相同的名称和参数)
    • 单例对象同样能从类中继承,和类的继承语法相同
  • 重写
    • Scala中使用override保留字进行方法,字段的重写

    • override保留字实际使用类似与private,因此,其也可以写在类定义的参数中

      class A(override val a:String) extends B {...}
      
    • 子类的重写或修改Scala会检查其超类,但是,超类的修改并不会检查其子类

    • 重写包括字段和方法,但参数不同的方法可以不重写

      class A { def fun(m:String)={...}}
      
      class B extends A{ def fun ={...}}
      
      
      重写规则:
      • 重写 def

        用val :利用val能重写超类用没有参数的方法(getter)
        用def:子类的方法与超类方法重名
        用var:同时重写getter、setter方法,只重写getter方法报错

      • 重写val

        用val:子类的一个私有字段与超类的字段重名,getter方法重写超类的getter方法

      • 重写var

        用var:且当超类的var是抽象的才能被重写,否则超类的var都会被继承

        abstract class A {
          val a = 25   // 可在子类中用val重写
          var b = 15   // 不可在子类中用var重写,因为不是抽象的
          var c: Int   // 可在子类中用var重写
          def fun1     // 可在子类中用val重写
          def fun      // fun的getter方法,可在子类中用var重写
          def fun_     // fun的setter方法,用var重写setter后,还要用var重写此方法
          def fun3(m:Char)  // 可在子类中用def重写
        }
      
    • 子类中,def只能重写超类的def,val能重写超类的val或不带参数的def,var只能重写超类中抽象的var或者超类的getter/setter

  • 抽象

    • 不能被实例的类叫做抽象类
    • 抽象类的某个或某几个成员没有被完整定义,这些没有被完整定义的成员称为抽象方法或抽象字段
    • 用abstract保留字标记抽象类
    • 只要类中有任意一个抽象成员,必须使用abstract标记
    • 重写抽象方法、抽象字段不需要使用override保留字
    abstract class A {
    
    val name:Array[String]    //抽象的val,带有一个抽象的getter方法
    
    var num:Int       //抽象的var,带有抽象的getter/setter方法
    
    def sign  //没有方法体/函数体,是一个抽象方法
    
    }
    
    

  • 保护

    • 当一个类不希望被继承、拓展时,可在类声明前加上final保留字

      final class A {...}
      
    • 当一个类的某些成员不希望被重写时,可以在成员声明前加上final保留字

      class A { final def sign{...} }
      
    • 当超类中的某些成员需要被子类继承,又不想对子类以外成员可见时,在成员声明前加上protected保留字

       class A { protected def sign{...} }
      
    • protected[this],将访问权限定于当前对象,类似于private[this]

    • 类中protected的成员对其子类可见,对其超类不可见

  • 构造

    • 子类构造器的运行在超类构造器运行之后

    • 在超类的构造器中调用val声明的成员被子类重写后,返回值可能不正确:

      class A {
        val a = 10
        val arr = new Array[Int](a)
      }
      
      class B extends A {
        override val a = 3 
      }
      
      val obj = new A
      
      

      构造B对象前先执行A的构造器,变量a被初始化为10,类A为了初始化arr数组,需要调用变量a,但a被子类B进行了重写,但此时B的构造器还没被调用,所以变量a的值未被初始化,因而返回0,则arr就被初始化成长度为0的数组,A的构造器执行完毕,再执行B的构造器,此时变量a被初始化为7

      解决办法:

      1. 将val声明为final
      2. 在超类中将val声明为lazy
      3. 在子类中使用提前定义语法
      
      提前定义

      所谓的“提前定义”语法让你可以在超类的构造器执行之前初始化子类的val字段。需要将val字段放到extends关键字之后的一个块中,并后接with保留字

      class B extends {
        overried val a = 7
      } with A {...}
      

      注意:提前定义的等号右侧只能引用之前已有的提前定义,而不能使用类中其他的定义或方法。

特质

  • 在scala中,一个类只能继承一个父类,但可以扩展多个特质
  • 多重继承
    • Scala不支持多重继承,取而代之的是特质

    • 一个子类只能拥有一个超类,一个超类能拥有多个子类

    • Scala使用特质达到类似多重继承的效果

    • 一个类可以扩展自一个或多个特质,一个特质可以被多个类扩展

    • 特质能限制被什么样的类所扩展

      为什么不支持多重继承?
      • 若一个子类继承自不同的超类,不同的超类中同名成员子类不知如何处理
      • 多重继承产生菱形继承问题
      • 解决多重继承可能导致的问题消耗的资源远比多重继承产生的价值高
  • 使用特质
    • 特质是Scala里代码复用的基础单元,封装了方法和字段的定义

    • 特质的定义使用保留字trait,具体语法与类定义相似,除了不能拥有构造参数

      trait A { def fun(m:Int,n:Int)=if(m>=n) 1 }
      
    • 一旦特质被定义了,就可以混入到类中

      class B extends A {...}
      
    • 当要混入多个特质时,利用with保留字

      class D extends A with B with C {...}
      
    • 特质的成员可以是抽象的,而且,不需要使用abstract声明

    • 重写特质的抽象方法无需给出override

    • 多个特质重写同一个特质的抽象方法需给出override

    • 除了在类定义中混入特质以外,还可以在特质定义中混入特质,以及构造对象时混入特质

    // 定义特质时混入 
    trait B extends A {...}
    
    // 构造对象时混入
     val obj = new C with B
    
  • 特质构造
    • 特质的构造是有顺序的,从左到右
    • 构造器的构造顺序:
      • 超类---》父特质----》第一个特质---》第二个特质(父特质不重复构造)--- 》... ---》类
    class A extends B with C with D
    

    串接B,C,D特质并去掉重复项,右侧胜出

  • 特质应用

    特质的一个主要应用方面在于接口,根据类已有的方法自动为类添加方法

    • 利用特质实现富接口:
      • 构造一个具有少量抽象方法和大量基于抽象方法的具体方法的特质
      • 只要把特质混入类中,通过类重写抽象方法后,类便自动获得大量具体方法

    特质的另一个应用方面在于:为类提供可堆叠的改变(super保留字)

    • 当为类添加多个互相调用的特质时,从最后一个开始进行处理
    • 在类中super.fun() 这样的方法调用是静态绑定的,明确是调用它的父类的 fun() 方法
    • 在特质中写下了 super.foo() 时,它的调用是动态绑定的。调用的实现将在每一次特质被混入到具体类的时候才被决定
    • 特质混入的次序的不同其执行效果也就不同

你可能感兴趣的:(Scala继承和特质)