【Java编程思想笔记】第八章-多态

  • 多态通过分离做什么和怎么做,从另一角度将接口和实现分离开来,多态不但能够改善代码的组织结构和可读性,还能够创建可扩展的程序。

  • 前期绑定在编译时期(如C),后期绑定是在运行时期(即多态,C++与Java),多态得程序的后期扩展。

  • Java中除了static方法和final方法(private方法属于final方法),其他所有的方法都是后期绑定。这意味着通常情况下,我们不必判定是否应该进行后期绑定——它会自动发生。

  • final方法会关闭动态绑定。

  • 类的构造方法隐式的为static,它们实际上是static方法。

  • 如果父类构造函数抛出异常,子类构造函数一定要抛出,不能被捕获。

  • 覆写/重写父类方法时,子类方法异常处理有以下几种:

import java.io.IOException;
public class Parent {
       public void overwrite(int i) throws IOException {}
       public void overwrite() throws NullPointerException {}
}
import java.io.ObjectStreamException;
class SubSub extends Parent {
       /*
        * 父类抛出捕获型异常,子类却抛出运行时异常,这是可以,因为
        * 抛出运行时就相当于没有抛出任何异常
        */
       public void overwrite(int i) throws RuntimeException {}
       /*
        * 如果父类抛出的是非捕获型异常,则子类可以抛出任意非捕获型异
        * 常,没有扩大异常范围这一问题
        */
       public void overwrite() throws RuntimeException {}
}

class Sub2 extends Parent {
       //也可以不抛出任何异常
       public void overwrite(int i) {}   
       /*
        * 如果父类抛出的是非捕获异常,子类也可以不用抛出,这与父类为捕
        * 获型异常是一样的
        */  
       public void overwrite() {}
}
class Sub3 extends Parent {
       //如果抛出的是捕获型异常,则只能是IOException的子类
       //!!public void overwrite(int i) throws ObjectStreamException{}   
       /*
        * 如果父类抛出的是非捕获异常,子类就不能抛出任何捕获型异常,因
        * 为这样会扩大异常的范围
        */
       //!! public void overwrite() throws IOException {}
}
  • 组合更加灵活,因为它可以动态选择类型,运行时可以动态的修改具体的行为,而继承在编译时就知道确切类型。“用继承表达行为间的差异,并用字段(组合)表达状态上的变化。”下面实例中,两者都用到了:通过继承得到了两个不同的类,用于表达act()方法的差异;而Stage通过运用组合使用自己的状态发生变化。
//演员
class Actor {
  public void act() {}
}
//继承(is-a关系):HappyActor(喜剧演员)确实是一种Actor(演员)
class HappyActor extends Actor {
  public void act() { System.out.println("HappyActor"); }
}
//继承(is-a关系):HappyActor(悲剧演员)确实是一种Actor(演员)
class SadActor extends Actor {
  public void act() { System.out.println("SadActor"); }
}
//舞台
class Stage {
  private Actor actor = new HappyActor();//组合(has-a关系):舞台上有演员
  public void change() { actor = new SadActor(); }
  public void performPlay() { actor.act(); }
}
public class Transmogrify {
  public static void main(String[] args) {
    Stage stage = new Stage();
    stage.performPlay();
    stage.change();//运行过程中改变具体性为
    stage.performPlay();
  }
} 
/* Output:
HappyActor
SadActor
*///:~
  • private方法不能被覆写,即使子类覆写也不会起作用,根据调用它的引用类型来调用相应的方法,实质上private方法就是final方法,所以在编译期就已确定调用哪个方法。
public class PrivateOverride {
  private void f() { System.out.println("private f()"); }
  public static void main(String[] args) {
    PrivateOverride po = new Derived();
    po.f();
  }
}
class Derived extends PrivateOverride {
  public void f() { System.out.println("public f()"); }
} /* Output:
private f()
*///:~
  • 只有非静态的方法才有可能构造多态,属性成员(即使是public)与静态方法不会造成多态,即使用子类重写了这些属性成员与静态方法,因为这此是在编译期进行解析的,而不是在运行时间确定的。因此在调用属性成员与静态方法时只与调用它的引用类型相关。
class Super {
       public int field = 0;

       public int getField() {
              return field;
       }
}
class Sub extends Super {
       public int field = 1;

       public int getField() {
              return field;
       }

       public int getSuperField() {
              return super.field;
       }
}
public class FieldAccess {
       public static void main(String[] args) {
              Super sup = new Sub(); // Upcast
              System.out.println("sup.field = " + sup.field + ", sup.getField() = "
                            + sup.getField());
              Sub sub = new Sub();
              System.out.println("sub.field = " + sub.field + ", sub.getField() = "
                            + sub.getField() + ", sub.getSuperField() = "
                            + sub.getSuperField());
       }
} /*
* Output:
* sup.field = 0, sup.getField() = 1
* sub.field = 1, sub.getField() = 1, sub.getSuperField() = 0
*/// :~
  • 上面实例中,为Super.field和Sub.field分配了不同的存储空间。这样,Sub实际上包含两个称为field的属性成员:它自己的和它从Super类中得到的。因此在子类中要得到父类中的同名属性成员时,则要使用super.field来获取。
class StaticSuper {
  public static String staticGet() {
    return "Base staticGet()";
  }
  public String dynamicGet() {
    return "Base dynamicGet()";
  }
}
class StaticSub extends StaticSuper {
  public static String staticGet() {
    return "Derived staticGet()";
  }
  public String dynamicGet() {
    return "Derived dynamicGet()";
  }
}
public class StaticPolymorphism {
  public static void main(String[] args) {
    StaticSuper sup = new StaticSub(); // Upcast
    System.out.println(sup.staticGet());
    System.out.println(sup.dynamicGet());
  }
} /* Output:
Base staticGet()
Derived dynamicGet()
*///:~
  • 如果自己处理清理动作,则销毁的顺序应该和初始化顺序相反。对于字段,则意味着与声明的顺序相反(因为字段的初始化是按照声明的顺序进行的)。对于基类,应该首先对其子类进行清理,然后才是基类,这是因为子类的清理可能会调用基类中的某些方法,所以需要使用基类中的构件仍起作用而不应过早地销毁它们,这与C++中的析构函数形式是一样的。

  • 在父类的构造函数中调用多态方法时,可能会引发问题:

class Glyph {
       void draw() {
              System.out.println("Glyph.draw()");
       }

       Glyph() {
              System.out.println("Glyph() before draw()");
              draw();//在父类构造函数中调用多态方法
              System.out.println("Glyph() after draw()");
       }
}
class RoundGlyph extends Glyph {
       private int radius = 1;

       RoundGlyph(int r) {
              radius = r;
              System.out.println("RoundGlyph.RoundGlyph(), radius = " + radius);
       }

       void draw() {
              System.out.println("RoundGlyph.draw(), radius = " + radius);
       }
}
public class PolyConstructors {
       public static void main(String[] args) {
              new RoundGlyph(5);
       }
} /* Output:
Glyph() before draw()
RoundGlyph.draw(), radius = 0
Glyph() after draw()
RoundGlyph.RoundGlyph(), radius = 5
*///:~
  • 在构造函数内唯一能够安全调用的那些方法是基类中的final方法(当然也适用于private方法,它们自动属于final方法)。

  • 子类覆盖父类方法时,子类的返回类型可以是父类返回类型的某个子类型,但返过来不行。

  • 注,没有父子关系时返回类型要一样,如果允许子类返回类型是父类返回类型的子类型,则要求在J2SE5或以上版本
class Grain {}
class Wheat extends Grain {}

class Mill {
       Grain p() { return new Grain(); }
       Wheat f() { return new Wheat(); }
       float g() { return 0;}
}
class WheatMill extends Mill {
       Wheat p() { return new Wheat(); }

       //子类覆盖父类方法时返回类型不能比子类宽
       //!! Grain f() { return new Grain(); }

       //虽然int可以隐式的转换成float,但int不是float的子类,所以编译不能通过
       //!! int g() { return 0;}
}
  • 向上转型是安全的,因为基类不会具有大于子类的接口,因此,通过基类接口发送的消息保证都能被接;由于向下转型是不安全的,所以在Java语言中,所有向下转型都会得到检查!所以即使我们只是进行一次普通宾加括弧形式的类型转换,但在进入运行期时仍然会对其进行检查,以便保证它的确是我们希望的那种类型,如果不是,运行时就会抛出一个ClassCastException的类型转换异常,这种在运行期间对类型进行检查的行为称作“运行时类型识别”(RTTI),这种我们也可以通过编程的方式来保证,在后面反射章节我们会看到。

你可能感兴趣的:(java编程思想笔记)