如何理解java面向对象编程三大特性之一的多态?

前言

要学好一门语言首先要了解它的机制和特性,我在查阅了许多资料后,发现官方语言解释有点不容易理解,今天我用相对易懂的方式讲出来。

                                  

1.多态是什么

多态在面向对象编程中是一种机制,它是通过继承和方法重写来实现的。在官方的解释中,多态(英语:polymorphism)指为不同数据类型的实体提供统一的接口。也有人说,多态是同一个行为具有多个不同表现形式或形态的能力。其实说白就是同一种方法,不同的对象调用执行会有不同的结果。

有人会好奇,为什么同一种方法,不同对象调用就会有不同的结果呢?这得多亏了java多态机制中的两个核心概念:

  • 继承:通过继承,一个类可以继承另一个类,从而获得父类的属性和方法。被继承的那个就是父类。就像犬这个类,既有犬的特点,也有动物的特点,那动物类就是犬类的父类。

  • 方法重写:子类可以重写从父类里面继承的方法,方法名、返回类型和参数都和父类的方法是一样的。当重新写完父类的方法后,子类对象调用该方法执行的是子类中的方法。

java中一个父类可以被多个子类继承,不同的子类继承都重新写了父类中的方法,那父类中的同一种方法被不同子类重写为不同的执行过程,自然也就得到了不同的结果。

正是通过这种机制,才会让java展现出多种优点,下面会讲到。

2.代码举例

  • 就拿一个相对简单的代码:

// 父类 Animal
class Animal {
    public void makeSound() {
        System.out.println("动物发出声音");
    }
}

// 子类 Dog
class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("狗发出汪汪声");
    }
}

// 子类 Cat
class Cat extends Animal {
    @Override
    public void makeSound() {
        System.out.println("猫发出喵喵声");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal dog = new Dog();
        Animal cat = new Cat();

        dog.makeSound(); // 输出:狗发出汪汪声
        cat.makeSound(); // 输出:猫发出喵喵声
    }
}

控制台输出结果是:

狗发出汪汪声
猫发出喵喵声

我们可以发现,父类Animal中有一个makeSound方法,而子类dog和cat继承了父类并且重写了Animal中的makeSound方法,因为重写后makeSound方法中的代码不同,导致的输出结果也不同。

这样,通过方法重写实现了同一方法名在不同子类中具有不同行为的效果,即多态性。

3.多态的优点

多态的优点有很多,这也是java为什么会有多态机制的意义。

  • 使代码更加灵活:

    根据上面的例子,假如我们要写一个程序,来打印不同动物的声音,我们可以创建很多个动物类,如猫类狗类等,这样我们可以根据不同的动物来调用相同的方法,去重写,而不是对每个单独的子类,写具体的方法。比如在猫类中写猫叫方法,狗类中写狗叫方法,这样非多态写法,子类多了冗余代码越来越多,重复率高了不说,而且会造成可读性差、增加错误和漏改的风险。

    通过使用多态,我们可以将相似的操作抽象到父类或接口中,并在不同子类中实现具体功能。这样可以避免重复的代码,并提高代码的可读性和可维护性。

  • 使代码扩展性更强:

    多态机制中父类就像一个接口,子类就像多个模块;当我们要使用模块时,只要差在接口上就可以使用,代码也一样。

    如果我们需要添加一个新的动物类——鱼,我们只需创建一个Fish类,并继承自Animal类,然后实现自己的makeSound()方法即可。这样就不用修改原有的代码,这就是可扩展性。通过添加新的子类来实现功能的扩展。

  • 代码复用性:

    如果我们需要打印所有动物类的名称和声音,我们可以在Animal类中添加一个getName()方法,并在printSound()方法中调用它。这样,无论是狗、猫还是其他动物类,都可以共享getName()方法的实现,提高了代码的复用性。

  • 更加容易维护:

    假如有一天猫的叫声改变了,我们没必要去动物类里面重新加一种猫改变叫声后叫的方法,我们只需在Cat类中重写makeSound()方法来更改声音的输出方式。由于多态的存在,原始的printSound()方法不需要修改。这样做可以避免影响其他动物类的功能,降低了对代码的修改,提高了可维护性。

  • 可替代性:

    我们可以将子类对象设为参数,这样想要哪个动物,将哪个动物设置为参数就好,这样我们就仅仅改变参数来完成不同动物叫声的实现,大大提高效率。

public static void playSound(Animal animal) {
        animal.playSound();
    }

我在这里创建了一个方法,如果想听猫叫,我们只需要把参数改为Cat cat;如果想听狗叫,我们只需要改为Dog dog;

Game.playSound(dog); // 输出:"The dog barks."
  Game.playSound(cat); // 输出:"The cat meows."

多态的可替代性使得程序更加灵活可变,可以方便地适应不同的需求和变化。我们只需定义一个通用的方法,在不同的对象上调用该方法即可实现动态灵活的行为替代。

4.多态的体现

父类可以引用指向子类的对象

举个例子

 Animal animal1 = new Dog();
 Animal animal2 = new Cat();

这样animal1就可以调用Dog类和Animal类共有的方法和属性,animal2也一个道理。那这样做有什么意义呢?虽然猫和狗都是继承动物类,但是猫和狗也是两个不同的类,他们有共同点,也有不同点,而共同点就是他们继承的父类的属性。因此可以通过父类引用调用Animal类中的共有方法,如makeSound()。这样我们无需在意是哪个类的对象就可以完成猫和狗叫的操作。

class Animal {
    public void makeSound() {
        System.out.println("Animal is making a sound.");
    }
}

class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Dog is barking.");
    }

    public void fetch() {
        System.out.println("Dog is fetching.");
    }
}

class Cat extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Cat is meowing.");
    }

    public void scratch() {
        System.out.println("Cat is scratching.");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal1 = new Dog();
        Animal animal2 = new Cat();

        animal1.makeSound();  // 调用子类重写的makeSound()方法
        // 输出: Dog is barking.

        animal2.makeSound();  // 调用子类重写的makeSound()方法
        // 输出: Cat is meowing.

       
    }
}

当我们需要调用猫或狗特有的方法的话:可以通过强制类型转换将父类引用转换成对应的子类引用,然后调用子类特有的方法。这样就能灵活地访问子类独有的属性和行为。

 // animal1.fetch();  // 错误,父类引用无法访问子类特有的fetch()方法
        // animal2.scratch();  // 错误,父类引用无法访问子类特有的scratch()方法

        Dog dog = (Dog) animal1;  // 向下转型为Dog类型
        dog.fetch();  // 可以通过Dog类型引用调用子类特有的fetch()方法
        // 输出: Dog is fetching.

        Cat cat = (Cat) animal2;  // 向下转型为Cat类型
        cat.scratch();  // 可以通过Cat类型引用调用子类特有的scratch()方法
        // 输出: Cat is scratching.

父类引用指向子类对象的意义在于实现多态性和灵活性,提供了代码的扩展性和可读性。

多态与动态绑定

动态绑定是指在运行时根据对象的实际类型来决定调用哪个重写的方法。当使用父类引用调用被子类重写的方法时,实际执行的是子类中的方法。这种机制可以使程序在运行时具有灵活的行为。比如是猫就调用猫叫方法,是狗就调用狗叫方法,这里就不举例了上面都是。

多态在集合中的应用
  • 多态的灵活性使其在集合中的应用非常方便。我们可以使用父类的集合类型(如ListSet等)来存储不同子类的对象。

  • 通过将不同子类的对象添加到父类集合中,我们可以方便地进行添加、删除、遍历等操作,而无需关心具体子类的类型。

接口与多态:
  • 接口提供了一种统一的契约,各个类可以实现相同的接口,并具备相同的行为。

  • 通过接口实现的多态性,不同的类可以以不同的方式实现接口中的方法,但由于接口的统一契约,可以通过接口类型的引用来调用这些方法。

    举例时间到:

interface Animal {
    void makeSound();
}

class Dog implements Animal {
    @Override
    public void makeSound() {
        System.out.println("The dog barks.");
    }
}

class Cat implements Animal {
    @Override
    public void makeSound() {
        System.out.println("The cat meows.");
    }
}

class Bird implements Animal {
    @Override
    public void makeSound() {
        System.out.println("The bird chirps.");
    }
}

创建了一个Animal接口,各个类实现了这个接口,并重写了该方法。

import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Animal> animals = new ArrayList<>();
        animals.add(new Dog());
        animals.add(new Cat());
        animals.add(new Bird());

        for (Animal animal : animals) {
            animal.makeSound(); // 使用接口类型的引用调用makeSound()方法
        }
    }
}

创建了一个名字为animals的动态数组,添加不同子类对象,遍历循环,得到的结果是:

The dog barks.
The cat meows.
The bird chirps.

以上就是对java多态的介绍,如果有遗漏的地方请联系我,我会补充的

                                              

猜你喜欢

转载自blog.csdn.net/lb220303082/article/details/132722864