02月28, 2018

分派

  java是一门面向对象的程序语言,因此java具备面向对象的三个特征:继承、封装和多态,而分派调用过程将会揭示多态性特征。

概念

  在将不同分派之前,先看一个概念:

// Huamn Man Woman的关系如下
static interface Human {
}
static class Man implements Human {
}
static class Woman implements Human {
}

//声明变量
Human man = new Man();

  我们将上面变量声明中的Human成为变量的静态类型(Static Type),或者外观类型,而后面的Man则成为变量的**实际类型(Actual Type).
   变量的静态类型是在编译期可知的,而实际类型变化的结果在运行期才可确定,编译器在编译程序时并不知道一个对象的实际类型是什么,例如:

//实际类型变化
Human man = new Man();
man = new Woman();
//强制转换
Huamn human = new Man();
(Man)human  //使用时,实际类型为Human

静态分派

例子
public class DispatcherTest {

    static interface Human {
    }
    static class Man implements Human {
    }
    static class Woman implements Human {
    }

    public void sayHi(Human human) {
        System.out.println("human say hi. ");
    }
    public void sayHi(Man man) {
        System.out.println("man say hi. ");
    }
    public void sayHi(Woman woman) {
        System.out.println("woman say hi. ");
    }

    public static void main(String[] args) {
        Human man = new Man();
        Human woman = new Woman();

        DispatcherTest test = new DispatcherTest();
        test.sayHi(man);
        test.sayHi(woman);
    }

}
测试结果
human say hi. 
human say hi.
分析

  上面的例子是一个重载的例子,DispatcherTest包含三个say方法,方法参数的类型不同,但却存在继承关系。测试过程中使用了两个静态类型相同而实际类型不同的变量,但虚拟机(准确来说是编译器) 在重载时是通过参数的静态类型而不是实际类型作为判断依据的。且静态类型是编译期可知的
  所有依赖静态类型来定位方法执行的分派动作成为静态分派,典型应用是方法重载。静态分派发生在编译阶段,因此确定静态分派的动作实际不是由虚拟机来执行的

总结

   "重载"通过参数的静态类型做判断。

备注

  重载方法匹配优先级,例如

public class A {
    public method(int a) {}
    public method(long a) {}
    public method(byte a) {}
    public menthod(Object a) {}
}

调用A类的方法method的参数为char,char a = 'a'; A.mehtod(a),该如何选择...
  实际上参数会自动转换,按照`char->int->long->float->double的顺序转型进行匹配,但不会匹配到byte和shrot类型的重载。

动态分配

  动态分配和多态性的另外一个重要特性:重写紧密相关。

例子
public class DispatcherTest {

    static interface Human {
        public void say();
    }
    static class Man implements Human {
        @Override
        public void say() {
            System.out.println("Class Man say hi. ");
        }
    }
    static class Woman implements Human {
        @Override
        public void say() {
            System.out.println("Class Woman say hi. ");
        }
    }

    public static void main(String[] args) {
        Human man = new Man();
        Human woman = new Woman();

        man.say();
        woman.say();
    }

}
测试结果
Class Man say hi. 
Class Woman say hi.
分析

  静态类型相同的两个变量执行相同的方法却表现了不同的行为,因为这两个变量的实际类型不同。
  实际过程:

  1. 找到操作数栈顶的第一个元素所指向的对象的实际类型A;
  2. 如果C中找到与常量中的描述符合简单名称都相符的方法,权限验证,通过则直接返回这个方法的直接引用,查找结束。如果权限不通过,则返回java.lang.IllegalAccessError
  3. 否则,按继承关系从下往上依次对A的各个父类进行第2步的搜索和验证
  4. 始终没找到,抛出java.lang.MethodNotFoundError。

单分派和多分派

  方法的接收者与方法的参数统称为方法的宗量,根据分派基于多少种宗量,可以将分派划分为单分派和多分派。单分派指依据一个宗量对目标方法进行选择,多分派则依据多于一个宗量对目标方法进行选择。

例子
public class DispatcherTest {

    static class Animal {}
    static class Cat extends Animal {}
    static class Dog extends Animal {}

    static class Man {
        public void say(Animal animal) {
            System.out.println("Class Man say animal. ");
        }
        public void say(Cat cat) {
            System.out.println("Class Man say cat. ");
        }
    }
    static class Woman extends Man {
        @Override
        public void say(Animal animal) {
            System.out.println("Class Woman say hi. ");
        }
        @Override
        public void say(Cat cat) {
            System.out.println("Class Woman say cat. ");
        }
    }

    public static void main(String[] args) {
        Animal animal = new Animal();
        Cat cat = new Cat();
        Man man = new Man();
        Woman woman = new Woman();

        man.say(animal);
        man.say(cat);
        woman.say(animal);
        woman.say(cat);
    }

}
测试结果
Class Man say animal. 
Class Man say cat. 
Class Woman say hi. 
Class Woman say cat.
分析

  首先看静态分派过程(编译阶段编译器的选择),这时选择目标方法的依据有两个:一是静态类型是Man还是Women,二是方法参数    编译期已经决定目标方法的签名,虚拟机此时不会关心传递过来的参数具体是哪个,因为这时参数的静态类型、实际类型都对方法的选择不会构成任何影响,唯一可以影响虚拟机选择的因素只有此方法的接受者的实际类型是Man还是Woman。仅有一个宗量

总结

   java是一门静态多分派,动态单分派语言,仍不是很理解。

本文链接:https://www.daguanren.cc/post/java_vm_dispatch.html

-- EOF --

Comments