您的位置:首页 > 编程语言 > Java开发

Java中的方法分派

2016-01-02 14:32 441 查看

Java中的方法分派

程序设计中,许多时候把不同的函数命名成名字相同可以更清晰地表达出语义。相同的方法名字,需要根据方法的参数、调用者等信息来确定到底应该执行哪个方法。这个确定执行哪个方法的过程就是方法分派。

众所周知,Java是一门面向对象语言,为我们提供了重载和重写的机制。那么,在重载和重写的背后是一个怎样的原理或者原则呢?今天通过实验来揭示

一,静态分派
假设,有如下的几个类和方法,并且接下来的一系列实验也都基于这些类和方法:
public void sayHello(Human human){
        System.out.println("human say hello!");
    }
    public void sayHello(Man man){
        System.out.println("man say hello.");
    }

    private static class Human{
        public void sayHello(){
            System.out.println("say hello in human");
        }

        public void eat(Fruit fruit){
            System.out.println("human eat fruit.");
        }
        public void eat(Apple apple){
            System.out.println("human eat apple.");
        }
    }

    private static class Man extends Human{
        public void sayHello(){
            System.out.println("say hello in man");
        }

        public void eat(Fruit fruit) {
            System.out.println("man eat fruit.");
        }

        public void eat(Apple apple) {
            System.out.println("man eat apple.");
        }
    }

    private static class Fruit{}
    private static class Apple extends Fruit{}


来看一个简单的静态分派的例子

public void overload(){
        Human human = new Human();
        Human man = new Man();
        Man realMan = new Man();

        sayHello(human);
        sayHello(man);
        sayHello(realMan);
    }


这段代码唯一不太确定的就是sayHello(man)到底调用哪个目标方法,man是一个子类的类型,但却是一个父类类型的引用。最终虚拟机选择的是sayHello(Human)方法,也就是说,在静态分派阶段,虚拟机是根据引用的类型来判断调用哪个重载方法的。实际上,静态分派是在编译期完成的,调用哪个重载方法在编译期就已经确定,如刚刚的sayHello(man)语句,经过编译器的编译会把sayHello(Human)做为调用目标写入到invokevirtual指令的参数中。

静态分派还有一个特点,如果编译期没有找到确切的重载方法,则会尝试找到一个更加合适的版本。如刚才的例子中,把sayHello(Man)方法注释掉,sayHello(realMan)还是可以成功执行,只不过此时执行的目标方法为sayHello(Human)。也就是说,编译期找到了参数为Man的父类的重载方法。但是,若一个类型父类或实现的接口不止一个,而当前类中又分别有以其父类活接口类型作为参数的重载方法,编译期会默认选择其中一个吗?当然不会,这样就会出现语义上的歧义,编译器的选择是报出错误,编译不通过。如下面的例子:

public class AmbiguousDispatch {
    private interface Eater{}
    private interface Drinker{}

    //同时继承子类和实现接口是一样的,也会报错
    private class Human implements Eater,Drinker{}
    public void serve(Eater eater){}
    public void serve(Drinker drinker){}

    @Test
    public void ambiguous(){
        Human human = new Human();
        //serve(human);
    }
}


如果代码中注释的serve(human)放开的话,编译器会提示Ambiguous Method Call。因为它不知道该调用哪个方法,不知道这个人是按吃货算还是按酒鬼算~

二,动态分派

方法的动态分派发生在执行阶段,虚拟机根据执行方法的实际类型来判断执行哪个目标方法。

public void dynamicDispatch(){
        Human man = new Man();
        Human human = new Human();
        Man realMan = new Man();

        human.sayHello();
        man.sayHello();
        realMan.sayHello();
    }


对于任何一个Java程序员,都会准确的推断出这段代码的结果。父类和子类都实现了相同的方法,在编译期不能确定执行父类中的方法还是执行子类中复写的方法,而需要到运行期知道了执行方法的具体类型才会判断出目标方法。如man.sayHello(),man是一个Human类型的引用,它的实际类型是Man而且可能会发生变化,到了运行期已经可以确切的知道了它的实际类型就是Man,所以会期限Man中复写的sayHello方法。
如果,方法的调用过程既有静态分派又有动态分派,则会按顺序先进行静态分派,然后再进行动态分派。

三,静态方法

在Java中,静态方法同样也可以继承,可以重载。那么它是怎么分派的呢?先看下面的实验

public class StaticMethod {    private static class Human{
        public static void sayHello(){
            System.out.println("human say hello.");
        }
    }

    private static class Man extends Human{
        public static void sayHello(){System.out.println("man say hello");}
        //public void sayHello(){}
    }

    @Test
    public void callMethod(){
        Human.sayHello();
        Man.sayHello();

        Human human = new Human();
        Human man = new Human();
        Man realMan = new Man();

        human.sayHello();
        man.sayHello();
        realMan.sayHello();
    }
}


静态方法可以通过类名直接访问,子类会继承父类的静态方法,但是不会复写父类方法。如果子类声明了和父类一样的方法,则这时候发生的是隐藏,而不是复写。因为静态方法是属于类的,即使是通过类的实例调用,最终生成的字节码指令也是invokestatic,目标方法是在编译期就已经确定好的。同时子类的实例方法也不能复写父类中的静态方法,编译器会报错。上面例子的结果为:

human say hello.
man say hello
human say hello.
human say hello.
man say hello


四,虚方法表

大多数的虚拟机动态分派时,都会利用虚方法表查找目标方法。我们知道,子类会继承父类中的方法,还有可能复写其中的方法。虚方法表中存放的就是当前类中所有方法的实际入口地址。如果方法直接继承自父类则子类中的方法入口地址与父类的相同,都是父类中方法的入口地址。如果子类复写了父类中的方法,则它的虚方法表中的入口地址就是自己复写后的方法的入口地址。运行阶段,虚拟机只需要查找实际类型中的虚拟方法表,就可以得到最终的目标方法入口地址。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: