文章目录
序言:
- 多态是面向对象编程语言的第三种特性,在数据抽象和继承之后。
- 多态从另一个角度分离接口和实现
- 封装创建新的数据类型,实现隐藏将接口和实现分离。多态致力于类型解耦
- 继承是对象作为其本身或者基类进行处理
1. 再论向上转型
- 将对象引用视为其基类引用被称为向上转型
代码示例
package polymorphism.music;
import static net.mindview.util.Print.print;
/**
* @author vincient
* @create 2020-04-03 1:27 PM
*/
public class Instrument {
public void play(Note n) {
print("Instrument.play()");
}
}
package polymorphism.music;
/**
* @author vincient
* @create 2020-04-03 1:26 PM
*/
public enum Note {
MIDDLE_C,
C_SHARP,
B_FLAT;
}
package polymorphism.music;
/**
* @author vincient
* @create 2020-04-03 1:28 PM
*/
public class Wind extends Instrument {
@Override
public void play(Note n) {
System.out.println("Wind.play() " + n);
}
}
package polymorphism.music;
/**
* @author vincient
* @create 2020-04-04 4:03 PM
*/
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);
}
}
// 运行结果
// Wind.play() MIDDLE_C
1.1 忘记对象类型
- 忘记对象类型,当向上转型时。
- 不使用向上转型,使用重载
代码示例:
package polymorphism.music;
import static net.mindview.util.Print.print;
/**
* @author vincient
* @create 2020-04-04 5:20 PM
*/
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);
tune(violin);
tune(frenchHorn);
}
}
class Stringed extends Instrument {
public void play(Note n) {
print("Stringed.play() " + n);
}
}
class Brass extends Instrument {
@Override
public void play(Note n) {
print("Brass.play() " + n);
}
}
// 运行结果
// Wind.play() MIDDLE_C
// Stringed.play() MIDDLE_C
// Brass.play() MIDDLE_C
- 缺点:
- 必须为每种类型的Instrument编写特定的方法。这就意味呀要编写更多代码。
- 编译器不会报错如果忘记了重载
- 类型的处理变得无法操作
- 编写使用基类的方法就意味着不必考虑有基类的存在。
2. 转换
2.1 方法调用绑定
- 将方法调用和方法体联系在一起的机制被称为绑定,发生在程序运行之前的绑定被称为预绑定。
- 后绑定发生在运行时,基于对象类型。
- 实现后绑定的程序语言会在运行时基于对象的类型调用适当的方法。也就是说编译器无法判断对象类型,但是方法调用机制可以找到正确的方法体。
- 除了static或final方法(包括private方法)之外,Java所有方法都使用后绑定。
2.2 产生正确行为
- 发送消息给对象,让对象自己决定正确的行为。
代码示例:
package polymorphism.shape;
/**
* @author vincient
* @create 2020-04-05 3:48 PM
*/
public class Shape {
public void draw(){
}
public void erase(){
}
}
package polymorphism.shape;
import static net.mindview.util.Print.print;
/**
* @author vincient
* @create 2020-04-05 3:49 PM
*/
public class Circle extends Shape {
public void draw() {
print("Circle.draw()");
}
public void erase() {
print("Circle.erase()");
}
}
package polymorphism.shape;
import static net.mindview.util.Print.print;
/**
* @author vincient
* @create 2020-04-05 3:50 PM
*/
public class Square extends Shape {
public void draw() {
print("Square.draw()");
}
public void erase() {
print("Square.erase()");
}
}
package polymorphism.shape;
import static net.mindview.util.Print.print;
/**
* @author vincient
* @create 2020-04-05 3:50 PM
*/
public class Triangle extends Shape {
public void draw() {
print("Triangle.draw()");
}
public void erase() {
print("Triangle.erase()");
}
}
package polymorphism.shape;
import java.util.Random;
/**
* @author vincient
* @create 2020-04-05 3:51 PM
*/
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();
}
}
}
package polymorphism.shape;
/**
* @author vincient
* @create 2020-04-05 3:53 PM
*/
public class Shapes {
private static RandomShapeGenerator gen = new RandomShapeGenerator();
public static void main(String[] args) {
Shape[] s = new Shape[9];
for (int i = 0; i < s.length; i++) {
s[i] = gen.next();
}
for (Shape shp : s) {
shp.draw();
}
}
}
// 运行结果
// Triangle.draw()
// Triangle.draw()
// Square.draw()
// Triangle.draw()
// Square.draw()
// Triangle.draw()
// Square.draw()
// Triangle.draw()
// Circle.draw()
2.3 可扩展性
代码示例:
package polymorphism.music3;
import polymorphism.music.Note;
import static net.mindview.util.Print.print;
/**
* @author vincient
* @create 2020-04-08 8:29 AM
*/
public class Music3 {
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) {
Instrument[] orchestra = {
new Wind(),
new Percussion(),
new Stringed(),
new Brass(),
new Woodwind()
};
tuneAll(orchestra);
}
}
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 {
@Override
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";
}
}
// 运行结果
// Wind.play() MIDDLE_C
// Percussion.play() MIDDLE_C
// Stringed.play() MIDDLE_C
// Brass.play() MIDDLE_C
// Woodwind.play() MIDDLE_C
结论:
- 对代码内的改动并不会对不应该被影响的程序造成损害。
- 多态是将变化的部分和不变的部分分离的一门技术。
2.4 缺陷:重写私有方法
代码示例:
package polymorphism;
import static net.mindview.util.Print.print;
/**
* @author vincient
* @create 2020-04-08 1:12 PM
*/
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()");
}
}
// 运行结果
// private f()
- private的方法自动视为final方法。
- 只有非私有方法才可以被重写,
- 为了弄清楚,应当在继承类中使用与私有父类方法不同名的方法。
2.5 缺陷:字段和静态方法
代码示例:
package polymorphism;
/**
* @author vincient
* @create 2020-04-08 1:29 PM
*/
public class FieldAccess {
public static void main(String[] args) {
Super sup = new Sub();
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());
}
}
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;
}
}
// 运行结果
// sup.field = 0, sup.getField() = 1
// sub.field = 1, sub.getField() = 1, sub.getSuperField() = 0
package polymorphism;
/**
* @author vincient
* @create 2020-04-08 1:38 PM
*/
public class StaticPolymorphism {
public static void main(String[] args) {
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()";
}
public String dynamicGet() {
return "Derived dynamicGet()";
}
}
// 运行结果
// Base staticGet()
// Derived dynamicGet()
- 静态方法与类相关,和单个对象无关。
3. 构造器和多态
3.1 构造函数的调用顺序
- 在构造派生类的过程中总是会调用一个基类的构造函数,将继承层次结构串联起来,使每一个基类都有一个构造函数被调用。理由如下:
- 构造器有一个特殊的工作:看对象是否被正确构建。
- 一个派生类只能访问它自己的成员,而不能访问基类的成员(其成员通常是私有的)。
- 只有基类构造函数才有适当的知识和权限来初始化自己的元素。
结论:所有的构造器都必须被调用,否则整个对象就无法构造。
- 编译器会对派生类的每一个部分执行构造函数调用。如果你没有在派生类构造函数体中明确地调用基类构造函数,它就会默默地调用默认的构造函数。
- 构成、继承、多态性对构建顺序的影响,代码示例:
package polymorphism;
import static net.mindview.util.Print.print;
/**
* @author vincient
* @create 2020-04-10 2:28 PM
*/
class Meal {
public Meal() {
print("Meal()");
}
}
class Bread {
public Bread() {
print("Bread()");
}
}
class Cheese {
public Cheese() {
print("Cheese()");
}
}
class Lettuce {
public Lettuce() {
print("Lettuce()");
}
}
class Lunch extends Meal {
public Lunch() {
print("Lunch()");
}
}
class PortableLunch extends Lunch {
public PortableLunch() {
print("PortableLunch()");
}
}
public class Sandwich extends PortableLunch {
private Bread b = new Bread();
private Cheese c = new Cheese();
private Lettuce l = new Lettuce();
public Sandwich() {
print("Sandwich()");
}
public static void main(String[] args) {
new Sandwich();
}
}
// 运行结果
// Meal()
// Lunch()
// PortableLunch()
// Bread()
// Cheese()
// Lettuce()
// Sandwich()
结论,复杂对象的构造函数调用顺序如下:
- 基层类构造函数被调用
- 按声明顺序初始化成员变量。
- 派生类构造函数被调用。
3.2 继承和清理
- 当使用组成和继承来创建一个新类时,大多数情况下不用担心清理问题,子对象通常可以交给垃圾回收器来处理。
- 如果确实遇到清理问题,必须小心地为新类创建一个dispose()方法。
- 对于继承,如果有任何特殊的清理工作必须作为垃圾收集的一部分发生,你必须在派生类中覆盖dispose()。
- 当你在继承类中重写dispose()时,一定要记得调用基类版本的dispose(),否则基类的清理就不会发生。
代码示例:
package polymorphism;
import static net.mindview.util.Print.print;
/**
* @author vincient
* @create 2020-04-10 3:09 PM
*/
class Characteristic {
private String s;
Characteristic(String s) {
this.s = s;
print("Creating Characteristic " + s);
}
protected void dispose() {
print("disposing Characteristic " + s);
}
}
class Description {
private String s;
Description(String s) {
this.s = s;
print("Creating Description " + s);
}
protected void dispose() {
print("disposing Description " + s);
}
}
class LivingCreature {
private Characteristic p = new Characteristic("is alive");
private Description t = new Description("Basic Living Creature");
LivingCreature() {
print("LivingCreature()");
}
protected void dispose() {
print("LivingCreature dispose");
t.dispose();
p.dispose();
}
}
class Animal extends LivingCreature {
private Characteristic p = new Characteristic("has heart");
private Description t = new Description("Animal not Vegetable");
Animal() {
print("Animal()");
}
protected void dispose() {
print("Animal dispose");
t.dispose();
p.dispose();
super.dispose();
}
}
class Amphibian extends Animal {
private Characteristic p = new Characteristic("can live in water");
private Description t = new Description("Both water and land");
public Amphibian() {
print("Amphibian()");
}
protected void dispose() {
print("Amphibian dispose");
t.dispose();
p.dispose();
super.dispose();
}
}
public class Frog extends Amphibian {
private Characteristic p = new Characteristic("Croaks");
private Description t = new Description("Eats Bugs");
public Frog() {
print("Frog()");
}
@Override
protected void dispose() {
print("Frog dispose");
t.dispose();
p.dispose();
super.dispose();
}
public static void main(String[] args) {
Frog frog = new Frog();
print("Bye!");
frog.dispose();
}
}
// 运行结果
// Creating Characteristic is alive
// Creating Description Basic Living Creature
// LivingCreature()
// Creating Characteristic has heart
// Creating Description Animal not Vegetable
// Animal()
// Creating Characteristic can live in water
// Creating Description Both water and land
// Amphibian()
// Creating Characteristic Croaks
// Creating Description Eats Bugs
// Frog()
// Bye!
// Frog dispose
// disposing Description Eats Bugs
// disposing Characteristic Croaks
// Amphibian dispose
// disposing Description Both water and land
// disposing Characteristic can live in water
// Animal dispose
// disposing Description Animal not Vegetable
// disposing Characteristic has heart
// LivingCreature dispose
// disposing Description Basic Living Creature
// disposing Characteristic is alive
结论:对象销毁的顺序和初始化顺序相反
- 其中一个成员对象与一个或多个其他对象共享:可能需要使用引用计数来跟踪仍在访问共享对象的对象数量。
代码示例:
package polymorphism;
import static net.mindview.util.Print.print;
/**
* @author vincient
* @create 2020-04-10 3:46 PM
*/
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 dispose() {
if (--refCount == 0) {
print("Disposing " + this);
}
}
@Override
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();
}
@Override
public String toString() {
return "Composing " + id;
}
}
public class ReferenceCounting {
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();
}
}
}
// 运行结果
// 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
3.3 构造器内部多态方法的行为
- 问题:在构造某个对象时调用了一个被构造对象的动态绑定的方法,会发生什么?
- 在一个普通方法内部,动态绑定的调用在运行时被解析
- 在构造函数中调用一个动态绑定的方法,则使用该方法的重载定义。
- 构造器的工作实际上时创建对象。在构造器中调用某个方法,可能会出现被调用对象未完成初始化
代码示例:
package polymorphism;
import static net.mindview.util.Print.print;
/**
* @author vincient
* @create 2020-04-12 4:58 PM
*/
class Glyph {
void draw() {
print("Glyph.draw()");
}
public Glyph() {
print("Glyph() before draw()");
draw();
print("Glyph() after draw()");
}
}
class RoundGlyph extends Glyph {
private int radius = 1;
public RoundGlyph(int r) {
radius = r;
print("RoundGlyph.RoundGlyph(), radius = " + radius);
}
@Override
void draw() {
print("RoundGlyph.draw(), radius = " + radius);
}
}
public class PolyConstructors {
public static void main(String[] args) {
new RoundGlyph(5);
}
}
// 运行结果
// Glyph() before draw()
// RoundGlyph.draw(), radius = 0
// Glyph() after draw()
// RoundGlyph.RoundGlyph(), radius = 5
- 真正的初始化流程:
- 分配内存
- 调用基类构造器
- 成员变量初始化
- 调用子类的构造器
- 编写构造器的准则:用尽量简洁的步骤将对象设置好。唯一可以在构造器中安全调用的方法是final修饰的方法,也包含private方法,这些方法不会被覆盖
4. 协变返回类型
Java SE5增加了协变返回类型,也就是说,派生类中的重载方法可以返回一个由基类方法返回的类型派生的类型。
代码示例:
package polymorphism;
/**
* @author vincient
* @create 2020-04-13 7:59 AM
*/
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();
}
}
public class CovariantReturn {
public static void main(String[] args) {
Mill m = new Mill();
Grain g = m.process();
System.out.println(g);
m = new WheatMill();
g = m.process();
System.out.println(g);
}
}
// 运行结果
// Grain
// wheat
5. 用继承进行设计
状态设计模式,代码示例:
package polymorphism;
import static net.mindview.util.Print.print;
/**
* @author vincient
* @create 2020-04-13 8:24 AM
*/
class Actor {
public void act() {
}
}
class HappyActor extends Actor {
@Override
public void act() {
print("HappyActor");
}
}
class SadActor extends Actor {
@Override
public void act() {
print("SadActor");
}
}
class Stage {
private Actor actor = new HappyActor();
public void change() {
actor = new SadActor();
}
public void performPlay() {
actor.act();
}
}
public class Transmogrify {
public static void main(String[] args) {
Stage stage = new Stage();
stage.performPlay();
stage.change();
stage.performPlay();
}
}
// 运行结果
// HappyActor
// SadActor
设计准则:用继承来表达行为的差异,用字段来表达状态的变化。
5.1 替换与扩展
-
“纯粹”继承:只有在基类中建立的方法才会在派生类中重写。
-
is-a:纯粹继承,is-like-a:扩展继承
5.2 向下转型和运行时类型信息
- 向下转型:获取子类信息。代码示例
package polymorphism;
/**
* @author vincient
* @create 2020-04-14 10:25 PM
*/
class Useful {
public void f() {
}
public void g() {
}
}
class MoreUseful extends Useful {
public void f() {
}
public void g() {
}
public void u() {
}
public void v() {
}
public void w() {
}
}
public class RTTI {
public static void main(String[] args) {
Useful[] x = {new Useful(), new MoreUseful()};
x[0].f();
x[1].g();
((MoreUseful) x[1]).u();
((MoreUseful) x[0]).u();
}
}
// 运行结果
Exception in thread "main" java.lang.ClassCastException: polymorphism.Useful cannot be cast to polymorphism.MoreUseful
at polymorphism.RTTI.main(RTTI.java:40)