java多态

2634 字
7 分钟

每日一言

The freedom to do better than you do now, to be better than you are now. The spirit did not create us perfectly good. She gave us the freedom to keep trying to be better everyday. — Maou from Maoyu

java多态

多态是Java面向对象编程的三大核心特征之一。

  • 基本定义:多态(Polymorphism)指的是同一个行为或操作可以在不同的对象上有不同的表现形式

注意:此处为对象,对象即由类创建的实例

java的多态主要由以下三种方式实现:

  1. 重写(override)
  2. 重载(overloading)
  3. 接口实现(Interface Implementation)

java多态存在的三个必要条件:

  1. 继承
  2. 重写
  3. 父类引用指向子类对象 Parent p = new Child()

之前的博客已经详细讲解过了重写与重载,继承等内容,这里我们详细讲解

  • 父类引用实现指向子类对象

父类引用指向子类对象

假设有如下的类型定义:

abstract class Animal {  
  abstract void eat();  
}

class Cat extends Animal { 
  @Override 
  public void eat() {  
      System.out.println("吃鱼");  
  }  
}  

class Dog extends Animal {  
  @Override
  public void eat() {  
      System.out.println("吃骨头");  
  }  
  public void work() {  
      System.out.println("看家");  
  }  
}

虽然Cat和Dog都是Animal的子类,但是都对eat()进行了重写,同时Dog类还有自己的独有的行为。

所有的动物都具有吃饭这一行为,如果我们想要写一个函数,传入参数为一个对象,函数的功能是让传入对象进行吃饭。

如果你不了解多态,你可能需要这样写:

public class Test {
    public static void main(String[] args) {
      Cat c = new Cat();
      Dog d = new Dog();
      Eat(c);
      Eat(d);
  }  
  public static void Eat(Cat c) {  
      c.eat();  
  }
  public static void Eat(Dog d) {  
      d.eat();  
  }   

}

这段代码我们运用了重载,当传入不同参数的动物类型后,会分别调用它的eat方法。运行结果为:

吃鱼
吃骨头

但是如果我们使用父类引用指向子类对象:

public class Test {
    public static void main(String[] args) {
      Cat c = new Cat();
      Dog d = new Dog();
      Eat(c);
      Eat(d);
  }  
  public static void Eat(Animal a) {  
      a.eat();  
  }

}

虽然在 Eat()函数中所有类型被声明为Animal类型,但是因为Animal类型本身拥有 eat()方法,所以编译器不会报错,同时JVM虚拟机在运行时会找到对象实际类型的方法,若传入对象为c,则找到Cat类的 eat()方法。所以运行效果与上面的一模一样:

吃鱼
吃骨头

总结:对于同一个父类派生出的各种子类,若处理的成员和方法类型都是父类中拥有的,即处理的层面没有涉及到某个子类特有的方法或成员,就可以用父类声明这些对象进行统一的处理。

虚函数

虚函数是面向对象编程的一个重要概念,要讲清虚函数这一概念,我们必须先讲解一下c++中的虚函数。

C++中的虚函数

  • 在c++的类中,用 virtual关键字申明的就是虚函数,其语法如下:
  class Base {
  public:
      // 声明虚函数
      virtual void functionName() {
          // 基类函数的实现
      }
  };

在c++中,只有虚函数才能够实现动态绑定,即当我们用基类的指针来调用在子类中被重写的函数时,能够正确执行子类中的函数而非基类中的函数。看看如下例子:

#include <iostream>

// 基类
class Base {
public:
    // 声明虚函数
    virtual void print() {
        std::cout << "This is the Base class." << std::endl;
    }
};

// 派生类
class Derived : public Base {
public:
    // 重写基类的虚函数
    void print() override {
        std::cout << "This is the Derived class." << std::endl;
    }
};

int main() {
    Base* basePtr = new Derived(); //用基类的指针指向子类的对象
    basePtr->print();  // 调用子类的print函数
    delete basePtr;
    return 0;
}

此时,运行的结果与我们在java中熟悉的结果一样,此时的 print()函数将运行子类中的版本:

This is the Derived class.

但是,如果我们将 virtual关键字删除,代码如下:

#include <iostream>

using namespace std;

// 基类
class Base {
public:
    // 声明虚函数
    void print() {
        std::cout << "This is the Base class." << std::endl;
    }
};

// 派生类
class Derived : public Base {
public:
    // 重写基类的虚函数
    void print(){
        std::cout << "This is the Derived class." << std::endl;
    }
};

int main() {
    Base* basePtr = new Derived(); //用基类的指针指向子类的对象
    basePtr->print();  // 调用子类的print函数
    Derived* derivedPtr = new Derived();// 将基类指针转换为子类指针
    derivedPtr->print(); // 调用子类的print函数
    delete basePtr;
    return 0;
}

现在,基类中的函数并没有被声明为virtual虚函数,此时程序的运行结果如下:

This is the Base class.
This is the Derived class.

当在子类中被重写的函数并非是虚函数时,c++并不会进行动态绑定,而是会调用引用指针类型的对应函数。

Java中的虚函数

与c++不同,java中的所有非静态方法默认都是虚函数,不需要特别声明,这是java面向对象设计的核心特性之一。

java方法的特点

  • 默认虚函数:Java中所有的非静态,非私有,非final的方法都是虚的
  • 不需要关键字声明
  • 动态绑定