Thinking in Java 第八章------多态(1)

概述

多态通过分离做什么和怎么做,从另一角度将接口和实现分离开来。多态不但能够改善代码的组织结构和可读性,还能够创建可扩展的程序——即无论在项目最初创建时,还是在需要添加新功能时都可以“生长”的程序。

  • 多态的作用是消除类型之间的耦合关系.
  • 多态方法允许一种类型表现出与其他相似类型之间的区别,只要它们是从同一基类导出.

8.1在论向上转型

对象既可以作为它自己本身的类型使用,也可以作为其基类型来使用.这种把某个对象的引用视为对其基类型的引用的做法被称为向上转型.
例如:

public enum Note {
    MIDDLE_C, C_SHARP, B_FLAT; // Etc.
} ///:~

乐器基类:

class Instrument {
  public void play(Note n) {
    print("Instrument.play()");
  }
}
 ///:~

Wind是一种Instrument:

public class Wind extends Instrument {
  // Redefine interface method:
  public void play(Note n) {
    System.out.println("Wind.play() " + n);
  }
} ///:~

音乐类:

public class Music {
  public static void tune(Instrument i) {
    // ...
    i.play(Note.MIDDLE_C);
  }
  public static void main(String[] args) {
    Wind flute = new Wind();
    tune(flute); // Upcasting
  }
} /* Output:
Wind.play() MIDDLE_C
*///:~
  • 在上述代码中,Music.tune(...)方法接受一个Instrument类型的对象,同时也接受任何继承自Instrument的类.
  • main()方法中传入一个Windtune(..)时,不需要任何类型转换,这是因为,WindInstrument继承而来,所以Instrument中的接口必定存在于Wind中;从Wind向上转型到Instrument可能会”缩小”接口,但是不是比Instrument的全部接口更窄.

8.1.1忘记对象类型

那么问题就来了,为什么所有人都故意“忘记”对象的类型呢?
似乎,直接让tune()方法去接收一个Wind类型更加的直观?
这样做,会导致一个十分严重的问题:需要为系统内Instrument的每种类型都编写一个新的tune(...)方法.
假设按照上述逻辑,加入Stringed(弦乐),Brass(管乐)这两种Instrument:

class Stringed extends Instrument {
  public void play(Note n) {
    print("Stringed.play() " + n);
  }
}

class Brass extends Instrument {
  public void play(Note n) {
    print("Brass.play() " + n);
  }
}

public class Music2 {
  public static void tune(Wind i) {
    i.play(Note.MIDDLE_C);
  }
  public static void tune(Stringed i) {
    i.play(Note.MIDDLE_C);
  }
  public static void tune(Brass i) {
    i.play(Note.MIDDLE_C);
  }
  public static void main(String[] args) {
    Wind flute = new Wind();
    Stringed violin = new Stringed();
    Brass frenchHorn = new Brass();
    tune(flute); // No upcasting
    tune(violin);
    tune(frenchHorn);
  }
} /* Output:
Wind.play() MIDDLE_C
Stringed.play() MIDDLE_C
Brass.play() MIDDLE_C
*///:~
  • 这样做是行得通的.但是有一个缺点:必须为每一个新Instrument类编写特定类型的方法.这意味着,在开始时,就需要更多的编程,这也意味着,如果以后想添加类似tune(...)的方法,或者添加自Instrument的导出类,任需要大量的工作.
  • 当我们写一个简单的方法,仅仅接收基类作为参数,而不是特殊的子类,这正是多态所允许的.

8.2转机

问题:

public static void tune(Instrument i) {
    i.play(Note.MIDDLE_C);
  }

wind发声,可以调用tune(wind)
该方法接收一个Instrument类型的引用,那么在这种情况下,编译器怎么才能知道,这个Instrument引用指向的是Wind对象而不是Brass对象呢?


首先我们要清楚几个概念:

  1. 将一个方法调用和一个方法主体关联起来的过程叫做绑定;
  2. 在程序执行之前进行绑定,叫做前期绑定;
  3. 在运行时根据对象的类型进行绑定,叫做后期绑定(动态绑定,运行时绑定);
  4. Java中出了staticfinal方法(private方法属于final方法)之外,其他所有方法都是后期绑定,并且自动发生;

答案就是:运行时绑定;编译器一直不知道对象的类型,但是在运行时会根据对象的类型动态的绑定,将方法的调用和对应的方法主体绑定起来.

举一个经典的例子:几何形状.

public class Shape {
  public void draw() {}
  public void erase() {}
} ///:~

public class Circle extends Shape {
  public void draw() { print("Circle.draw()"); }
  public void erase() { print("Circle.erase()"); }
} ///:~


public class Triangle extends Shape {
  public void draw() { print("Triangle.draw()"); }
  public void erase() { print("Triangle.erase()"); }
} ///:~


public class Square extends Shape {
  public void draw() { print("Square.draw()"); }
  public void erase() { print("Square.erase()"); }
} ///:~


public class RandomShapeGenerator {
  private Random rand = new Random(47);
  public Shape next() {
    switch(rand.nextInt(3)) {
      default:
      case 0: return new Circle();
      case 1: return new Square();
      case 2: return new Triangle();
    }
  }
} ///:~


----------------------
public class Shapes {
  private static RandomShapeGenerator gen =
    new RandomShapeGenerator();
  public static void main(String[] args) {
    Shape[] s = new Shape[9];
    // Fill up the array with shapes:
    for(int i = 0; i < s.length; i++)
      s[i] = gen.next();
    // Make polymorphic method calls:
    for(Shape shp : s)
      shp.draw();
  }
} /* Output:
Triangle.draw()
Triangle.draw()
Square.draw()
Triangle.draw()
Square.draw()
Triangle.draw()
Square.draw()
Triangle.draw()
Circle.draw()
*///:~
  1. RandomShapeGenerator是一种工厂,当我们每次调用next()方法时,它可以随机的为Shape对象产生一个引用.
  2. 向上转型发生在return语句中.每一个return语句取得一个指向某个Circle,Square,Triangle的引用,并且从next()方法中返回;
  3. 无论在什么时候调用next()方法,我们事先都不能预测具体是什么类型.
  4. 由输出信息可以得出,对数组中每个元素调用draw()方法时,与类型相关的特定行为会奇迹般的发生.

可以得出:在编译时,编译器不需要获得任何特殊信息就能进行调用.对draw()方法的所有调用都是通过动态绑定进行的.

8.2.1可扩展性

Instrument的基础上,进行扩展,直接上代码:

class Instrument {
  void play(Note n) { print("Instrument.play() " + n); }
  String what() { return "Instrument"; }
  void adjust() { print("Adjusting Instrument"); }
}

class Wind extends Instrument {
  void play(Note n) { print("Wind.play() " + n); }
  String what() { return "Wind"; }
  void adjust() { print("Adjusting Wind"); }
}   

class Percussion extends Instrument {
  void play(Note n) { print("Percussion.play() " + n); }
  String what() { return "Percussion"; }
  void adjust() { print("Adjusting Percussion"); }
}

class Stringed extends Instrument {
  void play(Note n) { print("Stringed.play() " + n); }
  String what() { return "Stringed"; }
  void adjust() { print("Adjusting Stringed"); }
}

class Brass extends Wind {
  void play(Note n) { print("Brass.play() " + n); }
  void adjust() { print("Adjusting Brass"); }
}

class Woodwind extends Wind {
  void play(Note n) { print("Woodwind.play() " + n); }
  String what() { return "Woodwind"; }
}   

public class Music3 {
  // Doesn't care about type, so new types
  // added to the system still work right:
  public static void tune(Instrument i) {
    // ...
    i.play(Note.MIDDLE_C);
  }
  public static void tuneAll(Instrument[] e) {
    for(Instrument i : e)
      tune(i);
  } 
  public static void main(String[] args) {
    // Upcasting during addition to the array:
    Instrument[] orchestra = {
      new Wind(),
      new Percussion(),
      new Stringed(),
      new Brass(),
      new Woodwind()
    };
    tuneAll(orchestra);
  }
} /* Output:
Wind.play() MIDDLE_C
Percussion.play() MIDDLE_C
Stringed.play() MIDDLE_C
Brass.play() MIDDLE_C
Woodwind.play() MIDDLE_C
*///:~
  1. 增添新方法what()返回一个String类型的引用.添加一个adjust()方法供每种乐器调音.
  2. main()中我们只需要将某
  3. 种引用置入orchestra数组中,就会自动向上转型到Instrument;
  4. tune()方法忽略周围代码的变化,依旧正常运行,这就是我们期望多态所具有的的特性.
  5. 换句话说,多态是一项让程序猿”将改变的事物与为改变的事物分离开来”的重要技术;

8.2.3缺陷

1.”覆盖”私有方法————————————————–

public class PrivateOverride {
  private void f() { print("private f()"); }//注意访问控制符
  public static void main(String[] args) {
    PrivateOverride po = new Derived();
    po.f();
  }
}

class Derived extends PrivateOverride {
  public void f() { print("public f()"); }
} /* Output:
private f()
*///:~

我们期望输出的是public f(),但是由于private的方法默认是final的,并且对子类是屏蔽的.因此在这种情况下,Derived类中的f()方法就是一个全新的方法

结论:只有非private方法才可以被覆盖,但是还需要注意覆盖private方法的现象,这时候,编译器不会报错,但是也不会按照我们所期望的来执行.

2.域————————————————–

谨记:只有普通的方法调用可以使多态的.

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
*///:~
  1. Sub对象转型为Super时,任何域访问操作都将由编译器解析,因此不是多态的.
  2. Super.fieldSub.field分配了不同的存储空间.
  3. Sub实际上包含两个称为field的域:它自己的,以及从Super得到的.
  4. 在引用Subfiled时产生的默认的域是其本身的.
  5. 若要得到Super.field,必须显示指明super.field

总结:成员变量的访问操作是在编译期进行的,成员方法的操作是在运行时进行的.

3.静态方法————————————————–

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()
*///:~

总结:静态方法是与类相关联,而非与单个对象相关联

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值