Java编程思想笔记8——第八章:多态

8、多态

​ 在面向对象的程序设计语言中,有三大特性,分别是封装、继承、多态。

​ 多态分离了做什么怎么做两个概念,做什么对应着接口,而怎么做对应着实现

​ 多态可以改善代码的组织结构和可读性,还能够创建可扩展的程序。

​ 一个基类(父类)有许多不同的子类,在通过向上转型调用时,可以根据不同的子类,调用不同子类的方法。

8.1 再论向上转型

​ 基类(父类)的指针指向子类的对象,就称之为向上转型。

class Father{
    void speak(){
        System.out.println("我是父类")
    }
}

class Son extends Father{
   void speak(){
        System.out.println("我是子类")
    }
}

public class Test{
    public static void main(String[] args){
        Father f = new Son();
        f.speak();
    }
}
// 输出结果
// 我是子类

​ Son类接受了一个Father类的引用,这以上代码中speak方法刚好也是Father类的方法,所以并不会报错。且输出的是子类的方法。

8.1.1 忘记对象类型

class Father{
	void speak() {System.out.println("我是父类");}
}
class Son extends Father{
	void speak() {System.out.println("我是子类");}
	void play() {System.out.println("我是子类");}
}
public class TestString{	
	public static void main(String[] args){		
		Father f = new Son();
		f.play();//报错!!!
	}	
}

以上的代码会报错,因为Father类中并没有play方法。

所以作者让我们忘记对象类型,忘记他是个Son类,只需要知道他是个Father类,它只能执行Father类中的方法。

8.2 转机

Father f = new Son();
f.speak();

​ 编译器如何知道Father f指向的是Son对象,而不是其他的对象。实际上,编译器无法得知。

​ 为了深入理解这个问题,有必要研究一下绑定这个话题。

8.2.1 方法调用绑定

​ 将一个方法调用 与 一个方法主体 关联起来被称为绑定。即方法名执行的具体方法进行绑定。

​ 前期绑定时面向过程语言的默认绑定方式。

​ 后期绑定则意味着运行时根据对象的类型进行绑定。后期绑定又称动态绑定或运行时绑定。

​ Java中除了static方法和final方法(private方法属于final方法)之外,其他所有的方法都是后期绑定。

8.2.2 产生正确的行为

​ 一旦知道Java中所有方法都是通过绑定实现多态这个事实后,就可以编写只与基类打交道的程序代码了。

class Shape{
	public void draw(){}    
}

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

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

public class Test{
    public static void main(String[] args){
        Shape a = new Circle();
        Shape b = new Square();
        a.draw();
        b.draw();
    }
}
// 输出结果
// Circle
// Square

8.2.3 可扩展性

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

8.2.4 缺陷:无法“覆盖”私有方法

​ 对于基类(父类)的私有方法,子类是无法通过多态进行覆盖的。


public class Father{
    private void f(){ 
    	System.out.println("私有的f方法");
    }
    
    public static void main(String[] args){
        Father t = new Son();
        t.f();
    }
}

class Son extends Father{
    public void f(){
        System.out.println("公有的f方法");
    }
}
// 输出结果
// 私有的f方法
### 8.2.5 缺陷:域与静态方法

​ 只有普通方法可以多态

​ 对象的属性,对象的静态方法 是不可以进行多态的

class Super{
    public int field = 0;
    public static int f(){ return 0;}
}
class Sub extends Super{
    public int field = 1;
    public static int f(){ return 1;}
}
public class Test{
    public static void main(String[] args){
        Super s = new Sub();
        System.out.println(s.field);
        System.out.println(s.f());
    }
}
// 输出结果
// 0
// 0

8.3 构造器(构造函数)和多态

​ 构造函数不具有多态性,因为他实际上是static方法,只不过该static声明是隐式的。

​ 但是还是很有必要了解多态如何在复杂的层次中运作。

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

public class Sandwish extends PortableLunch{
    private Bread b = new Bread();
    private Cheese c = new Cheese();
    private Lettuce l = new Lettuce();
    public Sandwish(){System.out.println("Sandwich()");}
    public static void main(String[] args){
        new Sandwich();
    }
}
// 输出结果
// Meal()
// Lunch()
// PortableLunch()
// Bread()
// Cheese()
// Lettuce()
// Sandwich()

如上图所示,SandWich继承了PortableLunch,ProtableLunch继承了Lunch,Lunch继承了Meal。

执行顺序如下

(1)调用父类构造器,父类再调用父类,反复递归下去

(2)初始化当前类的成员

(3)调用当前类的构造方法

8.3.2 继承与清理

​ 通过组合和继承方式创建新类,不需要担心对象的清理问题。子类会留给垃圾回收器进行处理。

​ 如果必须要清理,就需要为类创建dispose()方法。需要重写基类的dispose()方法(其中的dispose是作者自己设定的,你也可以设计自己的名称)

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

​ 如果在构造函数中调用一个多态方法(动态绑定的方法),会发生什么?

​ 多态方法,一般是在运行时才可以决定具体的方法体,如果在构造方法中调用,会出现难以发现的错误。

​ 虽然不会报错,但会导致变量为设置初始值,从而出现意想不到的错误。

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

class RoundGlyph extends Glyph{
    private int radius = 1;
    RoundGlyph(int r){
        radius = r;
        System.out.println("RoundGlyph, radius="+radius);
    }
    void draw(){
        System.out.println("RoundGlyph, radius="+radius)
    }
}

public class Test{
    public static void main(String[] args){
        new RoundGlyph(5);
    }
}
// 输出结果
// Glyph before
// RoundGlyph, radius=0
// Glyph after
// RoundGlyph, radius=5

从以上的代码发现,radius从未设置为0,结果却输出为0,出现了意想不到的错误,所以不要在构造器中使用多态方法

8.4 协变返回类型

​ Java SE5中添加了协变返回类型,它表示在导出类(子类)中被覆盖方法可以返回基类(父类)方法的返回类型的某种子类类型。

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

class Mill{
    Grain process(){ return new Grain(); }
}
class WheatMill extends Mill{
    Wheat process(){ return new Wheat(); } //这是一个多态方法!!!,尽管返回值不一样,但是有继承关系
}

public class Test{
    public static void main(String[] args){
        Mill m = new Mill();
        Grain g = m.process();// 返回Grain类型
        System.out.println(g);
        m = new WheatMill(); 
        g = m.process(); // 返回Wheat类型,也是可以的。
        System.out.println(g);
    }
}
// 输出结果
// Grain
// Wheat

​ SE5之前的版本,子类覆盖process必须强制返回Grain,而不是Wheat。

​ SE5之后,解除了这个限制。

8.5 用继承进行设计

​ 在考虑使用继承还是组合的时候,请优先使用组合,因为组合更加的灵活。

​ 继承在编译的时候需要知道确切的类型。

8.5.1 纯继承与扩展

​ 采取纯粹的方式创建继承结构似乎是最好的方式。也就是说,只有在基类中已经建立的方法才可以在导出类中被覆盖。

​ 让继承的子类完全替代基类(父类),并且继承的子类不需要任何额外的信息,即不需要创建除了父类以外的任何方法。所以,存粹的is-a才是继承的最好选择。

8.5.2 向下转型与运行时类型识别

​ 由于向上转型会丢失具体的类型信息,所以需要通过向下转型来获得具体的类型信息。

class Father{
    public void f(){}
    public void g(){}
}
class Son{
    public void f(){}
    public void g(){}
    public void h(){}
}

public class Test{
    public static void main(String[] args){
        Father f1 = new Son();
        f1.f();//正常执行
        f1.h();//报错
        ((Son)f1).h(); //强制转换为Son类型之后,可以执行
    }
}

8.6总结

​ 多态意味着不同的形式。从基类继承而来的相同接口,以及使用该接口的不同形式,不同版本的动态绑定方法。

​ 多态是在继承和数据抽象的基础上实现的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值