Java多态详解
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
Java多态详解
多态是⾯向对象程序设计三⼤特征之⼀,所谓多态就是指程序中定义的引⽤变量所指向的具体类型和通过该引⽤变量发出的⽅法调⽤在编程时并不确定,⽽是在程序运⾏期间才确定,即⼀个引⽤变量倒底会指向哪个类的实例对象,该引⽤变量发出的⽅法调⽤到底是哪个类中实现的⽅法,必须在由程序运⾏期间才能决定。
因为在程序运⾏时才确定具体的类,这样,不⽤修改源程序代码,就可以让引⽤变量绑定到各种不同的类实现上,从⽽导致该引⽤调⽤的具体⽅法随之改变,即不修改程序代码就可以改变程序运⾏时所绑定的具体代码,让程序可以选择多个运⾏状态,这就是多态性。
1、向上转型
要理解多态就必须理解向上转型,考虑以下例⼦,shape1和shape2是Shape类型,分别赋值的为Shape的两个⼦类Circle和Rectangle的实例,这说明Circle和Rectangle是可以转型为Shape类型,所以shape可以指向Circle和Rectangle的实例,这就是向上转型。
1public class Shape{
2void draw(){}
3 }
4
5public class Rectangle extends Shape {
6void draw() {
7 System.out.println("draw Rectange");
8 }
9 }
10
11public class Circle extends Shape {
12void draw() {
13 System.out.println("draw circle");
14 }
15 }
16
17public class Test {
18public static void main(String []args) {
19 Shape shape1 = new Circle();
20 Shape shape2 = new Rectangle();
21
22 shape1.draw();
23 shape2.draw();
24 }
25 }
out:
draw circle
draw Rectange
2、动态绑定
上⾯的例⼦中,shape1和shape2调⽤draw,可以从输出看出,实际调⽤的是⼦类的draw,⽽不是shape1和shape2本⾝的类型Shape 的draw,这是为什么呢?这⾥必须要知道函数绑定的概念,将⼀个函数调⽤与函数主题连接在⼀起就叫做函数绑定(binding),若在程序运⾏之前执⾏绑定(编译阶段和链接阶段),叫做早期绑定,⽐如熟悉的C语⾔;若在程序运⾏时执⾏绑定,就叫做动态绑定(后期绑定),这样即可以在运⾏时确定对象的类型,并正确调⽤相应的函数,不同的语⾔对于动态绑定的实现时不⼀样的,java中的绑定采⽤的都是动态绑定(final函数除外,final⽅法不能被继承)。
从上⾯的例⼦可以看出,虽然shape1和shape2是Shape类型,但是在运⾏过程中确可以准确的知道shape1和shape2指向实例的实际类型,并且正确的调⽤了相应的draw函数。
3、私有⽅法的“多态”
考虑下⾯的例⼦,Son继承了Father的私有函数,但是最后的结果中却没有按照我们“意想”的那样调⽤Son的f(),这位因为private⽅法会被默认为final⽅法,final是不能被继承的,⽽且private⽅法对于继承类是不可见的,在这种情况下,Son的f()⽅式其实是⼀个全新的⽅法,所以此处调⽤的任然是Father的f()⽅法。
注意:只有⾮private⽅法是可以继承的,同时需要注意覆盖private⽅法的情况,这种情况下编译器不会报错,但是也不会像我们“意想”的那样执⾏,所以,在导出类中,对于基类的private⽅法,最好采⽤不同的名字
1//Father.java
2public class Father {
3private void f() {
4 System.out.println("this is Father");
5 }
6public static void main(String []args) {
7 Father father = new Son();
8 father.f();
9 }
10 }
11
12//Son.java
13public class Son extends Father {
14public void f() {
15 System.out.println("this is Son");
16 }
17 }
18
19 out:
20this is Father
4、静态⽅法
静态⽅法不具有多态性,静态⽅法是与类,⽽不是与单个的对象相关联的。
不能在继承中更改静态属性,即⽗类中的⽅法是static,⽽⼦类中同名的⽅法确实⾮static(或者⽗类是⾮static⽅法,⼦类同名的⽅法是static),此时编译器会直接报错
5、域
只有普通的⽅法调⽤时多态,域访问操作都由编译器解析,所以域是不具有多态性,考虑如下例⼦,直接访问域时,father.field的输出为0,son.fiedl的输出为1,说明域并没有多态性,后⾯运⾏f()⽅法时,输出皆为1,说明普通⽅法的调⽤的多态。
在域的继承时,Father.field和Son.field分配了不同的存储空间,这样,实际上Son中包含了两个field域,它⾃⼰的和它从Father继承来的,在Son的引⽤中的field默认时⾃⼰的field,⽽不是从⽗类继承的field,⽽要访问从⽗类继承的field,必须显式使⽤super.field,如下⽅法prtSuperField()中所⽰。
在实际的应⽤中,对于应尽量定义成private,同时在继承中应当避免在导出类中使⽤与⽗类的域相同的名字。
//Father.java
public class Father {
public int field = 0;
public void f() {
System.out.println("this is Father, field = " + field);
}
public static void main(String []args) {
Father father = new Son();
Son son = new Son();
System.out.println("father.field = " + father.field);
System.out.println("son.fied = " + son.field);
father.f();
son.f();
son.prtSuperField();
}
}
//Son.java
public class Son extends Father {
public int field = 1;
public void f() {
System.out.println("this is Son, field = " + field);
}
public void prtSuperField() {
System.out.println("this is son, fater.field = " + super.field);
}
}
//out:
father.field = 0
son.fied = 1
this is Son, field = 1
this is Son, field = 1
this is son, fater.field = 0
6、构造器的“多态”
构造器不同于其他⽅法,构造器实际上是隐式的static,所以构造器实际上是不具有多态。
考虑⼀种特殊的场景,在构造器中调⽤正在构造的对象的某个动态绑定⽅法,即⽗类的构造器调⽤了⽅法f(),同时⼦类重写了⽅法f(),根据构造器的调⽤顺序,⼦类初始化的时候,会优先调⽤⽗类的构造器,然⽽此时出现了特殊的情况,⽗类的构造器中调⽤了⽅法f(),根据多态性,此时我们认为应该调⽤⼦类的f()⽅法,但是,此时⼦类对象并没有完成初始化,即⼦类的⽅法f()在⼦类被构造之前被调⽤了,这可能导致⼀些难以预料到的错误,如下例⼦:
//Father.java
public class Father {
public Father() {
System.out.println("this is father before f()");
f();
System.out.println("this is father after f()");
}
public void f() {
System.out.println("this is Father, in f()");
}
public static void main(String []args) {
Father father = new Son();
}
}
//Son.java
public class Son extends Father {
public int field = 1;
public void f() {
System.out.println("this is Son, in f()");
}
}
//out
this is father before f()
this is Son, in f()
this is father after f()
7、协变返回类型
所谓的协变返回类型是在java se5中加⼊的新特性,即导出类的重写的⽅法的返回值可以是基类对应⽅法的返回值的某种导出类,说起来⽐较绕,举个简单的例⼦,前⾯我们使⽤Shape类和Circle类,Circle继承于Shape,假设在Father类中有⼀个⽅法f(),返回值为Shape,那么在Son类中重写⽅法f()时,返回值可以为Circle(以及其他Shape的导出类)。
8、⽅法重载
既然⼦类可以覆盖⽗类中的⽅法,那么重载的情况呢,考虑如下例⼦,⼦类“重写”了⽅法f(),但是⼊参不⼀样,但是输出结果,却没有调⽤⼦类的⽅法,这是怎么回事呢?在java中,⽅法重载虽然⽅法名是⼀样,但是实际是不同的⽅法,所以在Son中的f(char)其实和⽗类的f(int)是不同的⽅法,⾃然不会发⽣动态绑定,所以Father得引⽤调⽤的永远是⾃⼰的f(int)⽅法
//Father.java
public class Father {
public void f(int c) {
System.out.println("this is Father" + c);
}
public static void main(String []args) {
Father father = new Son();
father.f('a');
father.f(1234);
}
}
//Son.java
public class Son extends Father {
public int field = 1;
public void f(char x) {
System.out.println("this is Son" + x);
}
}
//out
this is Father97
this is Father1234
在考虑以下例⼦,当引⽤换成了Son时,根据⼊参的类型分别调⽤了⽗类的f(char)和⾃⼰的f(int),这说明在Son中存在两个f()⽅法,继承⽗类的和⾃⼰新写的,根据⼊参的不同,调⽤相应的⽅法
//Father.java
public class Father {
public void f(int c) {
System.out.println("this is Father " + c);
}
public static void main(String []args) {
Son son = new Son();
son.f('a');
son.f(1234);
}
}
//Son
public class Son extends Father {
public int field = 1;
public void f(char x) {
System.out.println("this is Son " + x); }
}
//out
this is Son a
this is Father 1234。