序言
面向对象的三大特性为封装、继承、多态。前面的文章将了封装和继承,该文章讲最后一个特性–多态。它能使同一个操作当作用于不同的对象时,产生不同的执行结果。使用多态可以提高代码的可维护性和可扩展性。将详细介绍在 Java语言中如何实现多态、实现多态最常用的几种形式,使其代码变得更加简洁、更具灵活性。全文会使用宠物店的例子贯穿。
实现多态
简单来说, 多态是具有表现多种形态的能力的特征 。在程序设计的术语中,它意味着一个特定类型的变量可以引用不同类型的对象,并且能自动地调用引用对象的方法,也就是根据作用到的不同对象类型,响应不同的操作。
概念:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果,这就是多态性。简单的说:就是用基类的引用指向子类的对象
子类到父类的转换(向上转型)
子类到父类的转换被称为向上转型。向上转型的语法格式如下:
< 父类型 > < 引用变量名 > = new < 子类型 > () ;
向上转型的规则:
- 将一个父类的引用指向一个子类对象被称为向上转型,系统会自动进行类型转换
- 通过父类引用变量调用的方法是子类重写或继承的子类方法,不是父类的方法。
- 通过父类引用变量无法调用子类特有的方法。
例子:
假如有一个宠物店,宠物店里面有很多猫、狗,其中宠物店就是一个类,猫、狗就是子类。
宠物类Pet–父类
public class Pet {
public void Vocalization(){
System.out.println("宠物的叫声");
}
}
猫类Cat–子类
注: @Override 是Java的注解,可以帮助程序员更直观地理解和使用Java代码,可以在编译时期或运行时期检查代码中的潜在错误或不规范的用法
public class Cat extends Pet{
@Override
public void Vocalization(){
System.out.println("喵喵喵");
}
}
狗类Dog–子类
注: @Override 是Java的注解,可以帮助程序员更直观地理解和使用Java代码,可以在编译时期或运行时期检查代码中的潜在错误或不规范的用法
public class Dog extends Pet{
@Override
public void Vocalization(){
System.out.println("汪汪汪");
}
}
假如猫被人摸的话会触发叫声,那这个行为就不应该是宠物自己触发的了,所以就有了一个主人类(注:这个主人是可以摸到猫的人),这个主人类就是为了摸猫触发猫的行为
主人类Host–公共类
public class Host {
public void FeelVocalization(Pet pet){
pet.Vocalization();
}
}
测试类Test
public class Test {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
Pet pet = null;
System.out.println("请输入要领养的动物:(1、小狗 2、小猫)");
int select = input.nextInt();
if (select == 1) {
pet = new Dog();//向上转型
} else if (select == 2) {
pet = new Cat();//向上转型
}
//new一个主人类,用于使用触发宠物的行为
Host host = new Host();
host.FeelVocalization(pet);//实现了多态的调用
}
}
上面的例子实现了 子类到父类的转换(向上转型) ,向上转型是将子类的引用赋值给父类的引用,通过父类类型的引用去调用,实际执行的是子类对象上的覆盖方法(如果该方法被子类覆盖了),否则执行的是父类中定义的方法。
“host.FeelVocalization(pet)” 这句代码实现了多态的调用
实现多态的多种形式
多态性主要和 子类到父类的转换(向上转型) 有关,因为它允许父类引用指向子类对象,并通过该父类引用调用在不同子类中覆盖(重写)的方法,实现运行时多态。这是多态性的基础机制之一,使得同一操作可作用于不同对象上,产生不同的行为。
向下转型通常不涉及多态的直接实现。它是在已经向上转型之后,为了访问子类特有的成员(方法或属性)不得不做的转换。在向下转型之后,即便通过子类引用调用方法,该方法调用依然遵循 Java 的多态规则,即如果子类中有覆盖该方法,则调用子类的方法;没有则调用父类的方法。然而,向下转型本身并非是为了实现多态性,而是为了实现对特定子类的功能的访问。
总的来说多态性是通过子类到父类的转换实现的,允许父类引用指向子类对象,并调用不同子类中覆盖的方法。向下转型是为了访问子类特有的成员,而不是实现多态性。
实现方法:
- 使用父类作为方法形式参数实现多态
- 使用父类作为方法返回值实现多态
通常,使用父类作为方法形式参数的方式,是Java中实现和使用多态的主要方式之一。在上方主人类(Host)中使用的就是这种方法 ,将上方的主人类(Host)以及测试类(Test)进行修改:
主人类Host–公共类
public class Host {
//原来的代码,使用父类作为方法形式参数实现多态
/*public void FeelVocalization(Pet pet){
pet.Vocalization();
}*/
//修改后的代码,使用父类作为方法返回值实现多态
public Pet FeelVocalization(Pet pet){
return pet;
}
}
测试类Test
public class Test {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
Pet pet = null;
System.out.println("请输入要领养的动物:(1、小狗 2、小猫)");
int select = input.nextInt();
if (select == 1) {
pet = new Dog();//向上转型
} else if (select == 2) {
pet = new Cat();//向上转型
}
//new一个主人类,用于使用触发宠物的行为
Host host = new Host();
//原来的代码,使用父类作为方法形式参数实现多态
//host.FeelVocalization(pet);
//修改后的代码,使用父类作为方法返回值实现多态
pet = host.FeelVocalization(pet);
pet.Vocalization();//实现了多态的调用
}
}
因为上面已经声明过了宠物类(Pet),所以下面可以直接接着上面的继续使用。
实现多态的三个条件如下:
- 继承的存在(继承是多态的基础,没有继承就没有多态)
- 子类重写父类的方法(多态下调用子类重写后的方法)
- 父类引用变量指向子类对象(向上转型)
父类到子类的转换(向下转型)
当向上转型发生后,将无法调用子类特有的方法。这个时候就会使用父类到子类的转换(向下转型)。
将一个指向子类对象的父类引用赋给一个子类的引用,即将父类类型转换为子类类型,这被称为父类到子类的转换(向下转型),此时必须进行强制类型转换。通过强制类型转换,将父类类型转换为它的某个子类类型,然后才能调用其子类特有的属性。具体的语法如下:
< 子类型 > < 引用变量名 > = (< 子类型 >) < 父类型的引用变量 > ;
还是按照上面的宠物的例子,我们上面的子类并没有自己独有的方法,假如当人摸了猫、狗之后猫、狗都会做出一个动作。比如猫是躺下,狗是蹦跳,接下来将上面的例子重写同时使用使用父类作为方法形式参数实现多态:
宠物类Pet–父类
public class Pet {
public void Vocalization(){
System.out.println("宠物的叫声");
}
}
猫类Cat–子类
注: @Override 是Java的注解,可以帮助程序员更直观地理解和使用Java代码,可以在编译时期或运行时期检查代码中的潜在错误或不规范的用法
public class Cat extends Pet{
@Override
public void Vocalization(){
System.out.println("喵喵喵");
}
public void Action(){
System.out.println("躺下");
}
}
狗类Dog–子类
注: @Override 是Java的注解,可以帮助程序员更直观地理解和使用Java代码,可以在编译时期或运行时期检查代码中的潜在错误或不规范的用法
public class Dog extends Pet{
@Override
public void Vocalization(){
System.out.println("汪汪汪");
}
public void Action(){
System.out.println("蹦跳");
}
}
假如猫被人摸的话会触发叫声,那这个行为就不应该是宠物自己触发的了,所以就有了一个主人类,这个主人类就是为了摸猫触发猫的行为
主人类Host–公共类
public class Host {
public void FeelVocalization(Pet pet){
pet.Vocalization();
}
}
测试类Test
public class Test {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
Pet pet = null;
System.out.println("请输入要领养的动物:(1、小狗 2、小猫)");
int select = input.nextInt();
if (select == 1) {
pet = new Dog();//向上转型
} else if (select == 2) {
pet = new Cat();//向上转型
}
//new一个主人类,用于使用触发宠物的行为
Host host = new Host();
host.FeelVocalization(pet);//实现了多态的调用
//加入向下转型
if (pet instanceof Dog){
Dog dog = (Dog) pet;//向下转型
dog.Action();//调用子类的方法
}else if (pet instanceof Cat){
Cat cat = (Cat) pet;//向下转型
cat.Action();//调用子类的方法
}
}
}
上面的例子实现了 父类到子类的转换(向下转型) ,其中instanceof是接下来要讲的运算符。
instanceof 运算符
通过从父类到子类的向下转换可以实现多态,即执行不同子类中定义的特有方法。但事实上,父类对象的引用可能指向某一个子类对象,如果在向下转型时没有转换为真实的子类类型,就会出现转换异常如果出现 “ClassCastException” 就是JVM 在检测到两个类型不兼容时引发的运行异常。
那么如何通过代码来避免这种异常呢?
Java提供了instanceof 运算符来进行类型的判断。刚刚的例子就用到了instanceof 运算符,它的语法是:
对象 instanceof 类或接口
该运算符用来判断一个对象是否属于一个类或实现了一个接口,结果为 true或 false。在强制类型转换之前通过instanceof 运算符检查对象的真实类型,再进行相应的强制类型转换,这样就可以避免类型转换异常,从而提高代码的健壮性。
多态的优势
- 可替换性:多态对已存在的代码具有可替换性。
- 可扩充性:多态对代码具有可扩充性。增加新的子类不影响已存在类的多态性、继承性,以及其他特性的运行和操作。实际上新加子类更容易获得多态功能。
- 灵活性:在多态在应用中,体现了灵活多样的操作,提高了使用效率。
- 简化性:多态简化了应用软件的代码编写和修改过程,尤其在处理大量对象的运算和操作时,这个特点尤为突出。
面向对象的三大特性:封装、继承、多态
- 封装:就是声明类的成员变量为私有的,同时提供公有的方法实现对该成员变量的存取操作。
- 继承:是软件可重用性的一种表现,新类可以在不增加自身代码的情况下,通过从现有的类中继承其属性和方法充实自身内容,这种现象或行为被称为继承。
- 多态:是具有表现多种形态的能力的特征。在程序设计的术语中,它意味着一个特定类型的变量可以引用不同类型的对象,并且能自动地调用引用的对象的方法,也就是根据作用到的不同的对象类型,响应不同的操作。
总结
- 通过多态可以减少类中的代码量,可以提高代码的可扩展性和可维护性。继承是多态的基础没有继承就没有多态。
- 当实现多态时,可以使用父类作为方法的形式参数,还可以作为方法的返回值。
- 把子类转换为父类被称为向上转型,系统自动进行类型转换。把父类转换为子类,被称为向下转型,必须进行强制类型转换。
- 向上转型后,通过父类引用变量调用的方法是子类重写或继承父类的方法,通过父类引用变量无法调用子类特有的方法。
- 向下转型后可以访问子类特有的方法。向下转型必须转换为父类指向的真实子类类型:否则将出现类型转换异常 ClassCastException。
- instanceof 运算符用于判断一个对象是否属于一个类。