多态是面向对象的三大特征之一。指的是一个程序中同名的不同方法共存的情况。面向对象的多态机制可以提高程序的简洁性,也可以使系统具有更好的可扩充性。
Cartoon.java
子类:RealPoint.java
测试类:PointTest.java
运行测试程序,在控制台输入如下信息:
Child.java
运行结果如下图:
同C++一样,Java语言支持两种类型的多态性:
1.运行时的多态性
2.编译时的多态性
1.运行时的多态
通过覆盖基类中的同名成员函数(函数原型一致)来实现,调用规则是依据对象在实例化时而非定义时的类型,相应地调用对应类中的同名成员函数。
2.编译时多态
编译时的多态,Java中的静态多态性,通过重载函数来实现,调用规则是依据对象在定义时的类型相应的调用对应类中的重载函数。
多态的实现 :
方法重载:通常指在一个类中,相同的方法名对应着不同的方法实现,但是方法的参数不同。
成员覆盖:通常指在不同类(父类和子类)中,允许有相同的变量名,但是数据类型不同,也允许有相同的方法名,但是对应的方法不同。
编译时多态——重载:
在实现重载时,编译器会根据实际情况来挑选出正确的方法。如果编译器找不到匹配的参数,或者找出多个可能的匹配,就会产生编译错误。这个过程被称为重载解析。
1.普通方法的重载
重名方法重载的条件:
(1).参数个数不同
(2).对应位置上的参数个数不同
注:不允许参数完全相同而只是返回值不同的情况出现。这种情况不构成重载。
另外,访问权限修饰符以及final修饰符对于重载没有影响。
(1).方法的重载(参数个数不同)
OverloadTest.java
public class OverloadTest{
public void showMsg(){
System.out.println("a method without parameter");
}
public void showMsg(int k){
System.out.println("a method with parameter k, k="+k);
}
public static void main(String args[]){
OverloadTest oa = new OverloadTest();
//编译时的多态,方法的重载
oa.showMsg(); //调用不带参数的构造方法
oa.showMsg(100); //调用带参数的构造方法
}
编译时的多态是一种静态绑定的技术。
(2).方法重载(参数的类型不同)
Overload1.java
public class OverloadTest1{
public void showMsg(char ch){
System.out.println("This is a method with Character parameter,ch="+ch);
}
public void showMsg(int k){
System.out.printn("This is a method with integer parameter,k="+k);
}
public static void main(String args[]){
OverloadTestt1 oa = new Overload1();
//参数类型不同的重载
oa.showMsg('a');
oa.showMsg(10);
}
}
(3).静态方法和实例方法之间的重载
Overload2.java
public class Overload2{
public void showMsg(int k){
System.out.println("a method with integer para k, k ="+k);
}
public static void showMsg(double f){
System.out.println("a method with double para f="+f);
}
public static void main(String args[]){
Overload2 oa = new Overload2();
oa.showMsg(100); //调用整型参数的showMsg
oa.showMsg(3.24); //调用浮点参数的showMsg
}
}
2.构造方法的重载
与普通函数的重载相同,只是构造方法的修饰符不能是static,final。
编译器对重载的解析
方法的重载是由编译器决定执行哪一个函数的。编译器确定重载的函数唯一的依据是参数列表。确定的过程称为重载解析。
重载解析的步骤:
1.根据调用的方法名,查找是否有定义好的同名方法,若没有则报错
2.比较形参和实参的数目是否相等。如果没有,报错!有多个,则全都进入了候选集。
3.候选集中的函数与调用的函数中,相应位置的参数类型进行匹配。如果完全匹配或者通过扩展转换后匹配,都是可行的方法。
4.在可行的方法集中选取最佳的可行方法。
选取规则:
(1).若每一个参数都是完全匹配的,此方法即为最佳方法。
(2).若某方法的每一个参数匹配都不比别的方法差,且至少有一个参数比别的方法好,它就是最佳方法。
扩展转换的两条路径:
1.byte-->short-->int-->long-->float-->double
2.char-->int-->long-->float-->double
源类型和目标类型距离越近,则这种转换越好。
重载和覆盖的区别:
1.重载和覆盖的方法名称都相同,但是重载要求参数列表不同,而覆盖则要求参数列表完全相同。
2.重载对于方法前面的修饰符没有限制,而覆盖对这些修饰符有限制。
3.
同一类中的方法能够相互重载,但是不能覆盖。子类对父类方法既可以重载也可以覆盖。
4.重载时,编译器在编译期间就可以确定哪一个方法将会被执行。而覆盖则可能需要在运行时才能确定。
重载是类的多态性的一种表现形式,覆盖是父类和子类之间多态性的一种表现形式。
运行时多态
运行时的多态是实际意义上的多态。方法的调用不必在编程的时候确定,而是在运行的时候有系统来确定。
方法被调用时,系统根据当时对象本身所属的类来确定调用哪个方法。这种技术成为后期(动态)绑定。这会降低程序的运行效率,一般只在子类对父类方法进行覆盖时才使用。
实现多态的一个原理:Java中子类对象给父类对象赋值。反之,错误。
当父类的对象调用方法时,Java中采用动态绑定来处理这类问题。在调用方法时,该变量是什么对象,就调用该对象所属类的方法,与变量声明时所属的类无关。
运行时多态的示例;
Box.java
package com.leo.polymorphism;
public class Box {
public void getMsg() {
System.out.println("This is a method in Box");
}
}
Cartoon.java
package com.leo.polymorphism;
public class Cartoon extends Box {
public void getMsg(){
System.out.println("This is a method in Cartoon.");
}
public void normal(){
System.out.println("This is a normal method in Cartoon.");
}
public static void main(String[] args) {
Cartoon pCartoon ;
Box pBox ;
pCartoon = new Cartoon() ;
pBox = new Cartoon();
pBox.getMsg(); //通过父类的对象来调用了子类中覆盖父类的方法getMsg
// pBox.normal(); //不能通过父类的对象来调用子类中特有的方法
pCartoon.normal();
}
}
运行结果如下图:
可以看到,pBox是一个父类的引用。可以将子类创建的对象赋值给父类的引用变量。通过父类的引用变量可以访问子类中的方法。但是不可以访问子类中父类没有的方法。子类中独有的方法只能通过子类的引用来访问。这说明,将子类对象赋值给父类的引用,其中子类的独有的部分,被舍弃了。(赋值有损失)
运行时的多态是指对方法的调用会因为变量的实际类而不同,对于类中的成员变量,则没有运行时的多态性。
成员变量没有运行时的多态!!!
父类的引用就会调用父类的成员变量,子类的成员也会自动调用子类的成员。这与引用所指向的是父类还是子类无关!
父类:BasePoint.java
package com.leo.polymorphism;
public class BasePoint {
int x ;
int y ;
public BasePoint(){
x = 0 ;
y = 0 ;
}
void move(int dx, int dy){
x += dx ;
y += dy ;
}
public int getX(){
return x ;
}
public int getY(){
return y ;
}
}
子类:RealPoint.java
package com.leo.polymorphism;
public class RealPoint extends BasePoint{
float x ;
float y ;
RealPoint(){
x = 0.0f ;
y = 0.0f;
}
void move(int dx, int dy){
move((float)dx, (float)dy);
}
void move(float dx, float dy){
x += dx ;
y += dy ;
}
public int getX(){
return (int)Math.floor(x) ;
}
public int getY(){
return (int)Math.floor(y);
}
}
测试类:PointTest.java
package com.leo.polymorphism;
import static org.junit.Assert.*;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class PointTest {
@Test
public void test() {
RealPoint rp = new RealPoint();
BasePoint bs = rp;
rp.move(1.71828f, 4.1435534f);
bs.move(1, -1);
//变量没有运行时多态,编译的时候根据引用变量的类型,决定调用的成员。
show(bs.x,bs.y); //父类的成员变量
show(rp.x,rp.y); //子类的成员变量
//运行时多态,调用的都是子类的方法。
show(bs.getX(),bs.getY());
show(rp.getX(),rp.getY());
fail("Not yet implemented");
}
static void show(int x, int y){
System.out.println("("+x+","+y+")");
}
static void show(float x, float y){
System.out.println("("+x+","+y+")");
}
}
说明了,对于成员变量没有运行时的多态。
静态方法运行时的状况
静态方法没有运行时的多态性,这个和实例方法不同的!!!
实例:
Father.java
package com.leo.polymorphism;
public class Father {
public static void showMsg(){
System.out.println("我是父类");
}
public void greeting(){
System.out.println("你好,子类。--来自父类");
}
}
Child.java
package com.leo.polymorphism;
public class Child extends Father{
public static void showMsg(){
System.out.println("我是子类");
}
public void greeting(){
System.out.println("你好,父类。--来自子类");
}
public static void main(String[] args) {
Father fa = new Child();
fa.showMsg();
fa.greeting();
}
}
运行结果如下图:
父类的引用直接调用了父类的方法,尽管引用的对象时子类对象。
实例方法实现了多态。静态方法没有多态,仅与引用类型有关。
解释:实例方法总是和某个对象绑定在一起,而静态方法则没有与某个对象绑定在一起,因此不会因为运行的类型不同而产生区别。