个人学习记录,欢迎大家指导
什么是多态?
一个引用变量,它可以引用任何子类的对象,这个引用变量是多态的。
绑定
将一个方法调用与对应方法主体关联起来被称为绑定。(也就是,执行一条方法调用语句时所对应执行的方法体,叫做该方法体和这条方法调用语句绑定了)
动态绑定
来看一段代码
public class Parent {public void m(){System.out.println("父类方法执行");}public static void main(String[] arg){List<Parent> list = Arrays.asList(new A(),new B(),new C());for(Parent p : list){p.m();}}
}class A extends Parent{public void m(){System.out.println("A类方法执行");}
}class B extends Parent{public void m(){System.out.println("B类方法执行");}
}class C extends Parent{public void m(){System.out.println("C类方法执行");}
}output:
A类方法执行
B类方法执行
C类方法执行
何为动态绑定?
上述代码中,第一次循环,p变量接受一个A对象,p.m()语句调用A的m()方法,第二次循环时,p变量接受一个B对象,p.m()语句调用B的m()方法,第三次循环时,p变量接受一个C对象,p.m()语句调用C的m()方法,3次循环都是执行的p.m()语句,每次循环p.m()语句绑定的方法体不一样,这种运行时根据变量p实际引用对象动态绑定方法体的方式,叫做动态绑定。动态绑定是为了执行子类重写的方法。
静态绑定
对于static、final、private修饰的方法,以及构造方法,这些方法不可被子类重写,它们采用静态绑定。
方法调用的具体过程
看一段代码
public class Fu {public void m(Object obj){System.out.println("父类Object方法执行");}public static void main(String[] args){Fu f = new Zi();f.m(1);}}class Zi extends Fu{public void m(Object obj){System.out.println("子类Object方法执行");}public void m(Integer i){System.out.println("子类Integer方法执行");}
}output:
子类Object方法执行
为啥选择子类的m(Object)方法执行,而不是m(Integer)方法执行呢,你肯定想说因为向上转型,不能执行子类特有的方法,确实是这样,但为什么呢?
- 来看一下方法调用的过程
编译期:
1、编译器根据变量f声明的类型Fu,去Fu类中查找名字为m的方法。
2、此时可能找到多个名为m的方法,因为方法是可以重载的,编译器再根据调用方法时提供的参数类型,去找到最匹配的方法,在这里是m(Object)方法,
这个过程叫做重载解析。重载解析后,编译器获得了方法的签名(方法名+形参类型)。
3、如果方法是被static、final、private修饰的,该方法不能被重写,f.m(1)运行时,无论f引用什么子类对象,都是执行的父类的方法,此时可以确定运行时无论f的对象是什么都是执行父类中的方法,绑定Fu类中的m(Object)方法。此为前期绑定,也叫静态绑定,是编译期时发生的绑定。
4、如果方法不被static、final、private修饰,编译器采用动态绑定,记录方法签名m(Object)。
运行期:
执行f.m(1)字节码时
1、如果是静态绑定,直接执行绑定的方法,不关心对象类型。
2、如果是动态绑定,编译期只确定了方法签名,方法所在类未确定,根据f引用对象的实际类型,去该类型中找到签名为m(Object)的方法,执行该方法,此时运行时才把方法调用与具体方法体关联起来。
动态绑定就是运行的时候才决定执行哪个方法体,静态绑定在编译期就确定了要执行的方法体。
动态绑定在编译期确定执行的方法签名,在运行期确定执行哪个类的方法,实现自由执行子类重写的方法。静态绑定在编译期决定执行哪个类中的哪个方法,因为这些方法不能被重写,子类对象调用也是调用的父类中的,可以明确方法体,对于这类不能重写的方法,采用静态绑定性能更好,虽然采用动态绑定也能实现。
一个静态绑定的例子
public class Fu {private void m(){System.out.println("父类private方法执行");}public static void main(String[] args){Fu f = new Zi();f.m();}
}public class Zi extends Fu{public void m(){System.out.println("子类public方法执行");}public static void main(String[] args){Fu f = new Zi();// f.m(); 无法编译}
}output:
父类private方法执行
补充一个继承泛型类的例子
public class GenericClazz<T> {public void m(T t){System.out.println("泛型类中m()方法执行");}
}public class Zi extends GenericClazz<Integer>{public void m(Integer i){System.out.println("子类中m()方法执行");}// public void m(Object i){
// System.out.println("子类中m()方法执行");
// } 无法编译public static void main(String[] args){GenericClazz f = new Zi();f.m(1);}
}output:
子类中m()方法执行
泛型类中,由于有泛型擦除,T实际会被在编译时替换为Object类型,那么Zi类继承GenericClazz类就是继承的m(Object)方法,而m(Integer)是对m(Object)的重载而不是重写,在用f.m(1)调用时,按照上面的理论,运行时应该是调用的Zi类中的m(Object)方法,而m(Object)子类没有重写,应该走父类语句,但是这里却走了子类的m(Integer)方法,原因是编译器自动在子类做了m(Object)方法的重写,会执行类似下面的语句:
public void m(Object t){m((Integer) t);
}
让m(Integer)看起来就像是重写的父类的m(Integer)一样,并且还关闭了子类对m(Object)的手动重写,可以看到上面手动重写m(Object)编译是报错的。