一、多态的概念
不同的对象对同一消息做出不同的响应。
多态指的是父类的引用变量可以指向其子类的对象,并且在运行时根据实际所指向的对象类型调用相应的方法,而不是在编译时就确定调用哪个方法。

关键点:为减少复杂代码的复杂圈度(if-else),使用多态
多种类的直接做成列表遍历列表(向上转型+调用他们共同的父类的方法)
这个打印结果是什么?func()0
构造父类前先要把父类构造完 父类的fun()会去执行字类的重写同名方法 此时num 还没初始化 所以num为0

多态主要通过以下两种方式实现:
1. 方法重写(Override)
子类可以重写父类中具有相同方法签名(方法名、参数列表和返回类型)的方法,从而在运行时根据实际对象的类型调用相应的方法。

2. 方法重载(Overload)
在同一个类中,可以定义多个具有相同方法名但参数列表不同的方法,编译器会根据调用时传递的参数类型和数量来决定调用哪个方法。不过,严格意义上方法重载是编译时多态,而方法重写是运行时多态。

1. 方法重写实现多态
java
// 父类
class Animal {
public void makeSound() {
System.out.println("Animal makes a sound.");
}
}
// 子类 Dog
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Dog barks.");
}
}
// 子类 Cat
class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("Cat meows.");
}
}
public class PolymorphismExample {
public static void main(String[] args) {
// 父类引用指向子类对象
Animal dog = new Dog();
Animal cat = new Cat();
// 调用相同的方法,根据实际对象类型执行不同的实现
dog.makeSound();
cat.makeSound();
}
}
代码解释:
Animal
是父类,定义了makeSound
方法。Dog
和Cat
是Animal
的子类,它们重写了makeSound
方法。- 在
main
方法中,使用Animal
类型的引用变量分别指向Dog
和Cat
对象,调用makeSound
方法时,会根据实际对象的类型执行相应的方法。(重写实现的是 动态绑定 ,在运行时根据实际所指向的对象类型调用相应的方法)
2. 方法重载实现多态
java
class Calculator {
public int add(int a, int b) {
return a + b;
}
public double add(double a, double b) {
return a + b;
}
public int add(int a, int b, int c) {
return a + b + c;
}
}
public class OverloadExample {
public static void main(String[] args) {
Calculator calculator = new Calculator();
System.out.println(calculator.add(1, 2));
System.out.println(calculator.add(1.5, 2.5));
System.out.println(calculator.add(1, 2, 3));
}
}
代码解释:
Calculator
类中定义了多个add
方法,它们具有相同的方法名但参数列表不同。- 在
main
方法中,根据传递的参数类型和数量,编译器会自动选择调用合适的add
方法。(重载实现的是 静态绑定)
二、动态绑定vs静态绑定

在面向对象编程中,重载实现的多态是静态绑定,重写实现的多态是动态绑定
重载与静态绑定
- 重载的概念 :在同一个类中,允许存在多个同名方法,但这些方法的参数列表(参数个数、参数类型或参数顺序)不同。当调用重载的方法时,编译器会根据调用时传入的实际参数来确定具体调用哪个方法。
- 静态绑定的原理 :静态绑定也称为早期绑定,在编译阶段,编译器就能够根据调用方法时提供的参数类型、个数等信息,确定要调用的具体是哪个重载方法。编译器会在编译期间生成相应的调用代码,将方法调用与具体的方法实现绑定起来,这种绑定是在程序运行前就确定好的,所以重载实现的多态是静态绑定。
重写与动态绑定
- 重写的概念:子类中定义了与父类中方法签名(方法名、参数列表、返回类型)完全相同的方法,在子类对象调用该方法时,会执行子类中重写后的方法,而不是父类中的原始方法。
- 动态绑定的原理 :动态绑定也叫晚期绑定,在运行时,根据对象的实际类型来确定调用哪个类的重写方法。在编译阶段,编译器只知道对象的静态类型,但无法确定对象在运行时的实际类型,只有在程序运行到方法调用语句时,才会根据对象的实际类型来决定调用哪个类的重写方法,所以重写实现的多态是动态绑定。
静态类型是放在栈上的引用, 实际类型是放在堆上的对象
静态类型和实际类型的概念
- 静态类型:指的是在编译阶段就已经确定的变量类型。当你声明一个变量时,需要指定它的类型,这个类型就是静态类型。编译器依据静态类型来检查变量的使用是否合法。例如在 Java 里:
java
Animal animal = new Dog();
这里 animal
变量的静态类型是 Animal
,这在编译阶段就已经明确了
- 实际类型 :指的是变量在运行时实际引用的对象类型。在上述例子中,
animal
变量实际引用的是Dog
对象,所以它的实际类型是Dog
。实际类型要在程序运行时才能确定。
静态类型、实际类型与栈、堆的关系
- 静态类型变量的引用存储在栈上 :当你声明一个变量时,它的静态类型决定了编译器如何处理这个变量。变量的引用(指向堆中对象的地址)会被存储在栈上。例如:
java
Animal animal = new Dog();
这里 animal
变量的引用存储在栈上,它指向堆中 Dog
对象的地址
- 实际类型的对象存储在堆上 :当使用
new
关键字创建对象时,无论静态类型是什么,实际创建的对象实例都会被分配到堆上。在上述例子中,Dog
对象的实际数据存储在堆上。
三、向上转型
向上转型是指将一个子类对象转换为父类类型的过程。在这个过程中,子类对象被当作父类对象来使用,它可以调用父类中定义的方法和属性.
- 向上转型后,虽然可以调用父类的方法,但如果子类重写了父类的方法,那么实际执行的是子类重写后的方法,这是由多态性决定的。
- 由于向上转型会丢失子类特有的信息,所以在需要使用子类特有的方法和属性时,可能需要进行向下转型,但向下转型需要谨慎使用,因为如果转型不当可能会导致运行时错误。

四、向上转型的三种情况
1. 赋值转型
赋值转型是将子类对象直接赋值给父类类型的变量,这是最常见的向上转型方式。通过这种方式,子类对象会被当作父类对象使用,调用方法时,如果子类重写了父类的方法,则会执行子类重写后的方法。
java
// 定义父类 Animal
class Animal {
public void makeSound() {
System.out.println("Animal makes a sound");
}
}
// 定义子类 Dog 继承自 Animal
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Dog barks");
}
public void wagTail() {
System.out.println("Dog wags its tail");
}
}
public class AssignmentUpcasting {
public static void main(String[] args) {
// 创建 Dog 类的对象
Dog dog = new Dog();
// 赋值转型:将 Dog 对象赋值给 Animal 类型的变量
Animal animal = dog;
// 调用 makeSound 方法,由于子类重写了该方法,所以执行子类的实现
animal.makeSound();
// 以下代码会编译错误,因为 animal 是 Animal 类型,无法直接调用 Dog 类特有的方法
// animal.wagTail();
}
}
2. 方法参数转型
方法参数转型是指在调用方法时,将子类对象作为参数传递给接收父类类型参数的方法。这样,方法可以处理多种子类对象,提高了代码的通用性。
java
// 定义父类 Shape
class Shape {
public void draw() {
System.out.println("Drawing a shape");
}
}
// 定义子类 Circle 继承自 Shape
class Circle extends Shape {
@Override
public void draw() {
System.out.println("Drawing a circle");
}
}
// 定义子类 Rectangle 继承自 Shape
class Rectangle extends Shape {
@Override
public void draw() {
System.out.println("Drawing a rectangle");
}
}
public class MethodParameterUpcasting {
// 定义一个方法,接收 Shape 类型的参数
public static void drawShape(Shape shape) {
shape.draw();
}
public static void main(String[] args) {
// 创建 Circle 类的对象
Circle circle = new Circle();
// 创建 Rectangle 类的对象
Rectangle rectangle = new Rectangle();
// 方法参数转型:将 Circle 对象作为 Shape 类型传递给 drawShape 方法
drawShape(circle);
// 方法参数转型:将 Rectangle 对象作为 Shape 类型传递给 drawShape 方法
drawShape(rectangle);
}
}
3. 函数返回值转型
函数返回值转型是指方法返回一个子类对象,但将其赋值给父类类型的变量或者作为返回值返回给期望接收父类类型的地方。
java
// 定义父类 Fruit
class Fruit {
public void taste() {
System.out.println("Fruit has a taste");
}
}
// 定义子类 Apple 继承自 Fruit
class Apple extends Fruit {
@Override
public void taste() {
System.out.println("Apple tastes sweet");
}
public void peel() {
System.out.println("Peeling the apple");
}
}
public class ReturnValueUpcasting {
// 定义一个方法,返回 Fruit 类型,实际返回的是 Apple 对象
public static Fruit getFruit() {
return new Apple();
}
public static void main(String[] args) {
// 函数返回值转型:将返回的 Apple 对象当作 Fruit 类型接收
Fruit fruit = getFruit();
// 调用 taste 方法,执行 Apple 类重写后的方法
fruit.taste();
// 以下代码会编译错误,因为 fruit 是 Fruit 类型,无法直接调用 Apple 类特有的方法
// fruit.peel();
}
}

五、抽象类
存在的意义是为了让你记得字类要重写父类的方法 相当于多了一遍的编译器校验,大大提高了开发的效率 。如果继承抽象类的字类依然是抽象类 那么继承该子类的类出来混迟早要还 它肩负着重写上面所有的方法。
