目录
在面向对象编程中,多态性是指同一个方法可以根据不同的对象类型产生不同的行为效果。这使得代码更加灵活和可扩展。
在Java中,多态性通过继承和方法重写来实现。如果一个类继承自另一个类或者实现了某个接口,那么它就可以被当做父类或接口类型来使用。当调用父类或接口定义的方法时,具体执行的是子类或实现类中重写的方法,从而实现了不同对象之间的多态性。
多态的优点
- 灵活性和扩展性:多态性使得代码更加灵活和可扩展。通过多态性,可以在不修改现有代码的情况下,通过引入新的子类或实现类来扩展系统的功能。
- 代码复用:多态性促进了代码的复用。通过定义公共的父类或接口,并在子类或实现类中重写父类或接口的方法,可以通过父类或接口类型引用来调用这些方法,从而实现对多个子类或实现类的复用。
- 提高代码的可读性和可维护性:多态性可以提高代码的可读性和可维护性。通过使用父类或接口类型引用,可以更清晰地表达代码的意图,而不需要关注具体对象的类型。这样使得代码更易于理解、调试和修改。
- 可替换性:多态性使得对象可以相互替换使用。如果一个方法接受父类或接口类型作为参数,那么实际传入的是其任意子类或实现类的实例,从而实现了对象的可替换性,增强了代码的灵活性和通用性。
综上所述,多态性在面向对象编程中具有很多优点,能够增强代码的灵活性、可扩展性、可读性和可维护性,同时提高代码的复用性和对象的可替换性。这使得多态性成为一种重要的编程概念和设计原则。
多态存在的三个必要条件
多态存在的三个必要条件是:
-
继承或实现:多态性基于继承或实现关系。子类或实现类必须继承或实现父类或接口,以建立对象之间的层次关系。
-
方法重写:多态性要求子类或实现类重写(覆盖)父类或接口中的方法。子类或实现类通过重写方法,可以赋予方法不同的实现方式。
-
父类或接口引用:多态性通过使用父类或接口类型的引用来实现。即可以使用父类或接口类型的变量来引用具体的子类或实现类对象。例如:Parent p = new Child();
只有满足以上三个条件,才能实现多态性。通过继承或实现建立层次关系,通过方法重写赋予不同实现,在使用父类或接口引用时,根据实际对象的类型动态调用相应的方法。这样就能够在运行时实现多态性,提升代码的灵活性和可扩展性。
以下是一个多态实例的演示,详细说明请看注释:
class Animal {
public void makeSound() {
System.out.println("动物发出声音");
}
}
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("狗在叫");
}
}
class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("猫在喵喵叫");
}
}
public class PolymorphismDemo {
public static void main(String[] args) {
Animal animal1 = new Animal(); // 创建Animal对象
Animal animal2 = new Dog(); // 创建Dog对象,并用Animal类型引用指向它
Animal animal3 = new Cat(); // 创建Cat对象,并用Animal类型引用指向它
animal1.makeSound(); // 调用Animal对象的makeSound()方法,输出:"动物发出声音"
animal2.makeSound(); // 调用Dog对象的makeSound()方法(通过Animal类型引用),输出:"狗在叫"
animal3.makeSound(); // 调用Cat对象的makeSound()方法(通过Animal类型引用),输出:"猫在喵喵叫"
}
}
注释说明:
- Animal 类是一个基础的动物类,具有 makeSound() 方法用于输出动物发出的声音。
- Dog 和 Cat 类分别继承自 Animal 类,并重写了 makeSound() 方法,提供了不同的实现方式。
- 在 PolymorphismDemo 类的 main() 方法中,展示了多态性的应用。
- 首先创建了一个 Animal 对象 animal1,然后创建了一个 Dog 对象 animal2,以及一个 Cat 对象 animal3。这里需要注意,animal2 和 animal3 是用 Animal 类型的引用指向相应的子类对象。
- 调用 animal1.makeSound() 输出 "动物发出声音",因为 animal1 是 Animal 类的实例,调用的是 Animal 类的方法。
- 调用 animal2.makeSound() 输出 "狗在叫",尽管 animal2 的声明类型是 Animal,但它指向的是 Dog 类的对象,所以在运行时会调用 Dog 类的重写方法。
- 调用 animal3.makeSound() 输出 "猫在喵喵叫",与上述类似,尽管 animal3 的声明类型是 Animal,但它指向的是 Cat 类的对象,因此调用的是 Cat 类的重写方法。 通过多态性,同样是调用 makeSound() 方法,根据实际对象的类型动态决定调用哪个版本的方法,实现了灵活和可扩展的代码设计。
虚函数
虚函数是面向对象编程中的一种概念,用于实现多态性。在 C++ 中,通过将基类的成员函数声明为虚函数,可以在派生类中重新定义该函数,实现动态绑定和运行时多态性。
与 C++ 不同,Java 中的普通函数就具有虚函数的特性,即默认情况下,Java 中的方法调用会根据对象的实际类型来确定调用哪个方法。因此,在 Java 中,不需要额外设置函数为虚函数。如果某个方法在父类中被声明为 final 关键字,则该方法不能被子类重写,相当于非虚函数。
总结起来,Java 中的普通函数就具备了与 C++ 中虚函数相似的特性,实现了动态绑定和运行时多态性,而无需明确声明函数为虚函数。
以下是一个简单的示例代码,展示了虚函数的概念和用法:
class Animal {
public void makeSound() {
System.out.println("动物发出声音");
}
}
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("狗发出汪汪的声音");
}
}
class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("猫发出喵喵的声音");
}
}
public class VirtualFunctionExample {
public static void main(String[] args) {
Animal animal1 = new Dog(); // 创建一个 Dog 对象,将其存储在 Animal 引用中
animal1.makeSound(); // 动态绑定,实际调用 Dog 类的 makeSound() 方法,输出:狗发出汪汪的声音
Animal animal2 = new Cat(); // 创建一个 Cat 对象,将其存储在 Animal 引用中
animal2.makeSound(); // 动态绑定,实际调用 Cat 类的 makeSound() 方法,输出:猫发出喵喵的声音
}
}
多态的实现方式
方式一:重写:
详情请看 Java基础篇--重写(Override)与重载(Overload) 章节。
方式二:接口
-
生活中的接口最具代表性的就是插座,例如一个三接头的插头都能接在三孔插座中,因为这个是每个国家都有各自规定的接口规则,有可能到国外就不行,那是因为国外自己定义的接口类型。
-
在Java中,接口是一种定义行为的契约。通过实现接口,一个类可以声明它将遵循该接口定义的行为规范,并提供实现该接口中声明的所有方法。其他类可以使用该接口类型引用并调用这些方法,而不需要知道具体实现类的细节。具体可以看 java接口 这一章节的内容。
方式三:抽象类和抽象方法
抽象类是一种不能被实例化的类,它定义了子类必须实现的方法。抽象方法是在抽象类中声明但没有具体实现的方法,具体实现由其子类提供。
详情请看 Java抽象类 章节。
总结:
- 使用父类类型的引用指向子类的对象:可以使用父类类型的引用来引用子类的对象,并且通过这个引用访问父类和子类中的方法。
- 该引用只能调用父类中定义的方法和变量:虽然使用父类类型的引用指向子类对象,但只能访问父类中定义的方法和变量。如果子类中有重写父类方法的情况下,则会调用子类中的方法。
- 方法重写(覆盖)和动态调用:如果子类中重写了父类中的方法,那么在运行时调用这个方法时,将会调用子类中的这个方法。这被称为动态绑定或动态调用,即在运行时根据实际对象的类型确定调用哪个方法。
- 变量不能被重写(覆盖):只有方法才能被重写,变量没有这个概念。如果在子类中试图重写父类的变量,编译时会报错。
总之,多态允许使用父类类型的引用来处理不同子类对象,通过方法重写和动态绑定,实现了在运行时根据实际对象类型调用相应的方法。需要注意的是,只有方法才能被重写,变量不能被重写。