Java 的重载(overload)和重写(override)

简介: 重载和重写都是面向对象编程中的概念,但我们或许还听说过一种叫做覆写(overwrite)的概念。C++ 是拥有这个概念的,Java 只有 overload 和 override,Python 只有隐式的 overload 和 override,没有 overwrite 的概念。在重载(overload)、重写(override)和覆写(overwrite)中,我们一般对前面两个比较熟悉,对最后一个会略微陌生一些。

        重载和重写都是面向对象编程中的概念,但我们或许还听说过一种叫做覆写(overwrite)的概念。C++ 是拥有这个概念的,Java 只有 overload 和 override,Python 只有隐式的 overload 和 override,没有 overwrite 的概念。在重载(overload)、重写(override)和覆写(overwrite)中,我们一般对前面两个比较熟悉,对最后一个会略微陌生一些。

重载(overload)

重载的定义及规则

重载是指在一个类中,可以定义多个方法名相同但参数类型、个数或顺序不同的方法。JVM 编译器会根据传入的参数自动匹配并调用对应的方法。

重载的目的是为了让程序员能够使用同一个方法名来处理多种不同类型的参数。通过方法重载,程序员可以根据实际需要创建多个具有相同名称但参数列表不同的方法,这些方法可以用于执行类似但不完全相同的操作。这样做可以使代码更加简洁、灵活和易于维护,同时也提高了代码的复用性。最典型的,System.out.println() 方法就是一个被重载过的方法,它可以接受各种不同类型的参数,并且输出它们对应的字符串表示形式。

重载必须满足下面的规则:

  • 参数列表必须改变;
  • 返回类型可以改变;
  • 实现过程可以改变;
  • 异常声明可以改变;
  • 访问限制可以改变;

总结:外壳必须改变,内核可以改变。

重载方法的参数列表不能相同是因为编译器需要根据调用时传递的实际参数类型来确定要调用哪个重载方法。如果两个重载方法的参数列表相同,则编译器无法区分它们,并且会导致编译错误。至于返回类型和实现过程等,这些部分并不会影响到 JVM 对重载方法的区分,自然也就没有任何要求,是否修改都是可以的。

下面是一个简单的重载示例:

publicclassOverload {
publicstaticvoidprint(Integera) {
// 当传入参数的类型为Integer时,JVM会调用这个方法System.out.println("传入参数为Integer类型");
    }
publicstaticvoidprint(Strings) {
// 当传入参数的类型为String时,JVM会调用这个方法System.out.println("传入参数为String类型");
    }
publicstaticvoidmain(String[] args) {
Overload.print(1); // Output:传入参数为Integer类型Overload.print("1"); // Output:传入参数为String类型    }
}

image.gif

但其实重载中 JVM 自动寻找并匹配参数列表的过程,我们可以在一定程度上手动模拟完成:

publicclassOverload {
publicstaticvoidprint(Objecto) {
// 模拟 JVM 匹配参数列表的过程if (oinstanceofInteger)
System.out.println("传入参数为Integer类型");
elseif (oinstanceofString)
System.out.println("传入参数为String类型");
elsethrownewError("Unresolved compilation problem:");
    }
publicstaticvoidmain(String[] args) {
Overload.print(1); // Output:传入参数为Integer类型Overload.print("1"); // Output:传入参数为String类型    }
}

image.gif

其他编程语言中的重载

C++ 和 Java 在类的方法重载上面极其相似,不再详述。

Python 的重载就不太一样了,它是一门动态类型的语言,变量和参数没有确定的类型,自然没有真正的重载了。它的重载类似于上面手动模拟重载的过程,算是隐式的重载。在 Python 的内置库 typing 中有一个 overload 装饰器可以让 IDE 理解这是一个重载函数或者方法,但这并不是真正的重载,而是虚拟的。

fromtypingimportoverload@overloaddeffake_overload(x: int) ->None: ...  # 没有实现过程,实际被覆盖@overloaddeffake_overload(x: str) ->None: ...  # 没有实现过程,实际被覆盖deffake_overload(x: int|str) ->None:  # 真正实现过程的部分""" 伪重载 """iftype(x) ==int:
print('传入参数为int类型')
eliftype(x) ==str:
print('传入参数为str类型')
else:
raiseTypeError

image.gif

重写(override)

重写的定义及规则

重写是指子类重新实现父类中已有的方法,此方法可使用 @Override 注解来标记(非强制)。子类的方法必须与父类被重写的方法具有相同的名称、返回类型和参数列表。

重写,顾名思义就是重新编写原来的代码。Java 中重写的目的是让子类能够重新定义父类中已有的方法,从而实现多态性。通过重写,子类可以根据自己的需求来实现继承自父类的方法,使得代码更加灵活和可复用。同时,重写也可以提高程序的可读性和维护性。但是要注意一点,重写并不代表子类再也无法调用父类中被重写的方法了,子类仍可以通过 super 关键字进行调用。

重写必须满足以下规则:

    • 参数列表不能改变;
    • 返回类型可以为被重写方法的派生类(java5及之前版本完全不能改变);
    • 实现过程可以改变;
    • 异常声明不能比父类更加宽泛;
    • 访问限制不能比父类更加严格;
    • final 修饰的方法不可重写!
    • static 修饰的方法不可重写,但能重新声明!
    • 构造方法不可重写!
    • 父类无法被子类访问的方法不可重写!

    总结:外壳(几乎)不能改变,内核可以改变。

    重写可以说是父类方法的特化,所以从代码的具体程度上来说,子类重写后方法的具体程度比父类的更加大,自然而然地,重写后的方法抛出异常的声明不能比父类的更加宽泛。当然,父类中有些方法子类无法访问到,那么也就不存在所谓的重写。

    特别说明一下,static 修饰的父类方法是不可重写的,子类照着父类的去”重写“也并非真正的重写,尽管程序可以运行。子类实际上只是重新声明,定义了一个新的、独立于父类的静态方法,对于父类的那个同名方法,只是相当于把它”隐藏“起来了而已,并没有真的重写。父类无法被子类访问的方法也是类似的道理,都是子类重新定义了一个新的方法罢了。

    下面是一个简单的重写示例:

    classAnimal {
    publicstaticvoidmove() { // static 修饰,不可被重写System.out.println("动物移动");
        }
    publicvoidbark() { // 被重写方法System.out.println("动物叫");
        }
    }
    classDogextendsAnimal {
    // @Override // 错误的重写,添加注解则编译器报错publicstaticvoidmove() { // 重新声明,并非重写System.out.println("狗跑");
        }
    @Override// 重写注解publicvoidbark() { // 重写方法System.out.println("狗吠");
        }
    }
    publicclassTest {
    publicstaticvoidmain(String[] args) {
    Animalanimal=newAnimal();
    Dogdog=newDog();
    Animal.move(); // Output:动物移动Dog.move(); // Output:狗跑animal.bark(); // Output: 动物叫dog.bark(); // Output: 狗吠    }
    }

    image.gif

    @Override 注解

    @Override 是一种注解(Annotation),它用于标记一个方法是重写了父类或接口了的同名方法。使用 @Override 注解可以让编译器检查该方法是否正确地重写了父类中的方法。如果没有正确重写,则编译器会提示错误。

    注解 @Override 通常会用于下面两种情况:

      1. 子类重写父类方法时使用,以保证正确地重写,若错误重写(不满足规则)则编译报错;
      2. 重写接口中的抽象方法时使用, 以确保实现了接口中的所有抽象方法;

      虽然注解 @Override 并非强制使用的,但加上它可以提高代码的可读性和可维护性,我们应该养成使用 @Override 的好习惯。

      其他编程语言中的重写

      在 Python 里面的重写非常直白,没有任何特殊的要求,只要子类与父类的方法同名,就可以重写。而 C++ 里的重写和 Java 也比较相似。

      区别与联系

      下面是一张总结性的表格:

      方式 重载(overload) 重写(override) 覆写(overwrite)
      参数列表 必须改变 不能改变 \
      返回类型 可以改变 (几乎)不能改变 \
      实现过程 可以改变 可以改变 \
      异常声明 可以改变 不能比父类更加宽泛 \
      访问限制 可以改变 不能比父类更加严格 \
      备注

      final 关键字修饰的方法不可重写

      子类无法访问的父类方法不可重写

      static 关键字修饰的方法不可重写,但可重新声明

      构造方法不可重写

      \
      总结 外壳必须改变,内核可以改变

      外壳(几乎)不能改变,内核可以改变

      \

      下面一张图片生动地展示了什么是重载和重写:

      重载和重写image.gif编辑

      这里要特别强调的是,Java 中的构造方法只能被重载而不能被重写:

      子类构造方法可以与父类构造方法同名,但参数列表必须不同,这就是方法重载的特性。子类构造方法可以调用父类构造方法来初始化从父类继承下来的属性或行为,即使用 super 关键字来调用父类的构造方法。重写是指子类重写了父类中已有的方法,并且方法名、参数列表和返回值类型都相同,在调用该方法时会优先调用子类中的方法而非父类中的方法。

      目录
      相关文章
      |
      2月前
      |
      Java 编译器
      在Java中,关于final、static关键字与方法的重写和继承【易错点】
      在Java中,关于final、static关键字与方法的重写和继承【易错点】
      27 5
      |
      3月前
      |
      Java 编译器 数据安全/隐私保护
      Java 重写(Override)与重载(Overload)详解
      在 Java 中,重写(Override)和重载(Overload)是两个容易混淆但功能和实现方式明显不同的重要概念。重写是在子类中重新定义父类已有的方法,实现多态;重载是在同一类中定义多个同名但参数不同的方法,提供多种调用方式。重写要求方法签名相同且返回类型一致或为父类子类关系,而重载则关注方法参数的差异。理解两者的区别有助于更好地设计类和方法。
      284 1
      |
      4月前
      |
      Java
      【Java基础面试二十二】、为什么要重写hashCode()和equals()?
      这篇文章解释了为什么需要重写`hashCode()`和`equals()`方法:因为Object类的`equals()`默认使用`==`比较,这在业务中通常是不够的,我们需要根据对象内容来比较相等性;同时,为了保持`hashCode()`与`equals()`的联动关系,一旦重写了`equals()`,通常也需要重写`hashCode()`。
      【Java基础面试二十二】、为什么要重写hashCode()和equals()?
      |
      4月前
      |
      Java
      描述 Java 中的重载和重写
      【8月更文挑战第22天】
      23 0
      |
      4月前
      |
      存储 Java 索引
      |
      Java Android开发
      Java @override报错的解决方法
      有时候Java的Eclipse工程换一台电脑后编译总是@override报错,把@override去掉就好了,但不能从根本上解决问题,因为有时候有@override的地方超级多。 这是jdk的问题,@Override是JDK5就已经有了,但是不支持对接口的实现,认为这不是Override而报错。
      959 0
      |
      3天前
      |
      安全 Java API
      java如何请求接口然后终止某个线程
      通过本文的介绍,您应该能够理解如何在Java中请求接口并根据返回结果终止某个线程。合理使用标志位或 `interrupt`方法可以确保线程的安全终止,而处理好网络请求中的各种异常情况,可以提高程序的稳定性和可靠性。
      26 6
      |
      18天前
      |
      设计模式 Java 开发者
      Java多线程编程的陷阱与解决方案####
      本文深入探讨了Java多线程编程中常见的问题及其解决策略。通过分析竞态条件、死锁、活锁等典型场景,并结合代码示例和实用技巧,帮助开发者有效避免这些陷阱,提升并发程序的稳定性和性能。 ####
      |
      16天前
      |
      存储 监控 小程序
      Java中的线程池优化实践####
      本文深入探讨了Java中线程池的工作原理,分析了常见的线程池类型及其适用场景,并通过实际案例展示了如何根据应用需求进行线程池的优化配置。文章首先介绍了线程池的基本概念和核心参数,随后详细阐述了几种常见的线程池实现(如FixedThreadPool、CachedThreadPool、ScheduledThreadPool等)的特点及使用场景。接着,通过一个电商系统订单处理的实际案例,分析了线程池参数设置不当导致的性能问题,并提出了相应的优化策略。最终,总结了线程池优化的最佳实践,旨在帮助开发者更好地利用Java线程池提升应用性能和稳定性。 ####
      |
      18天前
      |
      缓存 Java 开发者
      Java多线程编程的陷阱与最佳实践####
      本文深入探讨了Java多线程编程中常见的陷阱,如竞态条件、死锁和内存一致性错误,并提供了实用的避免策略。通过分析典型错误案例,本文旨在帮助开发者更好地理解和掌握多线程环境下的编程技巧,从而提升并发程序的稳定性和性能。 ####