[读书笔记]Java编程思想第8章之多态

Posted Spring-_-Bear

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[读书笔记]Java编程思想第8章之多态相关的知识,希望对你有一定的参考价值。

  1. 多态是继数据抽象和继承之后的第三种基本特征。多态通过分离做什么和怎么做,从另一角度将接口和实现分离开来。多态不仅能够改善代码的组织结构和可读性,还可以创建可扩展程序。“封装”通过合并特征和行为来创建新的数据类型;“实现隐藏”则通过将细节“私有化”把接口和实现分离开来;多态的作用则是消除类型之间的耦合关系。多态方法允许一种类型表现出与其它相似类型之间的区别,只要它们都是从同一基类导出而来的。
  2. 将一个方法调用同一个方法主体关联起来被称作绑定。若在程序执行前进行绑定(如果有的话,由编译器和连接程序实现),叫做前期绑定。所谓后期绑定:它的含义就是在运行时根据对象的类型进行绑定,后期绑定也叫做动态绑定或运行时绑定。后期绑定机制随编程语言的不同而有所不同,但不管怎样都必须在对象中安置某种“类型信息”。Java中除了static方法和final方法(private方法属于final方法)之外,其他所有的方法都是后期绑定。
  3. 一旦知道Java中所有方法都是通过动态绑定实现多态这个事实之后,我们就可以编写只与基类打交道的程序代码了。或者换一种说法,发送消息给某个对象,让该对象去判定应该做什么事。在编译时,编译器不需要获得任何特殊信息就能进行正确的调用。
  4. 在一个设计良好的OPP程序中,大多数或者所有方法都会基类接口通信。这样的程序是可扩展的,因为可以从通用的基类继承出新的数据类型,从而新添一些功能。那些操作基类接口的方法不需要任何改动就可以应用于新类。多态是一项让程序员“将改变的事物与未变的事物分离开来”的重要技术。
  5. 多态的缺陷:“覆盖私有方法”;“直接访问某个域”;“访问静态的方法”;只有普通的方法调用可以是多态的。任何域访问操作都将由编译器解析,因此不是多态的。如果某个方法是静态的,它的行为就不具有多态性。静态方法是与类,而并非与某个对象相关联的。
package thinkinginjava.charpenter8;

/**
 * @author Spring-_-Bear
 * @version 9/29/21 5:54 PM
 */
public class PrivateOverride {
    private void f(){
        System.out.println("private f()");
    }

    public static void main(String[] args) {
        PrivateOverride privateOverride = new Derived();
        privateOverride.f();
    }
}

class Derived extends PrivateOverride{
    public void f(){
        System.out.println("public f()");
    }
}
package thinkinginjava.charpenter8;

/**
 * @author Spring-_-Bear
 * @version 9/29/21 5:57 PM
 */
public class FieldAccess {
    public static void main(String[] args) {
        /**
         * Upcast
         */
        Super sup = new Sub();
        System.out.println("sup.field = " + sup.field + ", sup.getField() = " + sup.getField());

        Sub sub = new Sub();
        System.out.println("sup.field = " + sub.field + ", sup.getField() = " + sub.getField() + ", sum.getSuperField() = " + sub.getSuperField());

    }
}

class Super {
    public int field = 0;

    public int getField() {
        return field;
    }
}

class Sub extends Super {
    public int field = 1;

    @Override
    public int getField() {
        return field;
    }

    public int getSuperField() {
        return super.field;
    }
}
package thinkinginjava.charpenter8;

/**
 * @author Spring-_-Bear
 * @version 9/29/21 6:07 PM
 */
public class StaticPolymorphism {
    public static void main(String[] args) {
        /**
         * Upcast
         */
        StaticSuper sup = new StaticSub();
        System.out.println(sup.staticGet());
        System.out.println(sup.dynamicGet());
    }
}

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()";
    }

    @Override
    public String dynamicGet() {
        return "Derived dynamicGet()";
    }
}
  1. 构造器并不具有多态性(构造器实际是static方法,只不过该static声明是隐式的)。基类的构造器总是在导出类的构造过程中被调用,而且按照继承层次逐渐向上链接,以使每个基类的构造器都能得到调用。调用构造器要遵循下面的顺序:
    1) 调用基类的构造器。
    2) 按声明顺序调用成员的初始化方法。
    3) 调用导出类构造器的主体。
package thinkinginjava.charpenter8;

/**
 * @author Spring-_-Bear
 * @version 9/29/21 6:18 PM
 */
public class Sandwich extends PortableLunch {
    private Bread bread = new Bread();
    private Cheese cheese = new Cheese();
    private Lettuce lettuce = new Lettuce();

    public Sandwich() {
        System.out.println("Sandwich()");
    }

    public static void main(String[] args) {
        new Sandwich();
    }
}

class Meal{
    Meal() {
        System.out.println("Meal()");
    }
}

class Bread{
    Bread() {
        System.out.println("Bread()");
    }
}

class Cheese{
    Cheese() {
        System.out.println("Cheese()");
    }
}

class Lettuce{
    Lettuce(){
        System.out.println("Lettuce()");
    }
}

class Lunch extends Meal{
    Lunch() {
        System.out.println("Lunch()");
    }
}

class PortableLunch extends Lunch{
    PortableLunch() {
        System.out.println("PortableLunch()");
    }
}
  1. 动态绑定的调用是在运行时才决定的,因为对象无法知道它是属于方法所子的那个类,还是属于那个类的导出类。如果要调用构造器内部的一个动态绑定方法,就要用到那个方法被覆盖后的定义。然而,这个调用的效果可能相当难以预料,因为被覆盖的方法在对象完全构造之前就会被调用。这可能会造成一些难以预料的错误。构造器的工作实际是创建对象(这并非是一件平常的工作)。如果构造器只是在对象构造过程中的一个步骤,并且该对象所属的类是从这个构造器所属的类导出的,那么导出部分在当前构造器正在被调用的时刻仍然是未初始化的。
package thinkinginjava.charpenter8;

/**
 * @author Spring-_-Bear
 * @version 9/29/21 7:14 PM
 */
public class PolyConstructors {
    public static void main(String[] args) {
        new RoundGlyph(5);
    }
}

class Glyph {
    void draw() {
        System.out.println("Sup-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 radius) {
        this.radius = radius;
        System.out.println("RoundGlyph.RoundGlyph(), radius = " + radius);
    }

    @Override
    void draw() {
        System.out.println("Sub-RoundGlyph.draw(), radius = " + radius);
    }
}

初始化的实际过程:
1)在其他任何事物发生之前,将分配给对象的存储空间初始化为二进制的0;
2)如前所述那样调用基类构造器。此时,调用被覆盖后的draw()方法(要在调用RoundGlyph构造器之前调用),由于步骤1的缘故,此时radius为0;
3)按照声明的顺序调用成员的初始化方法;
4)调用导出类的构造主体。

  1. 编写构造器一条有效的准则:“用尽可能简单的方法使对象进入正常状态;如果可以的话,避免调用其它方法。”在构造器内唯一能够安全调用的那些方法就是基类中的final方法(也适用于private方法)。这些方法不能被覆盖,因此不会出现如此“离谱”的问题。
  2. Java SE5中添加了协变返回类型,它表示在导出类的被覆盖方法中可以返回基类方法的返回类型的某种导出类型。
package thinkinginjava.charpenter8;

/**
 * @author Spring-_-Bear
 * @version 9/29/21 7:29 PM
 */
public class CovariantReturn {
    public static void main(String[] args) {
        Mill mill  = new Mill();
        Grain grain = new Grain();
        System.out.println(grain);
        mill = new WheatMill();
        grain = mill.process();
        System.out.println(grain);
    }
}

class Grain{
    @Override
    public String toString() {
        return "Grain";
    }
}

class Wheat extends Grain{
    @Override
    public String toString() {
        return "Wheat";
    }
}

class Mill{
    Grain process() {
        return new Grain();
    }
}

class WheatMill extends Mill{
    @Override
    Wheat process() {
        return new Wheat();
    }
}
  1. 事实上,当我们使用现成的类来建立新类时,如果首先考虑使用继承技术,反倒会加重我们的设计负担,使事情变得不必要地复杂起来。一条通用的准则是:“用继承表达行为间的差异,并用字段表达状态上的变化”。
  2. 向下转型与运行时类型识别(RTTI),RTTI的内容不仅仅包括转型处理。例如它还提供一种方法,使你可以在试图向下转型之前,查看你所要处理的类型。
package thinkinginjava.charpenter8;

/**
 * @author Spring-_-Bear
 * @version 9/29/21 7:43 PM
 */
public class RTTI {
    public static void main(String[] args) {
        Useful[] usefuls = {
                new Useful(),
                new MoreUseful()
        };

        usefuls[0].f();
        usefuls[1].g();
        /**
         * Downcast/RTTI
         */
        ((MoreUseful)usefuls[1]).u();
        /**
         * Exception thrown
         */
        ((MoreUseful)usefuls[0]).u();
    }
}

class Useful {
    public void f() {
    }

    public void g() {
    }
}

class MoreUseful extends Useful {
    @Override
    public void f() {
    }

    @Override
    public void g() {
    }

    public void u() {
    }

    public void v() {
    }

    public void w() {
    }
}
  1. 如果不运用数据抽象和继承,就不可能理解或者甚至不可能创建多态的例子。为了有效地运用多态乃至面向对象技术,必须拓展自己的编程视野,使其不仅包括个别类的成员和消息,而且还要包括类与类之间的共同特征以及它们之间的关系。

练习1: 创建一个Circle类,它具有子类Unicycle、Bicycle和Tricycle。演示每一个类型的实例都可以经由ride()方法向上转型至Cycle。

package thinkinginjava.charpenter8;

/**
 * @author Spring-_-Bear
 * @version 9/29/21 7:59 PM
 */
public class Biking {
    public static void ride(Cycle cycle) {
        cycle.travel("" + cycle);
    }

    public static void main(String[] args) {
        Unicycle unicycle = new Unicycle();
        Bicycle bicycle  = new Bicycle();
        Tricycle tricycle = new Tricycle();
        ride(unicycle);
        ride(bicycle);
        ride(tricycle);
    }
}

class Cycle {
    public void travel(String s) {
        System.out.println("Cycle.travel " + s);
    }
}

class Unicycle extends Cycle {
    @Override
    public String toString() {
        return "Unicycle";
    }
}

class Bicycle extends Cycle {
    @Override
    public String toString() {
        return "Bicycle";
    }
}

class Tricycle extends Cycle {
    @Override
    public String toString() {
        return "Tricycle";
    }
}

练习2: 在几何图形的示例中添加@Override注解。

package thinkinginjava.charpenter8;

import java.util.Random;

/**
 * @author Spring-_-Bear
 * @version 2021/09/28 7:44 PM
 */
public class Shapes {
    private static RandomShapeGenerator generator = new RandomShapeGenerator();

    public static void main(String[] args) {
        Shape[] shapes = new Shape[9];
        /**
         * Fill up the array with Shape references
         */
        for (int i = 0; i < shapes.length; i++) {
            shapes[i] = generator.next();
        }

        for (Shape shp : shapes) {
            shp.draw();
        }
    }
}

class Shape {
    public void draw() {
    }

    public void erase() {
    }
}

class Circle extends Shape {
    @Override
    public void draw() {
        System.out.println("Circle.draw()");
    }

    @Override
    public void erase() {
        System.out.println("Circle.erase()");
    }
}

class Square extends Shape {
    @Override
    public void draw() {
        System.out.println("Square.draw()");
    }

    @Override
    public void erase() {
        System.out.println("Square.erase()");
    }
}

class Triangle extends Shape {
    @Override
    public void draw() {
        System.out.println("Triangle.draw()");
    }

    @Override
    public void erase() {
        System.out.println("Triangle.erase()");
    }
}

class RandomShapeGenerator {
    private Random random = new Random(55);

    public Shape next() {
        switch (random.nextInt(3)) {
            case 0:
                return new Circle();
            case 1:
                return new Square();
            case 2:
                return new Triangle();
            default:
        }
        
        return null;
    }
}

练习3: 在基类Shape.java中添加一个新方法,用于打印一条消息,但导出类中不要覆盖这个方法。请解释发生了什么。现在,在其中一个导出类中覆盖该方法,而在其它导出类中不要覆盖,观察又发生了什么。最后,在所有的导出类中覆盖这个方法。

package thinkinginjava.charpenter8;

import java.util.Random;

/**
 * @author Spring-_-Bear
 * @version 2021/09/28 7:44 PM
 */
public class Shapes {
    private static RandomShapeGenerator generator = new RandomShapeGenerator(以上是关于[读书笔记]Java编程思想第8章之多态的主要内容,如果未能解决你的问题,请参考以下文章

[读书笔记]Java编程思想第6章之访问权限控制

[读书笔记]Java编程思想第3章之操作符

[读书笔记]Java编程思想第9章之接口

[读书笔记]Java编程思想第7章之复用类

[读书笔记]Java编程思想第5章之初始化与清理

[读书笔记]Java编程思想第2章之一切都是对象