在这篇 《访问者模式讨论篇:java的动态绑定与双分派》,中看到作者说的,
Java的动态绑定就是在运行期间判断做引用对象的实际类型,根据实际类型调用对应的方法。继承体系中的重写就是动态绑定的。
静态绑定是指编译期就确定执行哪一个方法,Java 中,方法的重载就是静态绑定(根据不同的方法签名)
多态
多态是同一个行为具有多个不同表现形式或形态的能力。Java中所有对象都具有多态性,因为都有一个超类 Object。
比如“宠物”这个对象有很多不同的表达或实现。比如有小猫,小狗,兔子等等,小猫小狗等都是宠物的子类,通过“宠物”就可以引用不同的子类,就称“宠物”这个对象就有多态性。看到一个不严谨的说法:继承是子类使用父类的方法,而多态是父类使用子类的方法。
package com.something;
public class Ch4_plomorphism {
public static void main(String[] args) {
Pet dog1 = new Dog();
Dog dog2 = new Dog();
dog1.show(dog2); //show:Dog:pet(改为dog1.show(dog1) 输出相同)
dog1.display(dog2); // display:Pet:pet
dog2.show(dog1); //show:Dog:pet
}
}
class Pet{
void show(Pet pet){
System.out.println("show:Pet:pet");
}
static void display(Pet Pet){
System.out.println("display:Pet:pet");
}
}
class Dog extends Pet{
//子类重写父类方法
void show(Pet pet){
System.out.println("show:Dog:pet");
}
void show(Dog dog){
System.out.println("show:Dog:dog");
}
static void display(Pet pet){
System.out.println("display:Dog:pet");
}
}
上面的例子中Pet dog = new Dog();
等价于
Pet dog1 = new Pet();
Dog dog = new Dog();
dog1=dog;//所有的dog都是pet,所以是dog1=dog,而不是dog=dog1
声明是父类,实际指向的是子类的一个对象,初始化了一个Pet类型的引用,指向一个Dog类型的对象,但在变量dog看来它指向的堆空间其实是一个 Pet(不明白。。。。)
静态类型:Pet dog = new Dog(); 中Pet 就是静态类型,Dog 为实际类型。
静态类型是编译期可知的,实际类型运行期可知的。
编译期,编译器会根据参数的静态类型决定使用哪个重载版本。比如dog2.show(dog1); 参数dog1 的静态类型是Dog ,所以与show(Dog dog) 绑定。
如果参数类型不完全一致,遵从:
最终方法调用的优先级:this.show(O)、super.show(O)(过程2中重载解析,先寻找参数类型完全匹配的方法)、this.show((super)O)、super.show((super)O)。
上个例子的调用对象方法的执行过程:
首先编译器查看对象的声明类型和方法。
对dog1.show(dog2)
,dog1声明为父类Pet 对象,编译器会将Pet对象中的名为 show 的方法和其超类中访问属性为public且名为show 的方法一一列举。 至此编译器获得所有可能被调用的候选方法。接下来,编译器将查看调用方法提供的参数类型。根据静态类型决定选择哪个重载方法。(show(Dog dog),show(Pet pet)这两个就是重载方法),选择show(Pet pet)
如果是private,static,final 方法或者构造器,编译器可以准确的知道应该调用哪个方法,这种调用方式称为静态绑定。 与之对应是,调用的方法依赖于dog1的实际类型,在运行时实现的动态绑定 。
程序运行,并采用动态绑定调用方法时 ,虚拟机根据变量的实际类型来实现重写。
dog1.show(dog2)
dog2 的实际类型是Dog,Dog中有对show(Pet pet)的重写。
上面例子中在编译时,dog1.show(dog2),首先查询dog1所指向对象的函数列表,发现只有show(Pet pet)
,并且形参类型符合转型要求,与之绑定。运行时由于多态机制,发现show(Pet pet)
在被重写,于是动态的与子类重写方法绑定。而display是类方法,所有类方法都在编译时绑定,所以类方法都是静态绑定的。display 在编译时期就已经静态绑定,没有后面的动态绑定。
附上这个无语的例子:
package com.something;
public class Ch4_test {
public static void main(String[] args) {
A a1 = new A();
A a2 = new B();
B b = new B();
C c = new C();
D d = new D();
System.out.println("1--" + a1.show(b));
System.out.println("2--" + a1.show(c));
System.out.println("3--" + a1.show(d));
System.out.println("4--" + a2.show(b));
System.out.println("5--" + a2.show(c));
System.out.println("6--" + a2.show(d));
System.out.println("7--" + b.show(b));
System.out.println("8--" + b.show(c));
System.out.println("9--" + b.show(d));
}
}
class A {
public String show(D obj) {
return ("A and D");
}
public String show(B obj) {
return ("A and B");
}
public String show(A obj) {
return ("A and A");
}
}
class B extends A{
public String show(B obj){
return ("B and B");
}
public String show(A obj){
return ("B and A");
}
}
class C extends B{
}
class D extends B{
}
理解方法调用的优先级即可知道输出:
1--A and B
2--A and B
3--A and D
4--B and B
5--B and B
6--A and D
7--B and B
8--B and B
9--A and D
变量隐藏
看下面的代码
public class Variable {
public static void main(String[] args) {
Foo f = new Bar();
f.addFive();
System.out.println(" f.a="+f.a);
}
}
class Foo {
public int a=3;
public void addFive() {
a+=5;
System.out.print("f ");
System.out.print("a in addFive="+a);
}
}
class Bar extends Foo {
public int a=8;
public void addFive() {
this.a += 5;
System.out.print("b ;");
System.out.print("in addFive a="+a);
}
}
输出b ;in addFive a=13 f.a=3
(如果将Foo 中的变量 a 删除,System.out.println(f.a);
会报错)
看到的解释是:Java中实例变量不可以被重载。 只有方法可以被重载。
其中f 是 Foo 类型的引用,变量不是多态的,因此 f.a 指向的是 Foo 的变量 3。f.addFive();
由于多态,动态绑定到子类的addFive() 方法。
子类中定义一个和父类中名字相同的字段时会覆盖父类中的字段,即使他们的类型不同。子类中只能通过super来访问父类中的字段。
参考链接:
http://www.douban.com/note/272520236/
http://alexyyek.github.io/2015/03/04/Polymorphism/
http://blog.csdn.net/ns_code/article/details/17965867
http://www.cnblogs.com/chenssy/p/3372798.html
http://stackoverflow.com/questions/12086298/why-instance-variable-of-super-class-is-not-overridden-in-sub-class-method
http://stackoverflow.com/questions/11974428/java-code-snippet-output-explanation-required