第八章多态
第八章 多态
在面向对象程序设计语言中,多态是继承数据抽象和继承之后的第三种基本特征
多态通过 分离做什么 和 怎么做 ,从另一个角度将 接口 和 实现 分离开来
- 改善代码的组织结构和可读性
- 创建可扩展程序 (无论在项目最初创建时还是在需要添加新功能时都可以“生长”程序)
区别
-
封装 通过合并特征和行为来创建新的数据类型。
-
实现隐藏 则通过将细节 私有化 把接口和现实分离开来。
-
多态 的作用则是消除类型之间的耦合关系。方法调用允许一种类型表现出与其他类型相似类型之间的区别,只要它们都时从同一个基类导出来的。这种区别时根基方法行为的不同而表示出来的,虽然这些方法都可以通过同一个类型来调用
-
继承 允许将对象是为它自己本身的类型或者基类型来加以处理。(允许从基类型导出的视为同一类型处理,而一份代码也就无差别的允许在这些不同类型上了)
8.1 再论向上转型
对象既可以作为它自己本身使用,也可以作为它的基类型使用,而这种把某个对象的引用视为对其基类型的引用的做法被称作向上转型
8.1.1 忘记对象类型
在进行向上转型时,就会发生这种情况。我们只写一个简单的方法,它仅接收基类作为参数,而不是那些特殊的导出类。这正是多态所所允许的。
练习1
class Cycle {
private String name = "Cycle";
public static void travel(Cycle c) {
System.out.println("Cycle.ride() " + c);
}
public String toString() {
return this.name;
}
}
class Unicycle extends Cycle {
private String name = "Unicycle";
public String toString() {
return this.name;
}
}
class Bicycle extends Cycle {
private String name = "Bicycle";
public String toString() {
return this.name;
}
}
class Tricycle extends Cycle {
private String name = "Tricycle";
public String toString() {
return this.name;
}
}
public class No1biking {
public static void ride(Cycle c) {
c.travel(c);
}
public static void main(String[] args) {
Unicycle u = new Unicycle();
Bicycle b = new Bicycle();
Tricycle t = new Tricycle();
ride(u);
ride(b);
ride(t);
}
}
============================================================
Cycle.ride() Unicycle
Cycle.ride() Bicycle
Cycle.ride() Tricycle
8.2 转机
8.2.1 方法调用绑定
将一个方法调用同一个方法主体关联起来被称作绑定。
若在程序执行前进行绑定(由编译器和连接程序实现) 叫做 前期绑定(面向对象语言 不需要选择就默认绑定的方式)
运行时根据对象的类型进行绑定,后期绑定 叫做动态绑定或者运行后时绑定。(如果实现后期绑定,就必须具有某种机制,以便在运行时能判断对象的类型,从而调用恰当的方法,并加以调用。)
Java种 除了 static方法 和 final(private属于 final方法) 方法,其他所有方法都是 后期绑定 这意味着我们不用 判定是否进行后期绑定 会 自动发生
所以 **为什么 把 一种方法声明 为 final 呢 **
- 防止其他人覆盖方法
- 有效的关闭 动态绑定 告诉编译器 不需要再对他进行 动态绑定,编译器就可以为 final方法调用 生成更有效的代码。
然而 大多数这种情况下 对程序的整体性能 不会由 什么改变,所以最好根据设计来决定是否使用 finla 而不是试图提高效率的目的来使用
8.2.2 产生正确的行为
一旦知道 Java中所有方法都时通过 动态绑定实现 多态这个事实之后,我们就可以只与基类打交道的程序代码了,并且这些代码绑定代码对所有的导出类 都可以 正确运行。或者换一种方法,发生一个消息给某个对象,让该对象断定会发生什么。
经典例子 : 几何形状(shape) 直观 经常用的到
向上转型:
Shape s = new Circle();
创建一个 Ciecle对象,把它的引用 立即赋值给 Shape
假设调用一个基类方法 它已经再导出类里被覆盖
s.draw();
你可能再次 认为时 Shape 的 draw(), 毕竟是一个 Shape引用,但是时 Circle.draw()方法,怎么实现的呢? 由于 后期绑定 (多态)
在编译,编译器不需要获得任何特殊信息就能进行正确的调用。对draw()方法的所有调用都是通过动态绑定进行的
扩展
new Random(47)里面的47表示产生随机数的一个种子,nextInt(26)表示随机数的范围,种子和范围是相关联的,一个种子对应一个范围内的一个固定的随机数,如果不填种子,则会默认取当前时间的毫秒数作为种子来生成随机数。
练习二
import java.util.Random;
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 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 No2Shapes {
private static RandomShapeGenerator gen = new RandomShapeGenerator();
public static void main(String[] args) {
Shape[] s = new Shape[10];
// fill up the array wth shapes:
for(int i = 0; i < s.length; i++)
s[i] = gen.next();
// make polymorphic method calls:
for(Shape shp : s)
shp.draw();
}
}
==========================================================================
Triangle.draw()
Triangle.draw()
Square.draw()
Triangle.draw()
Square.draw()
Triangle.draw()
Square.draw()
Triangle.draw()
Circle.draw()
Square.draw()
练习3
a
Triangle.draw()
这里是基类
Triangle.draw()
这里是基类
Square.draw()
这里是基类
Triangle.draw()
这里是基类
Square.draw()
这里是基类
Triangle.draw()
这里是基类
Square.draw()
这里是基类
Triangle.draw()
这里是基类
Circle.draw()
这里是基类
Square.draw()
这里是基类
b
Triangle.draw()
这里是基类
Triangle.draw()
这里是基类
Square.draw()
这里是基类
Triangle.draw()
这里是基类
Square.draw()
这里是基类
Triangle.draw()
这里是基类
Square.draw()
这里是基类
Triangle.draw()
这里是基类
Circle.draw()
这里是Cricle类
Square.draw()
这里是基类
c
Triangle.draw()
这里是Triangle类
Triangle.draw()
这里是Triangle类
Square.draw()
这里是Square类
Triangle.draw()
这里是Triangle类
Square.draw()
这里是Square类
Triangle.draw()
这里是Triangle类
Square.draw()
这里是Square类
Triangle.draw()
这里是Triangle类
Circle.draw()
这里是Cricle类
Square.draw()
这里是Square类
练习5
class Cycle5 {
private String name = "Cycle5";
private int wheels = 0;
public static void travel(Cycle5 c) {
System.out.println("Cycle5.ride() " + c);
}
public int wheels() { return wheels; }
public String toString() {
return this.name;
}
}
class Unicycle5 extends Cycle5 {
private String name = "Unicycle";
private int wheels = 1;
@Override public int wheels() { return wheels; }
public String toString() {
return this.name;
}
}
class Bicycle5 extends Cycle5 {
private String name = "Bicycle";
private int wheels = 2;
@Override public int wheels() { return wheels; }
public String toString() {
return this.name;
}
}
class Tricycle5 extends Cycle5 {
private String name = "Tricycle";
private int wheels = 3;
@Override public int wheels() { return wheels; }
public String toString() {
return this.name;
}
}
public class No5Biking5 {
public static void ride(Cycle5 c) {
c.travel(c);
System.out.println("wheels: " + c.wheels());
}
public static void main(String[] args) {
Unicycle5 u = new Unicycle5();
Bicycle5 b = new Bicycle5();
Tricycle5 t = new Tricycle5();
ride(u); // upcast to Cycle
ride(b);
ride(t);
}
}
===================================================================
Cycle5.ride() Unicycle
wheels: 1
Cycle5.ride() Bicycle
wheels: 2
Cycle5.ride() Tricycle
wheels: 3
8.2.3 可扩展性
在 乐器方法 示例中,由于有多态机制。在一个 程序设计良好的 OOP 程序中,大多数或者所有方法都会遵循 tune()的模型 ,根据自己的需求对系统添加任意多的 新类型,而不需要更改 tune() 方法。
大多数方法都会 遵循 tune() 的 模型,而且只与基类 接口通信。这样的程序是 可扩展的,因为 可以从通用的基类继承出新的数据类型,从而新添一些功能。那些操控基类接口的方法不需要任何改动就可以应用于新类。
我们所做的代码修改,不会对程序中其他不应受到影响的部分产生破坏。
将改变事物与未改变事物分离开来
练习6
enum Note{
MIDDLE_C,
}
class Instrument {
void play(Note n) {
System.out.println("Instrument.play() " + n); }
public String toString() { return "Instrument"; }
void adjust() {
System.out.println("Adjusting Instrument"); }
}
class Wind extends Instrument {
void play(Note n) {
System.out.println("Wind.play() " + n); }
public String toString() { return "Wind"; }
void adjust() {
System.out.println("Adjusting Wind"); }
}
class Percussion extends Instrument {
void play(Note n) {
System.out.println("Percussion.play() " + n); }
public String toString() { return "Percussion"; }
void adjust() {
System.out.println("Adjusting Percussion"); }
}
class Stringed extends Instrument {
void play(Note n) {
System.out.println("Stringed.play() " + n); }
public String toString() { return "Stringed"; }
void adjust() {
System.out.println("Adjusting Stringed"); }
}
class Brass extends Wind {
void play(Note n) {
System.out.println("Brass.play() " + n); }
public String toString() { return "Brass"; }
void adjust() {
System.out.println("Adjusting Brass"); }
}
class Woodwind extends Wind {
void play(Note n) {
System.out.println("Woodwind.play() " + n); }
public String toString() { return "Woodwind"; }
}
public class No6Music {
// 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);
for(Instrument i : orchestra)
System.out.println(i);
}
}
==============================================================
Wind.play() MIDDLE_C
Percussion.play() MIDDLE_C
Stringed.play() MIDDLE_C
Brass.play() MIDDLE_C
Woodwind.play() MIDDLE_C
Wind
Percussion
Stringed
Brass
Woodwind
练习7
package 第八章多态;
class Keyboard extends Instrument {
void play(Note n) {
System.out.println("Keyboard.play() " + n); }
public String toString() { return "Keyboard"; }
void adjust() {
System.out.println("Adjusting Keyboard"); }
}
class Piano extends Keyboard {
void play(Note n) {
System.out.println("Piano.play() " + n); }
public String toString() { return "Piano"; }
}
public class No7Music {
// 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(),
new Piano()
};
tuneAll(orchestra);
for(Instrument i : orchestra)
System.out.println(i);
}
}
========================================================================Wind.play() MIDDLE_C
Percussion.play() MIDDLE_C
Stringed.play() MIDDLE_C
Brass.play() MIDDLE_C
Woodwind.play() MIDDLE_C
Piano.play() MIDDLE_C
Wind
Percussion
Stringed
Brass
Woodwind
Piano
练习8
import java.util.*;
class RandomInstrumentGenerator8 {
private Random rand = new Random();
public Instrument next() {
switch(rand.nextInt(7)) {
default:
case 0: return new Wind();
case 1: return new Percussion();
case 2: return new Stringed();
case 3: return new Keyboard();
case 4: return new Brass();
case 5: return new Woodwind();
case 6: return new Piano();
}
}
}
public class No8Music {
// 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);
}
private static RandomInstrumentGenerator8 gen = new RandomInstrumentGenerator8();
public static void main(String[] args) {
// Upcasting during addition to the array:
Instrument[] orchestra = new Instrument[5];
// fill up orchestra array wth instruments:
for(int i = 0; i < orchestra.length; i++)
orchestra[i] = gen.next();
tuneAll(orchestra);
for(Instrument i : orchestra)
System.out.println(i);
}
}
=================================================================
Brass.play() MIDDLE_C
Woodwind.play() MIDDLE_C
Keyboard.play() MIDDLE_C
Brass.play() MIDDLE_C
Piano.play() MIDDLE_C
Brass
Woodwind
Keyboard
Brass
Piano
练习9
import java.util.Random;
class RandomRodentGenerator9 {
private Random rand = new Random();
public Rodent next() {
switch(rand.nextInt(3)) {
default:
case 0: return new Mouse();
case 1: return new Rat();
case 2: return new Squirrel();
}
}
}
class Rodent {
private String name = "Rodent";
protected void eat() {
System.out.println("Rodent.eat()"); }
protected void run() {
System.out.println("Rodent.run()"); }
protected void sleep() {
System.out.println("Rodent.sleep()"); }
public String toString() { return name; }
}
class Mouse extends Rodent {
private String name = "Mouse";
protected void eat() {
System.out.println("Mouse.eat()"); }
protected void run() {
System.out.println("Mouse.run()"); }
protected void sleep() {
System.out.println("Mouse.sleep()"); }
public String toString() { return name; }
}
class Rat extends Rodent {
private String name = "Rat";
protected void eat() {
System.out.println("Rat.eat()"); }
protected void run() {
System.out.println("Rat.run()"); }
protected void sleep() {
System.out.println("Rat.sleep()"); }
public String toString() { return name; }
}
class Squirrel extends Rodent {
private String name = "Squirrel";
protected void eat() {
System.out.println("Squirrel.eat()"); }
protected void run() {
System.out.println("Squirrel.run()"); }
protected void sleep() {
System.out.println("Squirrel.sleep()"); }
public String toString() { return name; }
}
public class No9Rodent {
private static RandomRodentGenerator9 gen = new RandomRodentGenerator9();
public static void main(String[] args) {
Rodent[] rodents = new Rodent[5];
for(Rodent r : rodents) {
r = gen.next();
System.out.println(r + ": ");
r.eat();
r.run();
r.sleep();
}
}
}
==============================================================================
Squirrel:
Squirrel.eat()
Squirrel.run()
Squirrel.sleep()
Rat:
Rat.eat()
Rat.run()
Rat.sleep()
Rat:
Rat.eat()
Rat.run()
Rat.sleep()
Mouse:
Mouse.eat()
Mouse.run()
Mouse.sleep()
Rat:
Rat.eat()
Rat.run()
Rat.sleep()
加 Override 与 不加 Override 的 区别
java 中子类继承基类,可以重写基类中的方法,一般我们会定义 @Override 注解。
那么是否可以不加 @Override 呢?显然也可以!
那么,对于java中添加 Override 的区别在哪里呢?
写的情况下,表示子类覆盖基类中的方法,基类中必须存在该方法,控制器类型(public,protected,返回值,参数列表)与子类方法完全一致,否则会报错(找不到被 Override 的方法)
不写的情况下,JVM也会自动识别,如果不满足完全一致的条件,则被当作新方法定义
加Override的好处有利于编译器帮忙检测错误
练习10
class A {
protected void f() {
System.out.println("A.f()");
this.g();
}
protected void g() {
System.out.println("A.g()");
}
}
class B extends A {
@Override protected void g() {
System.out.println("B.g()");
}
}
public class No10Ex {
public static void main(String[] args) {
B b = new B();
// automatic upgrade to base-class to call base-class method A.f()
// which,by polymorphism, will call the derived-class method B.g():
b.f();
}
}
=======================================================================
A.f()
B.g()
8.2.4 缺陷: “覆盖”私有方法
Private 方法被自动认为使 final 方法,从而对导出类是屏蔽的。因此 导出类 如果覆盖 基类中的
private方法 那么其实并没有,会创建一个新方法。所以为了消除这种歧义,在导出类中最好不要与 基类中的 private 方法同名。
8.2.5 缺陷:“域于静态方法“
只有普通的方法调用可以是多态的
直接访问 域 ,这个访问就在编译期间进行解析,任何域访问 操作都将由编译器编译,因此不是多态的。
为 Super.field 和 sub.field 分配了不同的存储空间。
但 实际上 从来不会发生,**首先,你通常会将所有的域都设置成private ,因此不能之间访问它们,其副作用是只能调用方法访问 **
静态方法,它的行为就不具有多态性:
8.3 构造器和多态
构造器并没有多态(隐式的static方法),但是应该要理解构造器怎么通过多态在复杂的层次结构中运作
8.3.1 构造器的调用顺序
构造器 有一项特殊的任务:
检查对象是否被正确地构造。导出类只能访问它自己的成员,不能访问基类成员(基类成员通常是private类型)。只有基类的构造器才具有恰当的知识和权限来对自己的元素进行初始化。因此必须令所有的构造器都必须调用,否则不肯正确构造完整对象
- 调用基类构造器 ,从根开始 直到最底层
- 按声明顺序调用成员的初始化方法
- 调用导出类构造器主体
在构造器内部 我们必须确保所要使用的基类的所有成员都以构建完成,为确保这一目的,唯一的方法就是必须调用基类构造器。那么在进入导出类构造器是,在基类中可供我们访问的成员都已得到了初始化。
通过组合方法将对象置于类内,也应初始化当前对象的成员对象。(调用构造器,但这种方法不适用于所有情况)
练习11
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 Pickle {
Pickle() {
System.out.println("Pickle()"); }
}
class Lunch extends Meal {
Lunch() {
System.out.println("Lunch()"); }
}
class PortableLunch extends Lunch {
PortableLunch() {
System.out.println("PortableLunch()"); }
}
public class No11Sandwich11 extends PortableLunch {
private Bread b = new Bread();
private Cheese c = new Cheese();
private Pickle p = new Pickle();
private Lunch l = new Lunch();
public No11Sandwich11() {
System.out.println("Sandwich()"); }
public static void main(String[] args) {
new No11Sandwich11();
}
}
====================================================================
Meal()
Lunch()
PortableLunch()
Bread()
Cheese()
Pickle()
Meal()
Lunch()
Sandwich()
8.3.2 继承和清理
层次结构 中 的每个类都包含 Characteristic 和 Description 这 两种类型的成员对象,并且它们必须被销毁。所以万一某个子对象 要 依赖于其他对象,销毁的顺序应该和初始化顺序相反。先销魂导出类,再基类,因为基类的方法再导出类没有销毁之前可能还会被调用。
我们通常不必执行清理动作,但是一旦选择要执行,就必须谨慎和小心
练习12
import java.util.Random;
class RandomRodentGenerator12 {
private Random rand = new Random();
public Rodent12 next() {
switch(rand.nextInt(3)) {
default:
case 0: return new Mouse12();
case 1: return new Rat12();
case 2: return new Squirrel12();
}
}
}
class Characteristic {
private String s;
Characteristic(String s) {
this.s = s;
System.out.println("Creating Characteristic " + s);
}
}
class Description {
private String s;
Description(String s) {
this.s = s;
System.out.println("Creating Description " + s);
}
}
class Rodent12 {
private String name = "Rodent";
private Characteristic c = new Characteristic("has tail");
private Description d = new Description("small mammal");
Rodent12() {
System.out.println("Rodent()"); }
protected void eat() {
System.out.println("Rodent.eat()"); }
protected void run() {
System.out.println("Rodent.run()"); }
protected void sleep() {
System.out.println("Rodent.sleep()"); }
public String toString() { return name; }
}
class Mouse12 extends Rodent12 {
private String name = "Mouse";
private Characteristic c = new Characteristic("likes cheese");
private Description d = new Description("nocturnal");
Mouse12() {
System.out.println("Mouse()"); }
protected void eat() {
System.out.println("Mouse.eat()"); }
protected void run() {
System.out.println("Mouse.run()"); }
protected void sleep() {
System.out.println("Mouse.sleep()"); }
public String toString() { return name; }
}
class Rat12 extends Rodent12 {
private String name = "Rat";
private Characteristic c = new Characteristic("larger");
private Description d = new Description("black");
Rat12() {
System.out.println("Rat()"); }
protected void eat() {
System.out.println("Rat.eat()"); }
protected void run() {
System.out.println("Rat.run()"); }
protected void sleep() {
System.out.println("Rat.sleep()"); }
public String toString() { return name; }
}
class Squirrel12 extends Rodent12 {
private String name = "Squirrel";
private Characteristic c = new Characteristic("climbs trees");
private Description d = new Description("likes nuts");
Squirrel12() {
System.out.println("Squirrel()"); }
protected void eat() {
System.out.println("Squirrel.eat()"); }
protected void run() {
System.out.println("Squirrel.run()"); }
protected void sleep() {
System.out.println("Squirrel.sleep()"); }
public String toString() { return name; }
}
public class No12Rodent {
private static RandomRodentGenerator12 gen = new RandomRodentGenerator12();
public static void main(String[] args) {
Rodent12[] rodents12 = new Rodent12[3];
for(Rodent12 r : rodents12) {
r = gen.next();
System.out.println(r);
}
}
}
===============================================================
Creating Characteristic has tail
Creating Description small mammal
Rodent()
Creating Characteristic larger
Creating Description black
Rat()
Rat
Creating Characteristic has tail
Creating Description small mammal
Rodent()
Creating Characteristic climbs trees
Creating Description likes nuts
Squirrel()
Squirrel
Creating Characteristic has tail
Creating Description small mammal
Rodent()
Creating Characteristic likes cheese
Creating Description nocturnal
Mouse()
Mouse
存在于其他一个或多个对象共情的情况,问题就变得更加复杂了,你就不可能简单的调用dispose()了。这种情况下必须使用 引用计数器 来 追踪仍旧访问着共享对象得数量了。
练习13
class Shared {
private int refcount = 0; //判断 会逐渐增大 也会减小
private static long counter = 0;// 计数 只能增加
private final long id = counter++;
public Shared() {
print("Creating " + this);
}
public void addRef() { refcount++; }
protected void finalize() {
if(refcount > 0)
print("Error: " + refcount + " Shared " + id + " objects in use");
}
protected void dispose() {
if(--refcount == 0)
print("Disposing " + this);
}
public String toString() { return "Shared " + id; }
}
class Composing {
private Shared shared;
private static long counter = 0;
private final long id = counter++;
public Composing(Shared shared) {
print("Creating " + this);
this.shared = shared;
this.shared.addRef();
}
protected void dispose() {
print("Disposing " + this);
shared.dispose();
}
public String toString() { return "Composing " + id; }
}
public class ReferenceCounting13 {
public static void main(String[] args) {
Shared shared = new Shared();
Composing[] composing = { new Composing(shared),
new Composing(shared), new Composing(shared),
new Composing(shared), new Composing(shared)
};
for(Composing c : composing)
c.dispose();
Composing compTest = new Composing(shared);
Composing compTest2 = new Composing(shared);
// Test finalize():
shared.finalize();
Shared sharedTest = new Shared();
Composing compTest3 = new Composing(sharedTest);
// Test sharedTest finalize():
sharedTest.finalize();
}
}
===================================================================
Creating Shared 0
Creating Composing 0
Creating Composing 1
Creating Composing 2
Creating Composing 3
Creating Composing 4
Disposing Composing 0
Disposing Composing 1
Disposing Composing 2
Disposing Composing 3
Disposing Composing 4
Disposing Shared 0
Creating Composing 5
Creating Composing 6
Error: 2 Shared 0 objects in use
Creating Shared 1
Creating Composing 7
Creating Composing 8
Error: 2 Shared 1 objects in use
8.3.3 构造器内部的多态方法的行为
如果 一个构造器的内部 调用 正在构造器对象 的某个动态绑定 方法,那会发生什么?
在一般方法的内部 , 动态绑定的调用时在运行时才决定的,因为对象 无法知道 它是属于方法所在的类,还是那个类的导出类。
初始化的实际过程时:
- 在其他任何事物之前,将分配给对象的存储空间初始化成二进制零
- 如前述那样调用基类构造器。此时,调用覆盖后的draw()方法(要在调用RoundGlyph构造器之前调用),由于步骤1的缘故,我们此时会发现radius值为0.
- 按照声明的顺序调用成员的初始化方法
- 调用导出类的构造器主体
- 编写构造器时 有一条有效的准则:”用尽可能简单地方法使对象进入正常状态;如果可以地话,避免调用其他方法(能够安全调用地 final 方法(也适用于private方法))
**覆盖和重载的区别 **
①重载是指不同的函数bai使用相同的函数名,但是函数的参数个数或类型不同。调用的时候根据函数的参数来区别不同的函数。
②覆盖(也叫重写)是指在派生类中重新对基类中的虚函数(注意是虚函数)重新实现。即函数名和参数都一样,只是函数的实现体不一样。
2.类的关系区别
覆盖是子类和父类之间的关系,是垂直关系;重载是同一个类中方法之间的关系,是水平关系。
3.产生方法区别
覆盖只能由一个方法或只能由一对方法产生关系;重载是多个方法之间的关系。
练习15
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);
}//覆盖方法
}
class RectangularGlyph extends Glyph {
private int length = 2;
private int width = 4;
RectangularGlyph(int l, int w) {
length = l;
width = w;
System.out.println("RectangularGlyph.RectangularGlyph(), length = "
+ length + ", width = " + width);
}
void draw() {
System.out.println("RectangularGlyph.draw(), length = " + length
+ ", width = " + width);
}
}
public class No15PolyConstructors {
public static void main(String[] args) {
new RoundGlyph(5);
new RectangularGlyph(3, 6);
}
}
==========================================================================
Glyph() before draw()
RoundGlyph.draw(), radius = 0
Glyph() after draw()
RoundGlyph.RoundGlyph(), radius = 5
Glyph() before draw()
RectangularGlyph.draw(), length = 0, width = 0
Glyph() after draw()
RectangularGlyph.RectangularGlyph(), length = 3, width = 6
8.4 协变返回类型
协变返回类型,它表示在导出类中的被覆盖方法可以返回基类方法的返回类型的某种导出类型:
- WhearMill 是导出类
- process 是覆盖方法
- Mill 是基类方法
- new Grain 是基类方法的返回类型
- Wheat() 是基类方法的一种导出类型
8.5 用继承进行设计
因为多态是一种如此巧妙地工具。当我们使用现成的类来建立新类时,如果首先考虑使用继承计数,反倒会加重我们地设计负担。
更好地方式是:
首先选择 “组合”,尤其时不能确定应该使用哪一种方式时。组合不会强制我们的程序设计进入继承的层次结构中。而且更加灵活,因为它可以动态的选择类型(因此也选择了行为),相反,继承在编译时就需要知道确切类型。
用继承表达行为间的差异,用字段表达状态上的变化
练习16
class AlertStatus {
public void alert() {}
}
class NormalAlert extends AlertStatus {
public void alert() {
System.out.println("AlertStatus Normal"); }
}
class HighAlert extends AlertStatus {
public void alert() {
System.out.println("AlertStatus High"); }
}
class MaximumAlert extends AlertStatus {
public void alert() {
System.out.println("AlertStatus Maximum"); }
}
class Starship {
private AlertStatus alertStatus = new NormalAlert();
public void normalAlert() { alertStatus = new NormalAlert(); }
public void highAlert() { alertStatus = new HighAlert(); }
public void maximumAlert() { alertStatus = new MaximumAlert(); }
public void checkAlertStatus() { alertStatus.alert(); }
}
public class No16Transmogrify {
public static void main(String[] args) {
Starship ss = new Starship();
ss.checkAlertStatus();
ss.highAlert();
ss.checkAlertStatus();
ss.maximumAlert();
ss.checkAlertStatus();
ss.normalAlert();
ss.checkAlertStatus();
}
}
========================================================================
AlertStatus Normal
AlertStatus High
AlertStatus Maximum
AlertStatus Normal
8.5.1 纯继承与扩展
纯粹的方式来创建继承层次结构似乎是最好的方式。只有在基类中已经建立的方法才可以在导出类中被覆盖
基类可以接收发送消息给导出类的任何消息,因为二者有着完全相同的接口。我们只需要从导出类向上转型,都是通过多态来处理的。
导出类中的接口的扩展部分不能被基类访问,因此,一旦我们向上转型,就不能调用那些新的方法
8.5.2 向下转型与运行时类型识别
由于向上转型会丢失具体的 类型信息 是安全的。这种在运行期间对类型进行检查的行为称为 “运行时类别识别RTTI(Runtime Type Identification RTTI)”
**向下转型 应该就能获得类型信息?不安全,贸然转移到一种错误类型 **
想访问MoreUseful对象的扩展接口,向下转型是正确的类型,那么转型成功,否则 抛出 ClassCastException异常。
练习17
package 第八章多态;
class Cycle {
private String name = "Cycle";
public static void travel(Cycle c) {
System.out.println("Cycle.ride() " + c);
}
public String toString() {
return this.name;
}
}
class Unicycle extends Cycle {
private String name = "Unicycle";
public void balance() {
System.out.println("Balance Unicycle"); }
public String toString() {
return this.name;
}
}
class Bicycle extends Cycle {
private String name = "Bicycle";
public void balance() {
System.out.println("Balance Bicycle"); }
public String toString() {
return this.name;
}
}
class Tricycle extends Cycle {
private String name = "Tricycle";
public String toString() {
return this.name;
}
}
public class No17Biking {
public static void main(String[] args) {
Cycle[] ride = {
new Unicycle(),
new Bicycle(),
new Tricycle(),
};
// Compile time error: cannot find balance() method in Cycle:
// for(Cycle c : ride) {
// c.balance();
// }
((Unicycle)ride[0]).balance();
((Bicycle)ride[1]).balance();
// Compile time error: no balance() in Tricycle: // 不能随便转 还得有这个类型
// ((Tricycle)ride[2]).balance();
// RTTI: ClassCastException: Tricycle cannot be cast to Bicycle:
// ((Bicycle)ride[2]).balance();
}
}
===============================================================
Balance Unicycle
Balance Bicycle