zl程序教程

您现在的位置是:首页 >  Java

当前栏目

大意失荊州啊-java基础之多态 & 代理

2023-04-18 14:25:55 时间

遇到个java基础的bug,搞了许久,用了一个开源库,代码比较多,就赖得一点点看完整代码,结果是越懒越花时间,使用简单的代码模拟了一下,如下:

Animal.java

abstract class Animal {

    public static Animal getInstance() {
        return new Dog();
    }

    public void hello() {
        log("hello");
    }

    public abstract void log(String message);
}

Main.java

public class Main {
    public static void main(String[] args) {
        Animal.getInstance().hello();
    }
}

运行结果如下:

hello

很简单,就是输出一个字符串,于是我往Animal中增加另一个函数,如下:

Animal.java

abstract class Animal {

    public static Animal getInstance() {
        return new Dog();
    }

    public void hello() {
        log("hello");
    }

    public void world() {
        log("world");
    }

    public abstract void log(String message);
}

Main.java

public class Main {
    public static void main(String[] args) {
        Animal.getInstance().hello();
        Animal.getInstance().world();
    }
}

运行结果如下:

hello
Exception in thread "main" java.lang.RuntimeException
	at com.company.Dog.log(Dog.java:14)
	at com.company.Animal.world(Animal.java:14)
	at com.company.Dog.world(Dog.java:3)
	at com.company.Main.main(Main.java:6)

如上结果,出了异常,理想的结果是输出一个hello和一个world,但是只输出了hello,当时我没太在意这个异常,就是很奇怪为什么我增加一个方法就不行,不都是同一个类中的方法吗?于是我打印一下类名,如下:

abstract class Animal {

    public static Animal getInstance() {
        return new Dog();
    }

    public void hello() {
        System.out.println("hello: " + getClass().getName());
        log("hello");
    }

    public void world() {
        System.out.println("world: " + getClass().getName());
        log("world");
    }

    public abstract void log(String message);
}

运行结果如下:

hello: com.company.Cat
hello
world: com.company.Dog
Exception in thread "main" java.lang.RuntimeException
	at com.company.Dog.log(Dog.java:14)
	at com.company.Animal.world(Animal.java:16)
	at com.company.Dog.world(Dog.java:3)
	at com.company.Main.main(Main.java:6)

当时我就觉得是不是见鬼了,明明一个类上的两个方法,为什么打印的对象一个是猫一个是狗?

要解决这个问题还是得看完整代码,如下:

Animal.java

abstract class Animal {

    public static Animal getInstance() {
        return new Dog();
    }

    public void hello() {
        System.out.println("hello: " + getClass().getName());
        log("hello");
    }

    public void world() {
        System.out.println("world: " + getClass().getName());
        log("world");
    }

    public abstract void log(String message);
}

Cat.java

public class Cat extends Animal {
    @Override
    public void log(String message) {
        System.out.println(message);
    }
}

Dog.java

public class Dog extends Animal {

    private Cat cat = new Cat();

    @Override
    public void hello() {
        cat.hello();
    }

    @Override
    public void log(String message) {
        throw new RuntimeException();
    }
}

Main.java

public class Main {
    public static void main(String[] args) {
        Animal.getInstance().hello();
        Animal.getInstance().world();
    }
}

这里就很完整了,Animal.getInstance()拿到的对象是Dog,在Dog中又创建了一个Cat对象。画一下内存图就可以很容易理解了,如下:

首先看Animal.getInstance().hello();的对象内存图,如下:
在这里插入图片描述

如上图,有些函数是继承自父类的,有些则覆盖父类的,可以看到调用的hello函数是Cat对象上的,所以就不难理解下面的输出结果了:

hello: com.company.Cat
hello

再来看Animal.getInstance().world();的对象内存图,如下:
在这里插入图片描述
如上图,可以看到调用的world函数是Dog对象上的,所以就不难理解下面的输出结果了:

world: com.company.Dog
Exception in thread "main" java.lang.RuntimeException
	at com.company.Dog.log(Dog.java:14)
	at com.company.Animal.world(Animal.java:16)
	at com.company.Dog.world(Dog.java:3)
	at com.company.Main.main(Main.java:6)

所以总结起来就很简单了,Dog实际上是一个代理类,它没有实际的功能,实际的功能都在Cat类上,所以解决这个问题就很简单了,在Dog中覆盖world函数,并代理给Cat来执行,如下:

public class Dog extends Animal {

    private Cat cat = new Cat();

    @Override
    public void hello() {
        cat.hello();
    }

    @Override
    public void world() {
        cat.world();
    }

    @Override
    public void log(String message) {
        throw new RuntimeException();
    }
}

运行结果如下:

hello: com.company.Cat
hello
world: com.company.Cat
world