10.1多态
何为多态
多态(polumorphism)性是面向对象程序设计代码的一个重要机制。java语言的多态总是子类型的多态。 在面向对象中,通常把多态分为两大类(特定的、通用的),四个小类(强制的、重载的、参数的、包含的),如图:
在这样一个体系中,多态表现为多种形式的能力。
通用的多态:引用有相同结构类型的大量对象,他们有共同的特性。
特定的多态:小部分没有相同特征的对象。
强制的:一种隐式实现类型转换的方法。
重载的:将一个标志符用作多个意义。
参数的:为不同类型的参数提供相同的操作。
包含的:类包含关系的抽象操作。
(1)强制的多态:强制多态隐式将参数按照某种方法,转换成编译器认为正确的类型已避免错误。
2.0+2.0
2.0+2
2.0+“2”
第一个表达式将两个double操作数相加。
第二个表达式编译器隐式的将第二个操作数转换为double型。
第三个表达式,编译器将double转换成String类型,并将它们串联。
C c=new C();
Derived derived=new Derived();
c.m( derived );
在方法调用中,假设类Derived继承了Base,类C有一个原型为m(Base)的方法,则编译器隐式的将类Derived的对象derived转换为类Base的对象。这种隐式的转换使方法m(Base)使用所有能转换成Base类的所有参数。
(2)重载的多态:例如“+”在上面程序有两个意思,一是两个double型数相加,二是表示两个串相连。这些运算符的重载依赖于编译器根据上下文做出的选择。但不支持用户定义的操作符重载。
Java支持用户定义的函数重载,在一个类中可以有相同名字的方法,但方法可以有不同的参数。
(3)参数的多态:参数多态允许把许多类型抽象成单一的表示。例如在一个名为List的抽象类中,描述了一组具有同样特征的对象,提供一个通用的模板,可以通过指定一种类型以重用这个抽象类。
(4)包含的多态:包含多态是通过值的类型和集合的包含关系实现了多态的行为。
演示java中的多态
例子:
//定义基类jiBaseClass
class jiBaseClass {
public int book = 6;
public void base() {
System.out.println("老邓头说:父类的普通方法:");
}
public void test() {
System.out.println("父类的被覆盖的方法:");
}
}
public class duptai extends jiBaseClass {
public String book = "<青年公寓>";
public void test() {
System.out.println("子类的覆盖父类的方法:");// 子类中重写方法test()
}
public void sub() {
System.out.println("子类的普通方法:");
}
public static void main(String args[]) {
//下面编译时类型和运行时类型一样,因此不存在多态
jiBaseClass bc = new jiBaseClass();
System.out.println(bc.book);
//下面两次调用将执行jiBaseCase方法
bc.base();
bc.test();
//下面编译时类型和运行时类型一样,因此不存在多态
duptai sc = new duptai();
System.out.println(sc.book);
//下面调用将执行从父类继承的base方法
sc.base();
//下面调用将执行当前类的text方法
sc.test();
//下面调用将执行当前类的sub方法
sc.sub();
//下面编译时类型和运行时类型不一样,多态发生
jiBaseClass ploymophicBc = new duptai();
System.out.println(ploymophicBc.book);
//下面调用将执行从父类继承的base方法
ploymophicBc.base();
//下面调用将执行当前类的sub方法
ploymophicBc.test();
//jiBaseClass类没有提供sub方法,因为sanYin的编译类型是jiBaseClass
//所以下面代码编译时会出错
//sanYin.sub()
}
}
结果:
6
老邓头说:父类的普通方法:
父类的被覆盖的方法:
<青年公寓>
老邓头说:父类的普通方法:
子类的覆盖父类的方法:
子类的普通方法:
6
老邓头说:父类的普通方法:
子类的覆盖父类的方法:
第三个引用变量ploymophicBc,编译时类型是jiBaseClass,运行时类型是duptai,所以ploymophicBc.book是父类中的,ploymophicBc.base()是父类中的,ploymophicBc.test()因为子类重写了,所以用子类重写的!
因为:
编写代码时,只能调用父类中具有的方法,如果子类重写了该方法,运行时实际调用的是运行时类型的该方法。
(------------------------------------------------------------------------------------------------------------------)
补充一下:编译类型和运行类型
class Father {
Father() {
System.out.println("父类构造器");
}
public void doWork() {
System.out.println("父类doWork方法");
}
}
class son extends Father {
son() {
System.out.println("子类构造器");
}
public void doWork() {
System.out.println("子类doWork方法");
}
}
public class runORbian {
public static void main(String[] args) {
Father child = new son();
child.doWork();
}
}
其中代码段:
Father child = new son();
child这个引用变量的Father类就是编译类型,而 new son()则是运行类型。
在程序运行时,首先会调用父类构造器,再调用子类构造器。在编译时,就会自动检查引用变量child的编译类型中是否包含doWork方法,很明显,在父类中有doWork方法(如果没有会报错),但是,在JVM运行时,由于在子类中覆盖了doWork方法,所以child实际运行时,是调用了子类的doWork方法,而不是父类的,这也就是多态的一种(运行时多态)。
原话是:引用变量在编译阶段只能调用其编译时类型所具有的方法,但在运行时则去执行它运行时类型所具有的方法。
编写代码时,只能调用父类中具有的方法,如果子类重写了该方法,运行时实际调用的是运行时类型的该方法。
(------------------------------------------------------------------------------------------------------------------)
再举个例子:
代码:
class Father {
int a=100;
public void doWork() {
System.out.println("父类doWork方法");
}
public void test() {
System.out.println("父类test方法子类没有");
}
}
class son extends Father {
int a=99;
public void doWork() {
System.out.println("子类重写了的doWork方法");
}
public void noone() {
System.out.println("子类noone方法");
}
}
public class runORbian {
public static void main(String[] args) {
Father child = new son();
System.out.println(child.a);
child.doWork();
child.test();
}
}
输出结果:
100
子类重写了的doWork方法
父类test方法子类没有
其中100是父类中的,即使子类重写,但他不是方法。
第二个输出是因为子类重写了父类的方法。
第三个输出是因为子类没有父类的方法。
子类中noone函数输出不了是因为父类中没有。