第8章 多态
8.1 再论向上转型
public class Instrument {
public void play(String note){
System.out.println("Instrument play "+note);
}
}
public class Brass extends Instrument {
@Override
public void play(String note) {
System.out.println("Brass play " +note);
}
}
public class Wind extends Instrument {
@Override
public void play(String note) {
System.out.println("Wind play " +note);
}
}
// 非多态的使用,可以看到代码更臃肿,而且在添加乐器类时,必须添加对应的重载方法
public class Music {
public static void tune(Brass b){
b.play("音符");
}
public static void tune(Wind w){
w.play("音符");
}
public static void main(String[] args) {
tune(new Wind());
tune(new Brass());
}
}
// 向上转型的使用
public class Music {
public static void tune(Instrument i){
i.play("音符");
}
public static void main(String[] args) {
tune(new Wind());
tune(new Brass());
}
}
8.2 转机
绑定 将一个方法调用 同 一个方法主体关联起来。
前期绑定 在程序执行前进行绑定。
后期绑定/动态绑定/运行时绑定 在运行时根据对象的类型进行绑定。
public class Shape {
public void draw(){
System.out.println("几何图形");
}
}
public class Circle extends Shape{
@Override
public void draw() {
System.out.println("圆形");
}
}
public class Square extends Shape {
@Override
public void draw() {
System.out.println("正方形");
}
}
public class Triangle extends Shape {
@Override
public void draw() {
System.out.println("矩形");
}
}
public class RandomShape {
private static Random random = new Random(47);
// 产生随机几何图形
public static Shape next() {
switch (random.nextInt(3)) {
case 0:
return new Circle();
case 1:
return new Square();
default:
return new Triangle();
}
}
public static void main(String[] args) {
Shape[] shapes = new Shape[5];
for (int i = 0; i < shapes.length; i++) {
// 将随机产生的几何图形放入Shape数组中
shapes[i] = next();
}
for (Shape shape : shapes) {
// 正确画出图形
shape.draw();
/*
矩形
矩形
正方形
矩形
正方形
*/
}
}
}
总结:
结合8.1和8.2的案例可以得出以下结论:只与基类通信,其一可以得到正确的结果;其二,程序是可扩展的,而不用更改现有代码。
注意: 私有方法/静态方法/域不存在多态。
8.3 构造器和多态
构造器的调用顺序
public class Animal {
public Animal(){
System.out.println("Animal");
}
}
public class Person extends Animal {
public Person(){
System.out.println("Person");
}
}
public class Student extends Person {
private Table table = new Table();
public Student(){
System.out.println("Student");
}
}
@Test
public void test1(){
Student student = new Student();
}
/*
Animal
Person
Table
Student
*/
总结:
先调用积累构造器,再调用成员的初始化方法,最后调用类构造器,这样保证了所有成员都已经构建完毕。
继承与清理
当我们需要处理一些清理问题时。
public class Table {
public Table(){
System.out.println("Table");
}
public void dispose(){
System.out.println("Table被销毁");
}
}
public class Book {
public Book(){
System.out.println("Book");
}
public void dispose(){
System.out.println("Book被销毁");
}
}
public class Animal {
public Animal(){
System.out.println("Animal");
}
public void dispose(){
System.out.println("Animal被销毁");
}
}
public class Person extends Animal {
public Person(){
System.out.println("Person");
}
@Override
public void dispose() {
System.out.println("Person被销毁");
super.dispose();
}
}
public class Student extends Person {
private Table table = new Table();
private Book book = new Book();
public Student(){
System.out.println("Student");
}
/*
注意清理的顺序:
按照声明倒序清理成员;
清理本对象;
清理父类对象
*/
@Override
public void dispose() {
book.dispose();
table.dispose();
System.out.println("Student被销毁");
super.dispose();
}
}
@Test
public void test1(){
Student student = new Student();
student.dispose();
}
/*
Animal
Person
Table
Book
Student
Book被销毁
Table被销毁
Student被销毁
Person被销毁
Animal被销毁
*/
当一个对象被多处引用时的处理
public class Toy {
// 定义引用计数器
private int recount =0;
// 定义产生的对象的个数
private static int count = 0;
// 定义对象的id
// 这里执行了两步操作,先将对象计数器+1,然后将该值作为该对象的id
private final int id = ++count;
public Toy(){
System.out.println("创建:"+this);
}
// 增加引用
public void addRef(){
recount++;
}
public void dispose(){
// 减去一引用,判断是否还有其他引用
if (--recount <= 0){
System.out.println("Toy"+id+"号被销毁");
}
}
@Override
public String toString(){
return "Toy"+id+"号";
}
}
public class Children {
private Toy toy;
public Children(Toy toy){
this.toy = toy;
// 增加引用
this.toy.addRef();
}
public void dispose(){
toy.dispose();
System.out.println("Children被销毁");
}
}
@Test
public void test1(){
Toy toy = new Toy();
Children[] childrens = {new Children(toy),new Children(toy),new Children(toy)};
for (Children children : childrens) {
children.dispose();
}
}
/*
创建:Toy1号
Children被销毁
Children被销毁
Toy1号被销毁
Children被销毁
*/
构造器内部的多态方法行为
public class Animal {
public Animal(){
System.out.println("Animal");
eat();
}
public void eat(){
System.out.println("Animal吃东西");
}
}
public class Person extends Animal {
private String food = "盖饭";
public Person(){
System.out.println("Person");
}
@Override
public void eat() {
System.out.println("人类吃"+food);
}
}
@Test
public void test1(){
Person person = new Person();
}
/*
Animal
人类吃null
Person
*/
总结:
在初始化过程中,当基类调用被重写的方法时,基类会调用重写后的方法。
之前的初始化过程是不完整的,真正的初始化是将分配给对象的空间初始化为二进制的0,再按照之前的初始化过程进行初始化。
构造器中调用final或private方法时不会出现上述问题。
构造器中应当尽可能简单。
8.4 协变返回类型
在子类覆盖基类方法时,可以返回基类返回类型的子类型。
8.5 用继承进行设计
相对于继承,更优的选择是组合。
组合可以在运行期间选择选择具体的对象,而继承不能做到这一点。
public class Person {
public void eat(){
System.out.println("人吃饭");
}
}
public class Student extends Person {
@Override
public void eat() {
System.out.println("学生吃饭");
}
}
public class Teacher extends Person {
@Override
public void eat() {
System.out.println("老师吃饭");
}
}
public class School {
private Person person = new Student();
public void action() {
person.eat();
}
public void change() {
person = new Teacher();
}
}
@Test
public void test1(){
School school = new School();
school.action();
school.change();
school.action();
}
/*
学生吃饭
老师吃饭
*/
纯继承与扩展
导出类与基类具有完全一致的接口时,是Is-a关系,此时是纯替代关系,是安全的;当导出类扩充方法了,是is-like-a的关系,基类并不能访问导出类特有的方法,此时向下转型访问特有的方法是错误的。