Java——多态

一、多态的定义

多态父类的引用指向子类的对象

Java——多态_第1张图片

如上面的照片,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的内存存储形式

Java——多态_第2张图片

 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对象的创建内存图大致是这个样子的:

Java——多态_第3张图片

如果此时在B中也写一个run方法,那么A中的run方法就会被覆盖

  A ab = new B();

最后我们去分析ab对象产生时的内存图。

  A ab = new B();

在new B的时候,堆中的A、B都要出现,理由和上一个时相同的:创建子类对象一定会创建父类对象

Java——多态_第4张图片

那么不同点究竟是什么呢?

这里就需要提到我们前面曾提到过的数据类型了

因为不同的数据类型决定着数据不同的存储形式,而在  A ab = new B();这一行代码中,A就相当于数据类型,所以ab所能操作的只能使用A那部分的内存,不过如果run方法重写了以后,这个重写的run方法相当于由A和B所共享了,所以此时还可以调用run方法。

Java——多态_第5张图片

Java——多态_第6张图片

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)来决定调用哪个静态方法,而不是根据对象的实际类型。
    }
}

在这个例子中,尽管通过父类引用指向了子类对象,但是在调用静态方法时,仍然是根据引用的类型(而不是对象的实际类型)来决定调用哪个静态方法,这体现了静态方法不具有多态性。

你可能感兴趣的:(Java,java,开发语言)