Java多态详解
2018-03-15 03:15
85 查看
Java多态详解
一 . 多态概述
在面向对象的程序设计语言中,多态是继数据抽象和继承之后的第三种基本特征。多态通过分离做什么和怎么做,从另一个角度将接口和实现分离开。多态不但能够改善代码的组织结构和可读性,还能够创建可扩展的程序--即无论在项目最初创建时还是在需要添加新功能时都可以“生长”的程序。多态的作用时消除类型之间的耦合关系。它允许将多种类型(从同一基类导出的)视为同一类型来处理,而同一份代码也就可以毫无差别地运行在这些不同类型之上了。多态方法调用允许一种类型表现出与其它相似类型之间的区别,只要它们都是从同一类导出来的。这种区别是根据方法行为的不同而表示出来的,虽然这些方法都不可以通过同一个基类来调用。
Java引用变量有两个类型:一个是编译时类型,它由声明该变量时使用的类型决定。另一个时运行时类型,它由实际赋给该变量的对象决定。如果编译时类型和运行时类型不一致,就可以认为是多态出现了。
二 . 引用变量的向上转型
看下面的程序:class Shape{
public int length=6;
public void draw()
{
System.out.println("父类的普通方法");
}
public void erase()
{
System.out.println("父类中被覆盖的方法");
}
}
public class Circle extends Shape{
//重新定义一个length实例变量隐藏父类的length实例变量
public int length=8;
public void test()
{
System.out.println("子类的普通方法");
}
public void erase()
{
System.out.println("子类覆盖父类的方法");
}
public static void main(String [] arge)
{
//编译时类型和运行时类型相同,不存在多态
Shape shape=new Shape();
//输出6
System.out.println(shape.length);
//输出"父类的普通方法"
shape.draw();
//输出"父类中被覆盖的方法"
shape.erase();
//编译时类型和运行时类型相同,不存在多态
Circle circle=new Circle();
//输出8
System.out.println(circle.length);
//输出"子类的普通方法"
circle.test();
//输出"子类覆盖父类的方法"
circle.erase();
//编译时类型和运行时类型不相同,多态发生
Shape shapeCircle=new Circle();
//输出6,访问的是父类对象的实例变量
System.out.println(shapeCircle.length);
//输出"子类的普通方法",访问的是从父类继承到的 erase() 方法
shapeCircle.erase();
//因为shapeCircle的编译时是Shape,Shape类没有提供test()方法,所以下面代码编译时会报错
//下面是对 shapeCircle 进行强制类型转换为 Circle 类型,才能调用运行时类型的 erase() 方法
((Circle)shapeCircle).erase();
}
} 上面程序的类图如下:
因为子类是一种特殊的父类,因此Java允许把一个子类对象直接赋给一个父类引用变量,无须任何类型转换,或者被称为向上转型,向上转型由系统自动完成。当把一个子类对象直接赋给父类引用变量时,例如上面第一个程序的 Shape shapeCircle=new Circle( ) ; ,这个 shapeCircle 引用变量的编译时类型是 Shape ,而运行时类型是 Circle ,当运行时调用该引用变量时,其方法行为总是表现出子类方法的行为特征,这就是多态。
与方法不同的是,对象的实例变量不具备多态,即通过引用变量来访问其包含的实例变量时,系统总是试图访问它编译时类型所定义的成员变量,而不是运行时类型所定义的成员变量,如上面的 System.out.println(shapeCircle.length) , 输出的时子类的实例变量。
接下来再看一个多态的例子。class Person{
public void work()
{
System.out.println("Person.work()");
}
}
class Teacher extends Person{
public void work()
{
System.out.println("Teacher.work()");
}
}
public class DemoTest extends Person{
public static void base(Person p)
{
p.work();
}
public static void main(String [] arge)
{
Teacher teacher=new Teacher();
base(teacher);
}
} 这也是经常见到的一种向上转型的例子:DemoTest.base( ) 方法接受一个 Person 引用,同时也接受任何导出自 Person 的类。在 main( ) 方法中,当一个 Teacher 引用传递到 base( ) 方法时,就会出现向上转换,而不需要任何类型转换。这样做是自动完成的,因为 Teach 从 Person 继承而来,所以 Person 的接口必定存在于Teacher 中。从 Teacher 向上转型到 Person 可能会“缩小”接口,但不会比 Person 的全部接口更窄。
三 . 引用变量的强制类型转换(向下转型)
通常情况下,引用变量只能调用它编译时类型的方法,而不能调用它运行时类型的方法,即使它实际所引用的对象确实包含该方法。如果需要让这个引用变量调用它运行时类型的方法,则必须把它强制类型转换成运行时类型,强制类型转换需要使用类型转换运算符--小括号。用法是:(type)variable ,意思是将 variable 变量转成一个 type 类型的变量。如上面程序中,就是利用向下转型,使 shapeCircle 能够调用它运行时类型的方法,即 erase() 方法。
运用好强制类型转换,确实能省事不少,但是这种强制类型转换不是盲目的,还是有一些地方需要注意:
基本类型之间的转换只能在数值类型之间进行,如:整数型、字符型和浮点型。但数值类型和布尔类型之间不能进行类型转换。
引用类型之间的转换只能在具有继承关系的两个类型之间进行,如果是两个没有任何继承关系的类型,则无法进行类型转换,否则编译时就会出现错误。如果试图把一个父类实例转换成子类类型,则这个对象必须实际上是子类实例才行(即编译时类型为父类类型,而运行时类型时子类类型),否则将在运行时引发 ClassCastException 异常。
四 . instanceof 运算符
在进行强制类型转换之前,先用 instanceof 运算符判断是否可以成功转型,从而避免出现 ClassCastException 异常。instance 运算符的前一个操作数通常是一个引用类型变量,后一个操作数通常是一个类(也可以是接口),它用于判断前面的对象是否是后面的类,或者其子类、实现类的实例。如果是,则返回 true ,否则返回 false 。
在使用 instanceof 运算符时需要注意:instanceof 运算符前面操作数的编译时类型要么与后面的类相同,要么与后面的类具有父子继承关系,否则会引发编译错误。
相关文章推荐
- Java 多态详解
- java详解final、多态、抽象类、接口原理
- Java学习笔记(33)-- Java 多态的详解
- Java中final和多态调用成员变量、成员方法的应用(基础详解)
- java继承、多态实题详解加载顺序
- Java基础之封装、继承、多态、接口详解
- java中多态概念、实现原理详解
- java多态详解
- 【转】java多态详解
- [置顶] JAVA从零单排4-----继承、封装和多态详解
- 【Java】Java的继承和多态详解
- java详解 --- 多态
- JAVA中继承、封装和多态详解
- java中多态的详解
- Java面向对象的三大特征:封装、继承和多态的详解
- java 学习笔记——类之间的关系之封装、继承与多态的详解
- Java的多态详解
- Java 多态的详解
- Java中继承、接口、多态的作用详解(纯理论)
- java多态详解(接口指向实现类)