您的位置:首页 > 编程语言 > Java开发

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  运算符前面操作数的编译时类型要么与后面的类相同,要么与后面的类具有父子继承关系,否则会引发编译错误。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息