美文网首页
Scala:abstract override

Scala:abstract override

作者: Azur_wxj | 来源:发表于2018-02-15 17:37 被阅读59次

    定义

    官方文档对于abstract override的定义是:

    The override modifier has an additional significance when combined with the abstract modifier. That modifier combination is only allowed for value members of traits.
    We call a member M of a template incomplete if it is either abstract (i.e. defined by a declaration), or it is labeled abstractand override and every member overridden by M is again incomplete.
    Note that the abstract override modifier combination does not influence the concept whether a member is concrete or abstract. A member is abstract if only a declaration is given for it; it is concrete if a full definition is given.

    这里面有三个要点:

    • 修饰符abstract override只能用于特质(Trait)的成员

    • 称类成员M是不完整的(incomplete),如果M是抽象的(如M只有声明没有定义),或者M被abstract override修饰,同时任何被M重载的成员也同样是不完整的。
      数学定义:令

      • overrided(x) 为被x重载的成员
      • 布尔函数 abstract(x)为x是抽象的(即只有定义)
      • 布尔函数 abstractOverride(x)为x是被abstract override
      • 布尔函数 incomplete(x)为x是不完整的,


      incomplete(M)=abstract(M)∨[ abstractOverride(M)∧incomplete( overrided(M))]

    • abstract override并不影响成员是具体的还是抽象的。如果成员只有声明则它就是抽象的,如果有具体定义,那它就是具体的。

    注意到M被abstract override修饰时,如果M重载的成员是具体的,那么M就不是 incomplete

    class Father{
        def name="Father"
    }
    trait Son extends Father{
        abstract override def name="Son"
    }
    val son=new Son{}
    son.name //"Son"
    

    上面代码中,特质Son继承了类Father,并且方法name被重载。尽管nameabstract override修饰,但是因为name重载的方法(即Fathername)并不是 incomplete ,所以特质Son的方法name也不是 incomplete
    这同时也说明了,尽管nameabstract override修饰,但是因为有具体定义,所以它是具体的而不是抽象的。

    另外注意到语句val son=new Son{}Son是特质不能被实例化,但是此处写法实际上类似于Java一样声明了一个匿名类,因为Son中没有抽象成员,所以花括号为空。

    多提一句,在官方文档中abstractoverride定义分别是:

    abstract
    The abstract modifier is used in class definitions. It is redundant for traits, and mandatory for all other classes which have incomplete members. Abstract classes cannot be instantiated **with a constructor **invocation unless followed by mixins and/or a refinement which override all incomplete members of the class. Only abstract classes and traits can have abstract term members.

    要点:

    • abstract只能修饰类,成员不能使用abstract
    • 特质没有必要声明abstract
    • 类中如果有不完整(incomplete)成员,则必须声明abstract
    • 抽象类不能实例化,除非被混入,且/或 被使用匿名类实现
    • 只有抽象类或特质才能有抽象成员

    override
    The override modifier applies to class member definitions or declarations. It is mandatory for member definitions or declarations that override some other concrete member definition in a parent class. If an override modifier is given, there must be at least one overridden member definition or declaration (either concrete or abstract).

    要点:

    • 如果被重载的方法是父类中有具体定义的方法,则override是必须的,否则可省略(例如,实现特质的抽象方法,就没有必要写override
    • 如果用override修饰,则必然存在一个被重载成员的定义或声明,无论是抽象还是定义

    说明

    abstract override应该兼具abstractoverride两种特性。
    首先,如果特质的超类中不存在M方法,则特质中定义M方法时不能使用abstract override,因为会违反override的定义。

    其次考虑下面的例子:

    abstract class Animal{
        def bark    //abstract method
    }
    trait Dog extends Animal{
        abstract override def bark=println("WoooW!")  //abstract override
    }
    new Dog{}.bark 
    
    error: object creation impossible, 
      since method bark in trait Dog of type => Unit 
      is marked `abstract' and `override', 
      but no concrete implementation could be found in a base class
           new Dog{}.bark
               ^
    

    发现,方法bark尽管有具体定义,但是编译器无法创建匿名类对象,原因是“特质中的方法barkabstract override修饰,但是无法在基类中找到具体的实现”。从这一点上讲,方法bark此时也具有抽象性(abstract)。

    这是符合定义的,因为显然Dog.barkabstract override修饰,且被重载方法Animal.barkincomplete ,从而 Dog.bark也是 incomplete的,自然会报错,即:

      incomplete(Dog.bark)
    =abstract(Dog.bark)∨[ abstractOverride(Dog.bark)∧incomplete( overrided(Dog.bark))]
    =False∨[ Trueincomplete( Animal.bark)]
    =False∨[ TrueTrue]
    =True

    为了解决这个问题,有两种:
    只要让 被重载方法Animal.bark不是 incomplete即可(incomplete( overrided(Dog.bark))==False):

    abstract class Animal{
        def bark=println("Emmm....")  //Now it's not incomplete
    }
    trait Dog extends Animal{
        //So method bark is also not incomplete
        abstract override def bark=println("WoooW!")
    }
    //the invocation makes sense
    new Dog{}.bark //WoooW!
    

    或者只要让重载方法Dog.bark不被abstract override修饰即可( abstractOverride(Dog.bark)==False):

    abstract class Animal{
        def bark    //abstract method
    }
    trait Dog extends Animal{
        override def bark=println("WoooW!")  //redundant modifier override
    }
    new Dog{}.bark  //WoooW!
    

    以上只是极端情况的演示,但是实际上没有必要这么做。

    结论

    只有在满足下面条件的情况下,我们才应在trait 中定义的某一方法之前添加
    abstract 关键字:该方法调用了super 对象中的另一个方法,但是被调用的
    这个方法在该trait 的父类中尚未定义具体的实现方法。

    考虑下面的样板代码:

    abstract class AbstractSuper{
        def abstractMethod( Params ) : ReturnType
    }
    
    trait TraitSub extends AbstractSuper{
        def abstractMethod( Params ) :ReturnType ={  //implements the abstract method
            /**
            重载代码实现
            */
        }
    }
    

    如果重载代码实现逻辑中:

    • 不含有父类的 incomplete 方法:即没有super.method,或逻辑中有super.method但是super.method不是 incomplete 方法,则直接实现即可,不必加override修饰符,并且可以使用new TraitSub{}.abstractMethod来引用。
      注意
      • 这里的super.method指代父类中的任何 incomplete 方法,比如super.abstractMethod或是其他什么的。
      • 所谓“父类具有 incomplete 方法”,要么该方法就是抽象的(没有具体定义),要么其有定义,但是在定义内部包含了祖先类的 incomplete 方法,这是一个递归过程。
    • 含有父类的 incomplete 方法TraitSub.abstractMethod有具体定义,但是具体定义中又有类似super.abstractMethodincomplete方法,这样TraitSub.abstractMethod方法本身就不具体,此时必须使用abstract override来修饰,以此提醒编译器(和读者):尽管TraitSub.abstractMethod提供了方法体,但方法并没有完全实现

    为什么abstract override只能用来修饰特质的成员呢?假设可以用来修饰抽象类的方法,那么该方法本身就应该使用了父抽象类的抽象方法super.abstractMethod,但是这在设计逻辑上是说不通的:父抽象类的抽象方法本就是交由子类来实现的,表示抽象的通用行为,子类却又在实现逻辑内部调用父抽象类的抽象方法,这是毫无道理的。在这里,super就是指代父抽象类

    那么特质为什么能反过来使用父类的抽象方法呢,这似乎也在逻辑上说不通?因为特质有一个很特殊的性质:特质中的super动态绑定的,你应该注意到上面的两类情况讨论中,我并没有使用AbstractSuper.abstractMethod的写法而是写成super.abstractMethod,也就是说,尽管TraitSub继承了抽象类AbstractSuper,但是它的super并不指代父抽象类AbstractSuper,而是在运行过程中动态绑定,所以在此之前都是不定的,是抽象的。

    这样的性质使得特质变得可堆叠。考虑一个抽象类A,其内有抽象方法m,有一个具体类C继承了A并实现了方法m,另外有一个特质T也继承了A,并且m的实现内引用了super.m,故被标明abstract override
    现在假定有一个类P,它继承了C,设pP的实例,那么使用p.m实际就由C.m代理。现在,将特质T混入:class P extends C with T,那么使用p.m,根据特质的线性化(扁平化处理,类似python中的MRO),它将由T.m进行代理,而在其内部的super.m实际上绑定为C.m,因此p.m=>T.m ( C.m )
    试想如果有很多特质,它们对方法m具有多态性,那么按照不同顺序混入,super也就绑定不同的实例,可能就展现为p.m=>T1.m ( T2.m( ....Tn.m (C.m ))... ),通过将特质堆叠,使得方法变得更有选择性和层次感。

    特质的线性化顺序

    线性化算法
    (1) 当前实例的具体类型会被放到线性化后的首个元素位置处。
    (2) 按照该实例父类型的顺序从右到左的放置节点,针对每个父类型执行线性化算法,并将执行结果合并。(我们暂且不对AnyRef 和Any 类型进行处理。)
    (3) 按照从左到右的顺序,对类型节点进行检查,如果类型节点在该节点右边出现过,那么便将该类型移除。
    (4) 在类型线性化层次结构末尾处添加AnyRef 和Any 类型。
    如果是对值类(如Int、Short、Double等)执行线性化算法,请使用AnyVal 类型替代AnyRef 类型。

    例如,有如下继承关系(伪代码):

    class A
    trait B (A)
    trait C (A)
    trait D (A)
    trait E (C)
    trait F (C)
    class G (D,E,F,B)
    

    对G进行线性化:

    1. 当前实例的具体类型会被放到线性化后的首个元素位置处。
      【方法链】:G
    2. 按照其父类型的顺序从右到左的放置节点
      【方法链】:G B F E D
    3. 针对每个父类型执行线性化算法
      【方法链】:
      G B F E D
      G B A F C E C D A
    4. 按照从左到右的顺序,对类型节点进行检查,如果类型节点在该节点右边出现过,那么便将该类型移除。
      【方法链】:
      G B (A) F (C) E C D A
      G B       F       E C D A

    因此对G的线性化即(从左至右为):GBFECDA。方法链(super的绑定)也按照这个顺序进行。显然,根据特质不同的混入顺序,这个方法链也会不同。

    相关文章

      网友评论

          本文标题:Scala:abstract override

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