在前两篇博客中,我们已经讲完了面向对象程序三大特性之一的封装、继承,
【Java 基础】类和对象(构造&this&封装&static&代码块)-CSDN博客
下面让我们来看看多态有哪些内容吧
💢💢在Java中,多态是面向对象编程中的一个重要概念,它允许不同类型的对象对同一方法进行不同的实现。具体来说,多态性指的是通过父类的引用变量来引用子类的对象,从而实现对不同对象的统一操作。
在Java中,要实现多态性,就必须满足以下条件:
例如,下面的案例是根据猫和狗吃东西动作的不同,而实现的多态:
class Animal {
public void eat() {
System.out.println("动物吃东西");
}
}
class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃狗粮");
}
}
class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃猫粮");
}
}
public class Test {
public static void main(String[] args) {
Animal animal1 = new Dog(); // 父类引用指向子类对象
Animal animal2 = new Cat(); // 父类引用指向子类对象
animal1.eat(); // 输出:狗吃狗粮
animal2.eat(); // 输出:猫吃猫粮
}
}
上面代码中涉及的 Override 叫作 重写
💢💢重写(override):也称为覆盖。是 子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。
想要理解方法重写,需要知道以下概念:
@Override
注解来标记子类中的方法。该注解会在编译时检查是否满足重写条件,如果不满足会报错。
注:子类中重写的方法可以调用父类中被重写的方法,使用 super
关键字。
首先回顾重载的实现条件:
public
、private
、protected
)和抛出的异常。区别点 | 重写 | 重载 |
---|---|---|
定义位置 | 定义在父类和子类之间 | 定义在同一个类中 |
方法签名 | 重写方法具有相同的名称和方法签名 | 重载方法具有相同的名称,但方法签名(参数类型和个数)不同 |
继承关系 | 是在子类中对父类方法的重新定义和实现 | 不涉及继承关系,可以在同一个类中定义 |
运行时调用 | 是根据对象的实际类型进行动态绑定,在运行时确定 | 是根据方法的参数列表的不同进行静态绑定,在编译时确定 |
目的 | 用于子类重新定义父类方法的行为,以适应子类的特定需求 | 用于在同一个类中实现相似功能但具有不同参数的方法 |
☘️对于已经投入使用的类,尽量不要进行修改。最好的方式是:重新定义一个新的类,来重复利用其中共性的内容,并且添加或者改动新的内容
🍀静态绑定:也称为前期绑定(早绑定),即在编译时,根据用户所传递实参类型就确定了具体调用那个方法。典型代表函数重载。
🌿动态绑定:也称为后期绑定(晚绑定),即在编译时,不能确定方法的行为,需要等到程序运行时,才能够确定具体调用那个类的方法。
当调用对象方法的时候,该方法会和该对象的运行类型绑定
当调用对象属性时,没有动态绑定机制,即哪里声明,哪里使用。
代码示例:
//父类
class Person {
public void mission() {
System.out.println("人要好好活着!");
}
}
//子类
class Student extends Person {
@Override
public void mission() {
System.out.println("学生要好好学习!");
}
}
//演示动态绑定
public class DynamicBinding {
public static void main(String[] args) {
//向上转型(自动类型转换)
//程序在编译阶段只知道 p1 是 Person 类型
//程序在运行的时候才知道堆中实际的对象是 Student 类型
Person p1 = new Student();
//程序在编译时 p1 被编译器看作 Person 类型
//因此编译阶段只能调用 Person 类型中定义的方法
//在编译阶段,p1 引用绑定的是 Person 类型中定义的 mission 方法(静态绑定)
//程序在运行的时候,堆中的对象实际是一个 Student 类型,而 Student 类已经重写了 mission 方法
//因此程序在运行阶段对象中绑定的方法是 Student 类中的 mission 方法(动态绑定)
p1.mission();
}
}
/* 结果输出
* 学生要好好学习!
* */
🍎🍎总的来说:重载是在同一个类中根据参数列表的不同定义多个具有相同名称但参数不同的方法,而重写是子类重新定义和实现了从父类继承的方法。重载方法通过静态绑定在编译时确定调用,重写方法通过动态绑定在运行时确定调用。重载用于实现相似功能但具有不同参数的方法,重写用于改变父类方法的行为以适应子类的需求。
(1)本质:父类的引用指向子类的对象
(2)特点:
(3)语法:父类类型 对象名 = new 子类类型()
(4)样例代码:
class A1{
public String name;
public int age;
public A1(String name, int age) {
this.name = name;
this.age = age;
}
public void eat(){
System.out.println(this.name + " 父类");
}
public void bark1(){
System.out.println(this.name + " 不构成重写父类调用");
}
}
class A2 extends A1 {
private int num = 1;
public A2(String name, int age){
super(name,age);
}
public void eat(){ //和父类构成 重写
System.out.println(this.name + " 子类");
}
public void bark2(){
System.out.println(this.name + " 不构成重写子类调用");
}
}
public class Test {
public static void func1(A1 a) {
a.eat();
a.bark1();
}
public static A1 func2() {
A1 a = new A2("change",18);
return a;
}
public static void main(String[] args) {
// 向上转型
// 1. 直接赋值
A2 a2 = new A2("change", 18);
A1 a1 = a2; // 子类 A2 这个引用父类这个引用所指向对象
// 上下两种相同
A1 a = new A2("change", 18);
a.eat(); // 调用的是 子类 中的 eat() 方法
//a.bark2(); // 错误:无法访问 子类中独有的方法
// 2,方法的参数,传参的时候进行向上转型
// 传的是子类时,只能调用子类的方法
//func1(a);
func1(a2);
// 3. 返回值向上转型
func2();
}
}
/* 结果输出
* change 子类
* change 子类
* change 不构成重写父类调用
* */
【优缺点】
优点:让代码实现更简单灵活。 缺陷:不能调用到子类特有的方法。
(1)本质:一个已经向上转型的子类对象,将父类引用转为子类引用
(2)特点:
(3)语法:子类类型 引用名 = (子类类型) 父类引用;
(4)样例代码:
class Animal {
public void eat() {
System.out.println("Animal is eating.");
}
}
class Dog extends Animal {
@Override
public void eat() {
System.out.println("Dog is eating");
}
public void bark() {
System.out.println("Dog is barking");
}
}
class Cat extends Animal {
@Override
public void eat() {
System.out.println("Cat is eating");
}
public void bark() {
System.out.println("Cat is barking");
}
}
public class Test {
public static void main(String[] args) {
Animal animal = new Dog(); // 向上转型
animal.eat(); // 调用的是 Dog 类中的 eat() 方法
// animal.bark(); // 错误:无法访问 Dog 类中独有的方法
Dog dog = (Dog) animal; // 向下转型
dog.bark(); // 调用 Dog 类中的 bark() 方法
}
}
/* 结果输出
* Dog is eating
* Dog is barking
* */
向下转型用的比较少,而且不安全,万一转换失败,运行时就会抛异常。 比如我们在上面代码进行一些修改:
解释:这段代码在运行时出现了 ClassCastException 类型转换异常,原因是 Dog 类与 Cat 类 没有继承关系,因此所创建的是 Dog 类型对象在运行时不能转换成 Cat 类型对象。
因此Java中为了避免上述类型转换异常的问题,提高向下转型的安全性,引入了 instanceof 比较操作符,用于判断对象的类型是否为XX类型或XX类型的子类型,如果该表达式为true,则可以安全转换。
class Animal {
public void eat() {
System.out.println("Animal is eating.");
}
}
class Dog extends Animal {
@Override
public void eat() {
System.out.println("Dog is eating");
}
public void bark() {
System.out.println("Dog is barking");
}
}
class Cat extends Animal {
@Override
public void eat() {
System.out.println("Cat is eating");
}
public void bark() {
System.out.println("Cat is barking");
}
}
public class Test {
public static void main(String[] args) {
Animal animal = new Dog(); // 向上转型
if(animal instanceof Dog){ //判断对象 animal 是否是 Dog 类 的实例
Dog dog = (Dog) animal; // 向下转型
dog.bark(); // 调用 Dog 类中的 bark() 方法
//上面这两句也可简写为 ((Dog) animal).bark();
}
else if(animal instanceof Cat){ //判断对象 animal 是否是 Cat 类 的实例
((Cat)animal).bark();
}
}
}
【使用多态的好处】
【使用多态的缺陷】
虽然多态性具有一些缺点,但在大多数情况下,其优点远远超过缺点,使得代码更具灵活性、可扩展性和可维护性。因此,多态性在Java编程中被广泛应用。
🥝多态数组
多态数组:数组的定义类型为父类类型,里面保存的实际元素类型为子类类型。 代码示例:(循环调用基类对象,访问不同派生类的方法)
class Animal {
public void eat() {
System.out.println("Animal is eating.");
}
}
class Dog extends Animal {
@Override
public void eat() {
System.out.println("Dog is eating");
}
}
class Cat extends Animal {
@Override
public void eat() {
System.out.println("Cat is eating");
}
}
public class Test{
public static void main(String[] args) {
Animal[] animals = {new Animal(),new Dog(),new Cat()};
for(Animal a: animals){a.eat();};
}
}
/* 结果输出
* Animal is eating.
* Dog is eating
* Cat is eating
* */
我们需要避免在构造方法中调用重写的方法,先来看一段代码:
class B {
public B() {
// do nothing
func();
}
public void func() {
System.out.println("B.func()");
}
}
class D extends B {
private int num = 1;
@Override
public void func() {
System.out.println("D.func() " + num);
}
}
public class Test {
public static void main(String[] args) {
D d = new D();
}
}
上面这段代码的运行结果是:D.func 0
,其原因如下:
结论: “用尽量简单的方式使对象进入可工作状态”, 尽量不要在构造器中调用方法(如果这个方法被子类重写, 就会触发动态绑定, 但是此时子类对象还没构造完成), 可能会出现一些隐藏的但是又极难发现的问题
💞 💞 💞那么本篇到此就结束,希望我的这篇博客可以给你提供有益的参考和启示,感谢大家支持!!!祝大家天天开心