java编程思想(读书笔记):7.多态

七、多态
多态中重要概念:函数调用(method–call)绑定方式,所谓“绑定”,就是建立method call(函数调用)和method body(函数本体)的关联。
后期绑定(动态绑定):绑定动作将在执行期才根据对象型别而进行。Java中的所有函数,除了被声明为final者,皆使用后期绑定。
代码演示为:

class Pet{
    Pet(){
        System.out.println("pet created");
    }
    @Override
    public String toString() {
        return "pet string";
    }
    //传入Pet,打印信息
    public static void print(Pet pet){
        System.out.println(pet);
    }
}

class Dog extends Pet{
    Dog(){
        System.out.println("dog created");
    }
    @Override
    public String toString() {
        return "dog string";
    }
}
class Cat extends Pet{
    Cat(){
        System.out.println("cat created");
    }
    @Override
    public String toString() {
        return "cat string";
    }
}
class Cat1 extends Pet{
    Cat1(){
        System.out.println("cat1 created");
    }
    //cat1没有重写tostring方法
}

public class Main{
    public static void main(String[] args){
        Pet pet1 = new Pet();
        Pet pet2 = new Dog();
        Pet pet3 = new Cat();
        Pet pet4 = new Cat1();
        Pet.print(pet1);
        Pet.print(pet2);
        Pet.print(pet3);
        Pet.print(pet4);
    }
}

输出结果为:

pet created
pet created
dog created
pet created
cat created
pet created
cat1 created
pet string
dog string
cat string
pet string

说明两个问题:
1.当new出derived class对象时,调用自身构造函数之前会调用父类构造函数。
2.在 Pet中的print方法传入参数为Pet,但运行期java能够自动识别更准确的型别信息(是Dog,Cat还是Cat1),从而调用各自的tostring()方法。当没有重写tostring()方法时,如Cat1,调用其父类方法。为什么在运行期我们能够分别出他们的型别呢?这与“后期绑定”密不可分!

抽象类和抽象方法:
为抽象类产生任何对象都是不安全的,编译器会发出错误信息。
如果继承自一个抽象类,并且希望为新型别产生对象,那么得为base class中的所有抽象方法提供相应的定义。如果没有这么做,derived class也会成为一个抽象类,并且编译器强迫你使用abstract来修饰derived class。
小技巧:我们也可以将不含任何抽象方法的class声明为abstract。这样做的目的是:在这个class不具备“拥有抽象方法”的情况下,我们不希望class被产生出任何实体。
构造函数和多态:虽然构造函数不具有多态性格(但你还是可以拥有某种“虚拟构造函数”,详见12章)

当有继承时,复杂对象的调用顺序:

class Pet{
static{
        new Out("pet static before");
    }
    Pet(){
        System.out.println("pet created");
    }
    Object cat = new Out("pet"); 
    @Override
    public String toString() {
        return "pet string";
    }
    //传入Pet,打印信息
    public static void print(Pet pet){
        System.out.println(pet);
    }
}
class Cat extends Pet{
    Cat(){
        System.out.println("cat created");
    }
    static{
        new Out("cat static after");
    }
    Object cat = new Out("cat"); 
    @Override
    public String toString() {
        return "cat string";
    }
}
class Out{
    Out(String s){
        System.out.println(s);
    }
}
class SonOfCat extends Cat{
    static{
        new Out("static before");
    }
    Object sonofcat = new Out("sonofcat"); 
    SonOfCat(){
        System.out.println("sonofcat created");
    }
    static{
        new Out("static after");
    }
}


public class Main{
    public static void main(String[] args){
        Pet pet = new SonOfCat();
    }
}

输出结果:

pet static before
cat static after
sonofcat static before
sonofcat static after
pet
pet created
cat
cat created
sonofcat
sonofcat created

这样我们就知道了,当有继承时,复杂对象的调用顺序:
1.首先向上追溯到最上层class,完成它的static初始化(函数和成员变量),逐步向下,完成至自身static块的初始化。
2.完成最上层的non–static初始化,并调用其构造函数,完成后转向下一层class。
3.如此向下,直到自身的non-static初始化和构造函数调用完毕。

关于抽象class调用顺序:

abstract class Glyph{
    abstract void draw();
    Glyph(){
        System.out.println("glyph before draw");
        draw();
        System.out.println("glyph after draw");
    }
}
class RoundGlyph extends Glyph{
    int radius = 1;
    RoundGlyph(int r) {
        radius = r;
        System.out.println("roundglyph.roundglyph,radius="+radius);
    }
    void draw(){
        System.out.println("roundglyph.draw,radius="+radius);
    }
}
public class Main{
    public static void main(String[] args){
        new RoundGlyph(5);
    }
}

你可能认为函数调用是这样的,在Glyph中,draw()是抽象的,所以其作用是让其他人进行重写,于是在RoundGlyph 中加以重写。但Glyph构造函数中调用了此函数,结果唤起了RoundGlyph .draw(),这可能是你的意图。但这是错误的。其真实输出为:

glyph before draw
roundglyph.draw,radius=0
glyph after draw
roundglyph.roundglyph,radius=5

实际的初始化过程是:
1.任何事情发生前,分配给此对象的存储空间会被初始化为二进制零值(为0或null或false)。
2.此例中,先调用base class的构造函数,重写后的draw()会被调用(在RoundGlyph 构造函数被调用之前),由于步骤1的缘故,draw()看到的radius为0。
3.以“成员声明顺序”来调用各成员的初始式。
4.调用derived class构造函数本体。
因此,在撰写构造函数时,一条原则就是:“尽可能简单地让对象进入正确的状态。如果可以的话,别调用任何函数”。构造函数中唯一可以安全调用的函数就是“base class中的final函数(对private函数来说一样成立,因为他们天生就是final)”。此类函数无法被重写,也就不会产生这一类意想不到的结果。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值