一、多态的定义
多态:父类的引用指向子类的对象
如上面的照片,B是子类,A是父类
在Test中父类的引用指向了子类的对象,这个就是多态。
代码:
class A{
}
class B extends A{
}
public class Test {
public static void main(String[] args) {
A ab = new B();
}
}
不同的数据类型决定着数据不同的存储形式
为了更好的理解多态,我们可以先复习一下数据类型
从上面的图片中可以了解到:不同的数据类型决定着数据不同的存储形式
A a = new A();
然后我们继续对A代码进行扩充,并生成新的实例对象a:
class A{
public String name;
public int age;
public void run(){
System.out.println("A跑得快");
}
public void eat(String name){
System.out.println(name + "吃得多");
}
}
class B extends A{
}
public class Test {
public static void main(String[] args) {
A ab = new B();
A a = new A();
}
}
这里我们先画出实例对象a的内存存储形式
B b = new B();
然后继续对B类进行补充
class A{
public String name;
public int age;
public void run(){
System.out.println("A跑得快");
}
public void eat(String name){
System.out.println(name + "吃得多");
}
}
class B extends A{
public char sex;
public int height;
public void fly(){
System.out.println("B飞得高");
}
}
public class Test {
public static void main(String[] args) {
A ab = new B();
A a = new A();
B b = new B();
}
}
这里我们需要注意的一个问题是:创建子类实例对象的前提是要创建子类的父类对象
也就是说在创建B的子类之前,我们首先要将父类A的对象给他创建出来,所以b对象的创建内存图大致是这个样子的:
如果此时在B中也写一个run方法,那么A中的run方法就会被覆盖
A ab = new B();
最后我们去分析ab对象产生时的内存图。
A ab = new B();
在new B的时候,堆中的A、B都要出现,理由和上一个时相同的:创建子类对象一定会创建父类对象。
那么不同点究竟是什么呢?
这里就需要提到我们前面曾提到过的数据类型了
因为不同的数据类型决定着数据不同的存储形式,而在 A ab = new B();这一行代码中,A就相当于数据类型,所以ab所能操作的只能使用A那部分的内存,不过如果run方法重写了以后,这个重写的run方法相当于由A和B所共享了,所以此时还可以调用run方法。
class A{
public String name;
public int age;
public void run(){
System.out.println("A跑得快");
}
public void eat(String name){
System.out.println(name + "吃得多");
}
}
class B extends A{
public char sex;
public int height;
public void fly(){
System.out.println("B飞得高");
}
public void run(){
System.out.println("B跑得快");
}
}
public class Test {
public static void main(String[] args) {
A ab = new B();
ab.run();
ab.sex;//error
}
}
二、实现多态的方式
继承:
通过继承父类并在子类中重写父类的方法来实现多态。子类继承父类后,可以对父类的方法进行扩展和修改,以适应不同的需求。
接口实现:
一个类可以实现多个接口,不同的实现类可以对接口中的抽象方法进行不同的实现,从而实现多态。
三、多态的优点
提高代码的可维护性和可扩展性:
当需要增加新的功能或修改现有功能时,只需要在子类中进行修改,而不会影响到父类和其他子类的代码。
增强代码的灵活性和可复用性:
可以使用父类的引用指向不同的子类对象,根据实际情况动态地调用不同子类的方法,提高了代码的灵活性和可复用性。
四、多态的实现机制
向上转型(Upcasting):
将子类对象赋值给父类引用,这是自动进行的。在向上转型后,通过父类引用只能访问父类中定义的成员变量和方法,不能访问子类特有的成员变量和方法。
class Animal {
public void makeSound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Dog barks");
}
}
public class PolymorphismExample {
public static void main(String[] args) {
Animal animal = new Dog();
animal.makeSound();
}
}
在这个例子中,将Dog
对象赋值给Animal
类型的引用,这就是向上转型。当调用makeSound
方法时,实际执行的是Dog
类中的方法,这体现了多态。
方法重写(Override):子类重写父类的方法,使得在运行时根据对象的实际类型来调用相应的方法。当使用父类引用调用重写的方法时,实际执行的是子类中的方法。在上面的例子中,Dog
类重写了Animal
类中的makeSound
方法
动态绑定(Dynamic Binding):也称为后期绑定,是在运行时根据对象的实际类型来确定要调用的方法。Java 通过方法表来实现动态绑定,在运行时根据对象的实际类型查找对应的方法表,从而确定要调用的方法。
五、多态的注意事项
不能通过父类引用访问子类特有的成员变量和方法:
向上转型后,父类引用只能访问父类中定义的成员变量和方法。如果需要访问子类特有的成员变量和方法,可以进行向下转型(Downcasting),但需要注意进行类型检查,以避免出现类型转换异常。
class Parent {
int parentVariable = 10;
void parentMethod() {
System.out.println("This is parent method.");
}
}
class Child extends Parent {
int childVariable = 20;
void childMethod() {
System.out.println("This is child method.");
}
}
public class Main {
public static void main(String[] args) {
Parent parent = new Child();
// 可以访问父类的成员变量和方法
System.out.println(parent.parentVariable);
parent.parentMethod();
// 以下代码会报错,不能通过父类引用访问子类特有的成员变量和方法
// System.out.println(parent.childVariable);
// parent.childMethod();
}
}
在这个例子中,创建了一个父类Parent
和一个子类Child
。通过父类引用指向子类对象后,可以访问父类中定义的成员变量和方法,但不能直接访问子类特有的成员变量和方法。如果尝试访问子类特有的成员变量和方法,会在编译时出现错误。
构造方法不具有多态性:
在创建对象时,构造方法是按照类的继承层次依次调用的,不会发生多态。
class Parent {
public Parent() {
System.out.println("Parent constructor called.");
}
}
class Child extends Parent {
public Child() {
System.out.println("Child constructor called.");
}
}
public class Main {
public static void main(String[] args) {
Parent parent = new Child();
// 输出结果先是“Parent constructor called.”,然后是“Child constructor called.”
// 可以看到构造方法是按照类的继承层次依次调用的,没有多态性
}
}
在这个例子中,创建一个Child
对象并赋值给Parent
类型的引用。在这个过程中,首先调用父类的构造方法,然后调用子类的构造方法。这表明构造方法是按照类的继承层次依次调用的,不具有多态性,即不能根据对象的实际类型动态地选择调用哪个构造方法。
静态方法不具有多态性:
静态方法是与类相关联的,而不是与对象相关联的,因此静态方法不具有多态性。
class Parent {
public static void staticMethod() {
System.out.println("Parent static method.");
}
}
class Child extends Parent {
public static void staticMethod() {
System.out.println("Child static method.");
}
}
public class Main {
public static void main(String[] args) {
Parent parent = new Child();
Parent.staticMethod(); // 输出 "Parent static method."
Child.staticMethod(); // 输出 "Child static method."
// 即使通过父类引用指向子类对象,调用静态方法时还是根据引用的类型(这里是 Parent)来决定调用哪个静态方法,而不是根据对象的实际类型。
}
}
在这个例子中,尽管通过父类引用指向了子类对象,但是在调用静态方法时,仍然是根据引用的类型(而不是对象的实际类型)来决定调用哪个静态方法,这体现了静态方法不具有多态性。