根据何时确定执行多态方法中的哪一个,多态分为两种情况:编译时多态和运行时多态。如果在编译时能够确定执行多态方法
中的哪一个,称为编译时多态,否则称为运行时多态。
一、编译时多态
方法重载都是编译时多态。根据实际参数的数据类型、个数和次序,Java在编译时能够确定执行重载方法中的哪一个。
方法覆盖表现出两种多态性,当对象引用本类实例时,为编译时多态,否则为运行时多态。例如,以下声明p、m引用本类实例,调用toString()方法是编译时多态。
- public class Test {
- public static void main(String[] args) {
- Person p = new Person(); //对象引用本类实例
- Man m = new Man(); //编译时多态,执行Person类的toString()
- System.out.println(p.toString());
- System.out.println(m.toString()); //编译时多态,执行Man类的toString()
- }
- }
- class Person{
- public String toString() {
- String name = "Person";
- return name;
- }
- }
- class Man extends Person{
- public String toString(){
- String name = "Man";
- return name;
- }
- }
二、运行时多态
1.当以下父类对象p引用子类实例时,p.toString)执行谁的setName()方法?
- Person p = new Man();
- p.toString();
Java支持运行时多态,意为p.toString()实际执行p所引用实例的toString(),究竟执行Person类还是Man类的方法,运行时再确定。如果Man类声明了toString()方法,则执行之;否则执行Person类的toString()方法。
程序运行时,Java从实例所属的类(new 类)开始寻找匹配的方法执行,如果当前类中没有匹配的方法,则沿着继承关系逐层向上,依次在父类或各祖先类中寻找匹配方法,直到Object类。寻找p.toString()匹配执行方法的过程如下图所示。
2.将上述例子中toString方法改为getName,因为在Object类中有toString类,无法测试Person与Man中所匹配的执行方法。
- public class Test { //例子2
- public static void main(String[] args) {
- Person p = new Man();
- System.out.println(((Man) p).getName()); //返回结果为Man
- }
- }
- class Person{}
- class Man extends Person{
- public String getName(){
- String name = "Man";
- return name;
- }
- }
此例中Person类型要引用Man类的实例,因Person中未定义setName()方法,故需要把Person类显式地转换为Man类,然后调用Man中的getName方法。
3.将例子1中Person和Man的方法名改为静态的getName()方法,会返回什么结果呢?
- public class Test { //例子3
- public static void main(String[] args) {
- Person p = new Man();
- System.out.println(p.type); //返回结果为P
- System.out.println(p.getName()); //返回结果为Person
- }
- }
- class Person{
- String type = "P";
- public static String getName() {
- String name = "Person";
- return name;
- }
- }
- class Man extends Person{
- String type = "M";
- public static String getName(){
- String name = "Man";
- return name;
- }
- }
栗子中子类Man隐藏父类Person的属性,而 Person p = new Man() 表示“先声明一个Person类的对象p,然后用Man类对 p进行实例化”,即引用类型为Person类,实际代表的是Man类。因此,访问的是Person的属性及静态方法,详细解释如下。
所谓静态,就是在运行时,虚拟机已经认定此方法属于哪个类。“重写”只能适用于实例方法,不能用于静态方法。对于静态方法,只能隐藏,重载,继承。
子类对于父类静态方法的隐藏(hide),子类的静态方法完全体现不了多态,就像子类属性隐藏父类属性一样,在利用引用访问对象的属性或静态方法时,是引用类型决定了实际上访问的是哪个属性,而非当前引用实际代表的是哪个类。因此,子类静态方法不能覆盖父类的静态方法。
父类中属性只能被隐藏,而不能被覆盖;而对于方法来说,方法隐藏只有一种形式,就是父类和子类存在相同的静态方法。
若要转载请注明文章出处:http://blog.csdn.net/why_still_confused https://blog.csdn.net/why_still_confused/article/details/51295707
运行时多态demo:
public class Test {
public static void main(String[] args) {
//运行时多态
Father c = new SonClass();
c.method(); //父类的构造方法 子类的构造方法 子类的method()
c.method2(); //父类的构造方法 子类的构造方法 父类的method2()
}
}
class Father {
public Father() {
System.out.print("父类的构造方法\t");
}
public void method() {
System.out.print("父类的method()\t");
}
public void method2() {
System.out.print("父类的method2()\t");
}
}
class SonClass extends Father {
public SonClass() {
System.out.print("子类的构造方法\t");
}
@Override
public void method() {
System.out.print("子类的method()\t");
}
}
运行时多态性是面向对象程序设计代码重用的一个最强大机制,动态性的概念也可以被说成“一个接口,多个方法”。Java实现运行时多态性的基础是动态方法调度,它是一种在运行时而不是在编译期调用重载方法的机制,下面就继承和接口实现两方面谈谈java运行时多态性的实现。
一、通过继承中超类对象引用变量引用子类对象来实现
举例说明:
//定义超类superA
class superA
{
int i = 100;
void fun()
{
System.out.println(“This is superA”);
}
}
//定义superA的子类subB
class subB extends superA
{
int m = 1;
void fun()
{
System.out.println(“This is subB”);
}
}
//定义superA的子类subC
class subC extends superA
{
int n = 1;
void fun()
{
System.out.println(“This is subC”);
}
}
class Test
{
public static void main(String[] args)
{
superA a;
subB b = new subB();
subC c = new subC();
a=b;
a.fun(); (1)
a=c;
a.fun(); (2)
}
}
运行结果为:
This is subB
This is subC
上述代码中subB和subC是超类superA的子类,我们在类Test中声明了3个引用变量a, b, c,通过将子类对象引用赋值给超类对象引用变量来实现动态方法调用。也许有人会问:“为什么(1)和(2)不输出:This is superA”。java 的这种机制遵循一个原则:当超类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法。
所以,不要被上例中(1)和(2)所迷惑,虽然写成a.fun(),但是由于(1)中的a被b赋值,指向了子类subB的一个实例,因而(1)所调用的fun()实际上是子类subB的成员方法fun(),它覆盖了超类superA的成员方法fun();同样(2)调用的是子类subC的成员方法fun()。
另外,如果子类继承的超类是一个抽象类,虽然抽象类不能通过new操作符实例化,但是可以创建抽象类的对象引用指向子类对象,以实现运行时多态性。具体的实现方法同上例。
不过,抽象类的子类必须覆盖实现超类中的所有的抽象方法,否则子类必须被abstract修饰符修饰,当然也就不能被实例化了。
二、通过接口类型变量引用实现接口的类的对象来实现
接口的灵活性就在于“规定一个类必须做什么,而不管你如何做”。我们可以定义一个接口类型的引用变量来引用实现接口的类的实例,当这个引用调用方法时,它会根据实际引用的类的实例来判断具体调用哪个方法,这和上述的超类对象引用访问子类对象的机制相似。
举例说明:
//定义接口InterA
interface InterA
{
void fun();
}
//实现接口InterA的类B
class B implements InterA
{
public void fun()
{
System.out.println(“This is B”);
}
}
//实现接口InterA的类C
class C implements InterA
{
public void fun()
{
System.out.println(“This is C”);
}
}
class Test
{
public static void main(String[] args)
{
InterA a;
a= new B();
a.fun();
a = new C();
a.fun();
}
}
输出结果为:
This is B
This is C
上例中类B和类C是实现接口InterA的两个类,分别实现了接口的方法fun(),通过将类B和类C的实例赋给接口引用a而实现了方法在运行时的动态绑定,充分利用了“一个接口,多个方法”展示了Java的动态多态性。
需要注意的一点是:Java在利用接口变量调用其实现类的对象的方法时,该方法必须已经在接口中被声明,而且在接口的实现类中该实现方法的类型和参数必须与接口中所定义的精确匹配。
结束语:以上就是java运行时多态性的实现方法,大家在编程过程中可以灵活运用,但是在性能要求较高的代码中不提倡运用运行时多态,毕竟Java的运行时动态方法调用较之普通的方法调用的系统开销是比较大的。