第八章多态

多态是继数据抽象和继承之后的第三种基本特征.多态也叫(动态绑定,运行时绑定或者后期绑定)

8.1方法调用绑定

方法调用和同一个方法主体关联起来叫绑定.在调用执行前就绑定的称为前期绑定,面向过程编程的采用的默认方式就是前期绑定.例如c语言;java中除了static 和final 方法(private属于final方法)之外,其他的方法都是采用后期绑定的方式(动态绑定或者运行时绑定).

8.2.3可扩展性

以instrument为例.由于多态机制我们可以向系统中添加多个新的乐器类型,而不需要修改tune().良好的OOP程序中,大多数或者所有的方法都应该遵循tune()模型,只与基类接口对外公开方法)通信,这样的程序是可扩展的,因为可以从通用基类继承接口,从而修改成新的功能,而不需要修改基类的任何代码就可以实现.

package tinking_in_java.Polymorphism;
/**
 * Created by leon on 17-12-14.
 */
public enum Note {
    MIDDLE_C, C_SHARP, B_FLAT;
}

package tinking_in_java.Polymorphism;
/**
 * Created by leon on 17-12-14.
 */
public class Instrment {
    public void play(Note note) {
        System.out.println("play" + note);
    }
}

package tinking_in_java.Polymorphism;
/**
 * Created by leon on 17-12-14.
 */
public class Brass extends Instrment {
    @Override
    public void play(Note note) {
        System.out.println("Brass play" + note);
    }
}

package tinking_in_java.Polymorphism;
/**
 * Created by leon on 17-12-14.
 */
public class Wind extends Instrment {
    @Override
    public void play(Note note) {
        System.out.println("Wind play" + note);
    }
}

package tinking_in_java.Polymorphism;
/**
 * Created by leon on 17-12-14.
 */
public class Music {
    public void tune(Instrment instrment) {
        instrment.play(Note.MIDDLE_C);
    }
    public static void main(String[] args) {
        Music music = new Music();
        Wind wind = new Wind();
        music.tune(wind);
    }
}

8.2.4 :陷阱 不能重写(overriding) private 方法

package tinking_in_java.Polymorphism;
/**
 * Created by leon on 17-12-14.
 */
public class PrivateOverride {
    private void f() {
        System.out.println("private --");
    }
    public static void main(String[] args) {
        PrivateOverride privateOverride = new Deliver();
        privateOverride.f();
    }
}


package tinking_in_java.Polymorphism;
/**
 * Created by leon on 17-12-14.
 */
public class Deliver extends PrivateOverride {
    public void f() {
        System.out.println("public-----");
    }
}
//output –
private --

事实上PrivateOverride的f(),在子类Deliver是无法override的,Deliver中的f()是当做一个新方法,对于基类是无法知晓的.所以在PrivateOverride类里面调用的f(),只能是PrivateOverride本身的.

8.2.5 陷阱 域(成员变量)和静态方法

成员变量和静态方法是无法重写overriding

package tinking_in_java.Polymorphism;
/**
 * Created by leon on 17-12-14.
 */
public class Super {
    public int field = 0;
    public int getField() {
        return field;
    }
}
 package tinking_in_java.Polymorphism;
/**
 * Created by leon on 17-12-14.
 */
public class Sub extends Super {
    public int field = 1;
    public int getField() {
        return field;
    }
    public int getSurperField() {
        return super.getField();
    }
    public static void main(String[] args) {
        Super sub = new Sub();
        System.out.println("sub.field() " + sub.field);
        System.out.println("sub.getField() " + sub.getField());
    }
}
//---output
sub.field() 0
sub.getField() 1

任何访问域(成员变量)操作都是由编译器解析,所以不是多态的.事实上sub.field和super.field分配了不同的存储空间.Sub实际上包含了两个field域,一个自己的,一个是它从Super得到的.

8.3构造器和多态

构造器不具有多态性,也就是说构造器不能重写,必须是调用.实际上构造器是隐私的申明成static方法.基类的构造器总是在导出来的构造过程中被调用,按照继承的顺序逐层向上调用,以保证每个基类构造器都能得到调用.(最先构造的是最顶层的基类).

复杂对象调用构造器的顺序:

1.调用构造器,逐层递推.首先是构造基类的根类,然后往下一层导出类,最后是调用的直接调用的导出类.
2.按照顺序调用成员初始化方法
3.调用导出类的构造函器主体
如果遵守了这中顺序,那么在导出类的所有基类的成员都初始化了.但是遗憾这种做法不适用与所有的情况.

8.3.2 继承与清理

如果导出类overriding了基类的 自定义垃圾回收方法dispose(),必须在overriding的dispose()中调用super.dispose(),否则基类的dispose不会被执行.在进行垃圾清理时,必须先清理完导出类,在调用基类的dispose(),原因是所有对象清理的顺序都是按照构造时顺序的逆序进行的,这样才安全.

如果一个基类(或者被其他类多次调用的类)存在共享对象的情况(有static 对象),这时候情况变得比较复杂,必须在基类或者提供调用类本身进行引用计数器管理共享对象,然后进行释放.原因是共享对象不管创建多少实例,他都只会生成一分,如果在其他的需要垃圾回收的实例被释放了,很可能会导致剩下没有来得及垃圾回收的实例引用造成数据为空.
以下例子:

package tinking_in_java.share;
/**
 * Created by leon on 17-12-14.
 */
public class Share {
    private static String shareStr = "shareABC";
    private int refrence = 0;
    private static int count = 0;
    private int id = count++;
    public Share() {
        System.out.println("Creat" + this);
    }
    public void addRef() {
        refrence++;
    }
    @Override
    public String toString() {
        return "Share" + id;
    }
    public void printShare() {
        System.out.println(shareStr);
    }
    public void dispose() {
        if (--refrence == 0) {
System.out.println("readydispose" + shareStr.toString());
            shareStr = null;
        }
    }
}
package tinking_in_java.share;
/**
 * Created by leon on 17-12-14.
 */
public class Compose {
    private Share share;
    private static long count = 0;
    private final long id = count++;
    public Compose(Share share) {
        this.share = share;
        this.share.printShare();
        this.share.addRef();
    }
    @Override
    public String toString() {
        return "Compose" + id;
    }
    public void dispose() {
        share.dispose();
    }
    public static void main(String[] args) {
        Share share = new Share();
        Compose[] composes = {
                new Compose(share),
                new Compose(share),
                new Compose(share),
                new Compose(share)};
        for (Compose compose : composes) {
            compose.dispose();
        }
    }
}
//output------
CreatShare0
shareABC
shareABC
shareABC
shareABC
ready disposeshareABC

如果不进行引用判断,会抛出NPE异常.

8.3.3 构造器内部调用多态方法行为

构造器内部调用多态方法,会直接调用被覆盖后的方法.但是构造器的本质工作是创建对象,导出类在调用基类的构造器的时候,导出类其实还处于没有初始化状态,但是对于基类构造器里面动态绑定的方法确是可以调用导出类中的覆盖方法,这样的结果可能导致调用导出类中的overriding方法中的成员可能还没初始化.

package tinking_in_java.ConstructInner;
/**
 * Created by leon on 17-12-15.
 */
public class Super {
    public Super() {
        System.out.println("Super before draw");
        draw();
        System.out.println("Super atfer draw");
    }
    void draw() {
        System.out.println("Super draw");
    }
}

package tinking_in_java.ConstructInner;
/**
 * Created by leon on 17-12-15.
 */
public class Sub extends Super {
    String drawTxt;
    public Sub(String txt) {
        drawTxt = txt;
    }
    @Override
    void draw() {
        System.out.println("Sub draw " + drawTxt);
    }
    public static void main(String[] args) {
        Sub sub = new Sub("hello");
    }
}
//output----
Super before draw
Sub draw null
Super atfer draw

初始化的实际过程:

1.在其他任何事物发生之前,先分配给对象存储空间(进行定义),赋默认值.
2.调用基类的构造器
3.按照顺序调用成员的初始化方法
4.调用导出类的构造器主体

构造器有一条有效的准则:
用尽可能简单的方式使对象进入正常状态,如果可以的话,不要调用其他的方法.

8.5用继承进行设计

在创建新类的时候应该首先考虑组合方式,其实在考虑继承关系

你可能感兴趣的:(第八章多态)