多态不仅可以改善代码的组织结构和可读性,还可以创建可扩展程序
多态允许从同一基类导出的多种类型看成同一类型来处理,同一份代码可以无差别的运行在这些不同类型的之上了

向上转型

  1. public enum Note {
  2. MIDDLE_C,C_SHARP,B_FLAT;
  3. }
  4. class Instrument{
  5. public void play(Note n){
  6. System.out.println("Instrument.play");
  7. }
  8. }
  9. class Flute extends Instrument{
  10. public void play(Note n){
  11. System.out.println("flute play " + n );
  12. }
  13. }
  14. public class Wind extends Instrument{
  15. public void play(Note n){
  16. System.out.println("Wind.play" + n);
  17. }
  18. }
  19. class Flute extends Instrument{
  20. public void play(Note n){
  21. System.out.println("flute play " + n );
  22. }
  23. }
  24. public class Wind extends Instrument{
  25. public void play(Note n){
  26. System.out.println("Wind.play" + n);
  27. }
  28. public class Music {
  29. public static void tune(Instrument i){
  30. i.play(Note.B_FLAT);//这个方法接受一个Instrument的引用,同时也能接受任何导出来自
  31. Instrument的类比如WindFlute
  32. 如果想让tune的方法接受一个Wind引用作为自己的参数,就需要为
  33. Instrument中所有的导出类都编写一个新的tune方法
  34. }
  35. public static void main(String[] args) {
  36. Wind wind = new Wind();
  37. tune(wind);
  38. Flute flute = new Flute();
  39. tune(flute);
  40. }
  41. }

由于多态的机制,我们可以根据自己的需求对系统添加任意多的新类型,而且不需要更改tune方法

方法绑定

将一个方法调用和一个方法主体关联起来叫做绑定,如果在程序执行之前进行绑定,叫做前期绑定,但如果只有一个Instrument的话,编译器无法知道调用的是那个方法
后期绑定,在运行的时候根据对象的类型进行绑定
Java除了static和final方法(private是隐式的final)的时候,其他的方法都是后期绑定
将某个方法定义为final,可以防止其他人覆盖该方法,这样可以有效的关闭动态绑定
image.png

  1. public class Shape {
  2. public void draw(){}
  3. public void erase(){} //为所有从这里继承而来的导出类建立了一个公共接口
  4. }
  5. public class Circle extends Shape{
  6. public void draw(){
  7. System.out.println("Circle draw() ---");
  8. }
  9. public void erase(){
  10. System.out.println("Circle erase() ---");
  11. }
  12. }
  13. public class Square extends Shape{
  14. public void draw(){
  15. System.out.println("Square draw() ---");
  16. }
  17. public void erase(){
  18. System.out.println("Square erase() ---");
  19. }
  20. }
  21. public class Triangle extends Shape{
  22. public void draw(){
  23. System.out.println("Triangle draw() ---");
  24. }
  25. public void erase(){
  26. System.out.println("Triangle erase() ---");
  27. }
  28. }
  29. public class RandomShapeGenerator {
  30. private Random rand = new Random(47);
  31. public Shape next(){
  32. switch (rand.nextInt(3)){
  33. default :
  34. case 0 : return new Circle();
  35. case 1 : return new Square();
  36. case 2 : return new Triangle();//每次调用next方法的时候,可以为随机选择的shape
  37. 引用产生一个引用,这个时候只能通过运行的时候
  38. 才能知道返回的具体类型是哪个类型
  39. }
  40. }
  41. }
  42. public class Shapes {
  43. private static RandomShapeGenerator r = new RandomShapeGenerator();
  44. public static void main(String[] args) {
  45. Shape[] s = new Shape[9];
  46. for (int i = 0 ; i < s.length ;i ++){
  47. s[i] = r.next();
  48. }
  49. for (Shape shape : s) {
  50. shape.draw();//在编译的时候,编译器不需要获得任何的特殊信息就可以进行正确的调用
  51. draw方法的所有调用都是通过动态绑定进行的
  52. }
  53. }
  54. }

缺陷:覆盖私有方法

  1. class Derived extends PrivateOverride{
  2. public void f(){
  3. System.out.println("Public f()");
  4. }
  5. }
  6. public class PrivateOverride {
  7. private void f(){
  8. System.out.println("Private f()");//由于private方法被自动的认为是final方法,并且对
  9. 导出类来说是不可见的,所以Derived中的f方法是一个
  10. 全新的方法
  11. }
  12. public static void main(String[] args) {
  13. PrivateOverride po = new Derived();
  14. po.f();//只有非Private方法才可以被覆盖,在导出类中对于基类中的private方法,最好采取
  15. 不同的名字
  16. }
  17. }

缺陷:域与静态方法

  1. class Super{
  2. public int field = 0;
  3. public int getField(){return field;}
  4. }
  5. class Sub extends Super{
  6. public int field = 1;
  7. public int getField(){return field;}
  8. public int gerSuperField(){return super.getField();}
  9. }
  10. public class FieldAccess {
  11. public static void main(String[] args) {
  12. Super sup = new Sub();
  13. System.out.println("sup.field = " + sup.field + " sup.getField = " + sup.getField());
  14. Sub sub = new Sub();
  15. System.out.println("sub.Field = " + sub.field + " sub.getField = " + sub.getField() + " sub.getSuperField = " +
  16. sub.gerSuperField());//想要的到基类的field的话,必须用super关键字来明确的指出
  17. }
  18. }
  1. class StaticSuper{
  2. public static String staticGet(){
  3. return "Base staticGet()";
  4. }
  5. public String DynamicGet(){
  6. return "Base dynamicGet()";
  7. }
  8. }
  9. class StaticSub extends StaticSuper{
  10. public static String staticGet(){
  11. return "Derived staticGet()";
  12. }
  13. public String DynamicGet(){
  14. return "Derived staticGet()";
  15. }
  16. }
  17. public class StaticPolymorphism {
  18. public static void main(String[] args) {
  19. StaticSuper sup = new StaticSub();
  20. System.out.println(sup.staticGet());//得到的是父类的静态方法,静态方法继承不过来
  21. System.out.println(sup.DynamicGet());
  22. }//静态方法是和类相关联的,并不是和某个实例相关联的
  23. }

构造器的调用顺序

基类的构造器总是在导出类的构造过程中被调用,而且是按照继承的层次关系依次从上到下,让每一个基类的构造器都都能得到调用,确保在进入导出类的构造器的时候,在基类中可供我们访问的成员都已经得到了初始化

  1. class Meal{
  2. Meal(){
  3. System.out.println("Meal()");
  4. }
  5. }
  6. class Bread{
  7. Bread(){
  8. System.out.println("Bread()");
  9. }
  10. }
  11. class Cheese{
  12. Cheese(){
  13. System.out.println("Cheese()");
  14. }
  15. }
  16. class Lettuce{
  17. Lettuce(){
  18. System.out.println("Lettuce()");
  19. }
  20. }
  21. class Lunch extends Meal{
  22. Lunch(){
  23. System.out.println("Lunch()");
  24. }
  25. }
  26. class PortableLunch extends Lunch{
  27. PortableLunch(){
  28. System.out.println("PortableLunch()");
  29. }
  30. }
  31. public class Sandwich extends PortableLunch{
  32. private Bread b = new Bread();
  33. private Cheese c = new Cheese();
  34. private Lettuce l = new Lettuce();
  35. public Sandwich(){
  36. System.out.println("Sandwich()");
  37. }
  38. public static void main(String[] args) {
  39. new Sandwich();
  40. }
  41. }//调用构造器的顺序:
  42. 1 调用基类的构造器
  43. 2 按照声明的顺序调用成员的初始化方法
  44. 3 调用导出类构造器的主体

继承和清理

和调用构造器的顺序相反,先清理导出类,后清理基类

构造器内部的多态方法的行为

  1. class Glyph{
  2. void draw(){
  3. System.out.println("Glyph draw()");
  4. }
  5. Glyph(){
  6. System.out.println("Before draw()");
  7. draw();//子类中重写了这个方法,调用的是子类中的方法
  8. System.out.println("After draw()");
  9. }
  10. }
  11. class RoundGlyph extends Glyph{
  12. private int radius = 1;
  13. RoundGlyph(int r){
  14. this.radius = r;
  15. System.out.println("RoundGlyph .radius = " + radius);
  16. }
  17. void draw(){
  18. System.out.println("RoundGlyph .radius = " + radius);
  19. }
  20. }
  21. public class PolyConstructors {
  22. public static void main(String[] args) {
  23. new RoundGlyph(5);
  24. }
  25. }//在基类中调用了draw方法中尚未初始化的radius
  26. 初始化的实际过程
  27. 1 在其他的任何事物发生之前,将分配给对象的存储空间初始化为0
  28. 2 调用基类的构造器,此时调用了被覆盖之后的draw方法,由于1,所以radius的值为0
  29. 3 按照声明的顺序调用成员初始化的方法
  30. 4 调用导出类的构造器的主体

在构造器中唯一能够安全调用的方法是基类中的final方法(private方法),这些方法不能被覆盖

用继承进行设计

  1. class Actor{
  2. public void act(){ }
  3. }
  4. class HappyActor extends Actor{
  5. public void act(){
  6. System.out.println("HappyActor");
  7. }
  8. }
  9. class SadActor extends Actor{
  10. public void act(){
  11. System.out.println("SadActor");
  12. }
  13. }
  14. class Stage{
  15. private Actor actor = new HappyActor();
  16. public void change(){
  17. actor = new SadActor(); //发生了改变
  18. }
  19. public void performPlay(){
  20. actor.act();
  21. }
  22. }
  23. public class Transmogrify {
  24. public static void main(String[] args) {
  25. Stage stage = new Stage();
  26. stage.performPlay();
  27. stage.change();
  28. stage.performPlay();
  29. }
  30. }

纯继承与扩展

image.png
继承可以确保所有的导出类都含有基类的方法,但是导出类中的扩展方法并不能别基类访问,所以一旦向上转型就不能调用这些新方法
image.png