zoukankan      html  css  js  c++  java
  • Java基础(004):对重写Override和重载Overload的理解

      本篇主要说明对重写Override和重载Overload的理解,目录结构如下:

    1、Java的方法定义和方法签名

      提到方法的【重写Override】和【重载Overload】,就绕不开Java的方法定义和方法签名,毕竟这两都是针对方法,只有了解了Java中的方法定义和方法签名,才可以更好地理解【重写Override】和【重载Overload】的含义。

      Java的方法定义(method declaration),即一个方法所包含的组成,而Java的方法签名(method signature),则是从Java语言层面对方法的一种限定。

      关于Java的方法定义和方法签名,这里引用Oracle官网的描述进行说明[3]Defining Methods https://docs.oracle.com/javase/tutorial/java/javaOO/methods.html


    The only required elements of a method declaration are the method's return type, name, a pair of parentheses, (), and a body between braces, {}.

    More generally, method declarations have six components, in order:

    1. Modifiers—such as public, private, and others you will learn about later.
    2. The return type—the data type of the value returned by the method, or void if the method does not return a value.
    3. The method name—the rules for field names apply to method names as well, but the convention is a little different.
    4. The parameter list in parenthesis—a comma-delimited list of input parameters, preceded by their data types, enclosed by parentheses, (). If there are no parameters, you must use empty parentheses.
    5. An exception list—to be discussed later.
    6. The method body, enclosed between braces—the method's code, including the declaration of local variables, goes here.

    Definition: Two of the components of a method declaration comprise the method signature—the method's name and the parameter types.


      从上面的描述中我们可以总结如下:

    • Java中的方法定义:方法定义其实就是方法的组成,必须要有的有返回类型、方法名称、()+参数列表、{}+方法体;通常情况下,按顺序上包含以下6个部分,即:
      1. 访问权限修饰符:例如 public/protected/默认(什么都不写)/private,非必要,没写的话是默认的访问权限
      2. 返回类型
      3. 方法名称
      4. 参数列表,且包含在()中,可为空
      5. 异常列表,非必要,可无
      6. 方法体,且包含在 {} 中,可为空
    • Java的方法签名:只包含其中的方法名称和参数列表这2项
      • 注:Java语言层面,同一个类中不能同时存在2个方法签名一样的方法,否则会有编译错误

      Java语言规范中也有关于方法签名的一些相关说明,有兴趣的可以参考[4]8.4.2. Method Signature https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.4.2

      需要注意的是(参考[15]Java 方法签名 https://www.cnblogs.com/zxguan/p/7777952.html):

    • 方法签名只与方法名称和参数列表有关,与返回值、修饰符、异常等无关。
    • 泛型参数经过类型擦除后是相同的参数,例如 List<String> 和 List<Integer>。
      • (笔者注)Eclipse 中提示 "Erasure of method yourMethod(List<String>) is the same as another method in type YourExampleClass"
    • 可变参数 String... str 和 String[] str 是相同的参数
      • (笔者注)Eclipse 提示 "Duplicate method"

    注:对于类型擦除这一点,暂未找到Java语言规范的详细出处,待补充。

     

    2、覆盖/重写Override重载Overload的定义

      讲完了Java的方法定义和方法签名,回过头来看看【重写Override】和【重载Overload】,同样还是引用Oracle官网对【重载Overload】的一些说明[3]Defining Methods https://docs.oracle.com/javase/tutorial/java/javaOO/methods.html


    The Java programming language supports overloading methods, and Java can distinguish between methods with different method signatures. This means that methods within a class can have the same name if they have different parameter lists (there are some qualifications to this that will be discussed in the lesson titled "Interfaces and Inheritance").

    Overloaded methods are differentiated by the number and the type of the arguments passed into the method.

    You cannot declare more than one method with the same name and the same number and type of arguments, because the compiler cannot tell them apart.

    The compiler does not consider return type when differentiating methods, so you cannot declare two methods with the same signature even if they have a different return type.


      笔者直接简单翻译下:

    • Java语言支持方法重载Overload,并且Java语言是通过方法签名来区分方法的,也即是说,Java支持在类中存在同名而不同参数列表的方法。
    • 方法Overload是通过参数的数量和类型来进行区分的(即参数列表不同)。
    • Java的编译器并不考虑返回类型来区分不同的方法,因此在Java语言层面若存在相同方法签名但返回类型不一样的方法,则会抛出编译错误。
      • (笔者注)Eclipse 提示 "Duplicate method"

      总结成一句就是:方法名称相同但是方法签名不同就构成了重载Overload

      至于父类的同名方法算不算是重载,还是来看看Java语言规范中的说明[5]8.4.9. Overloading https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.4.9


    If two methods of a class (whether both declared in the same class, or both inherited by a class, or one declared and one inherited) have the same name but signatures that are not override-equivalent, then the method name is said to be overloaded.

    This fact causes no difficulty and never of itself results in a compile-time error. There is no required relationship between the return types or between the throws clauses of two methods with the same name, unless their signatures are override-equivalent.


      很明显,只要是方法名称相同而方法签名不一样的2个方法,就构成了重载Overload,不管这2个方法是在同个类中声明,还是都在父类中声明,抑或是一个继承过来的一个在类中声明的,而且和返回类型、异常也没有什么卵关系

    注:这些都未提及访问权限修饰符的问题,暂未找到出处,待补充。

      构造器的重载也是同个理,参考Java语言规范[6]8.8.8. Constructor Overloading https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.8.8

      接口中的方法重载也是如此[7]9.4.2. Overloading https://docs.oracle.com/javase/specs/jls/se8/html/jls-9.html#jls-9.4.2

      二进制字节码兼容性相关的重载可以参考[8]13.4.23. Method and Constructor Overloading https://docs.oracle.com/javase/specs/jls/se8/html/jls-13.html#jls-13.4.23

      接下来看看【重写Override】,同样还是绕不开方法签名。这里依旧是引用Oracle官方文档说明[9]Overriding and Hiding Methods https://docs.oracle.com/javase/tutorial/java/IandI/override.html

    注:这里面还列举了一些案例供参考。


    An instance method in a subclass with the same signature (name, plus the number and the type of its parameters) and return type as an instance method in the superclass overrides the superclass's method.

    The ability of a subclass to override a method allows a class to inherit from a superclass whose behavior is "close enough" and then to modify behavior as needed. The overriding method has the same name, number and type of parameters, and return type as the method that it overrides. An overriding method can also return a subtype of the type returned by the overridden method. This subtype is called a covariant return type.


      更多关于方法签名、override-equivalent、Overriding 的可以参考:

      Java语言规范中的相关说明比较分散(看目录分别针对接口、类等进行的说明),这里就不再一一去分析了。大致可以总结为:子类的实例方法与从父类继承过来的实例方法的方法签名一致 + 返回类型一样(或者返回子类型,称为协变类型)即构成【重写Override】。具体可参考后面的总结。

      【重载Overload】和【重写Override】的定义和注意点总结下:

    • 重写Override:子类的实例方法与从父类继承过来的实例方法的方法签名一致 + 返回类型一样
      • 返回类型可以是被重写的方法的返回类型的子类型,这种就称为协变类型,因为可以通过父类引用指向子类型,反之就不行了,这种比较容易理解。
      • 存在继承关系且可以重写的有 public、protected、default 实例方法,私有方法无法继承,子类无法直接使用,不存在重写
      • 访问权限修饰符:子类方法 >= 父类方法,即 public > protected > 默认(什么都不写),子类重写的方法需要有相同或者更大的访问权限
      • 异常: 子类方法 <= 父类方法,子类可以减少、抛出异常子类,但是不能抛出超纲的异常类型,这种也比较容易理解。
      • 声明为 final 的方法不能被重写
      • 声明为 static 的方法能够再次声明,但这属于隐藏父类静态方法,需要显式使用父类/父类引用调用才能调用到父类的被隐藏的静态方法。
      • 构造器不是成员,没有继承关系,不存在重写
      • 善用 @Override ,凡是重写的方法,都可以而且建议使用注解@Override,编译器会进行编译时检查
      • 善用 IDE 的提示,例如 Eclipse ,结合使用@Override,对于拼写错误、类型错误等的检查会有很大的帮助。
    • 重载Overload:方法名称相同但是方法签名不同的多个方法构成了重载
      • 这里指的是方法名称相同但是参数列表(类型/顺序和个数)不同,与方法在继承结构中的哪个类中声明没有关系,也和返回类型、异常、访问权限修饰符无关。
      • 重载与返回类型、异常无关,意味着被重载的方法可以改变返回类型,还可以改变访问修饰符,甚至可以声明新的异常或者更广的异常类型
      • 笔者认为,说白了,重载其实就是方法名称相同、参数列表不同的各种场景下的合理的方法实现而已,理论上,编译器根据参数列表是可以直接解析到对应的方法的。

     

    3、注解 @Override 的作用

      对于重写的方法,无论何时,都可以而且建议使用注解@Override,这样可以带来以下好处:

    • 编译时的强制检查
    • 代码结构上会更清晰
    • 确实是在重写已存在的父类方法,而不是想当然认为已经重写了(经常会有拼写错误、参数列表不匹配的情况)。
    • IDE 可以协助进行错误检查,看是否有匹配的父类方法被重写
    • 父类/父接口方法签名变更时,会即时提示错误,并强制实现类方法同时进行变更。

    注:参考 [13]When do you use Java's @Override annotation and why? https://stackoverflow.com/questions/94361/when-do-you-use-javas-override-annotation-and-why

     

    4、重写Override 的作用/为什么要重写Override

      诚如上面提及的【whose behavior is "close enough" and then to modify behavior as needed】,即按需修改:既可以原样继承,也可以重写进行扩展和增强

    • 当父类中的方法实现无法满足子类需求时,无论是进行扩展增强,还是全新的实现,都可以按需重写,对外则还是一样的、完全兼容的方法调用
    • 同个方法调用,允许不同的响应(多态)

     

    5、子类中创建相同的方法是编译时错误?

    • 只要是方法签名一致(方法名称 + 参数列表),编译器就认为是一样的,会出现编译时错误

     

    6、编译器对重载方法的选取过程:重载解析(overloading resolution)

    注:参考极客时间课程《深入拆解Java虚拟机》之《04 | JVM是如何执行方法调用的?(上)》

      重载的方法在编译过程中即可完成识别。具体到每一个方法调用,Java 编译器会根据所传入参数的声明类型(注意与实际类型区分)来选取重载方法。选取的过程共分为三个阶段:

    1. 在不考虑对基本类型自动装拆箱(auto-boxing,auto-unboxing),以及可变长参数的情况下选取重载方法;
    2. 如果在第 1 个阶段中没有找到适配的方法,那么在允许自动装拆箱,但不允许可变长参数的情况下选取重载方法;
    3. 如果在第 2 个阶段中没有找到适配的方法,那么在允许自动装拆箱以及可变长参数的情况下选取重载方法。

      如果 Java 编译器在同一个阶段中找到了多个适配的方法,那么它会在其中选择一个最为贴切的,而决定贴切程度的一个关键就是形式参数类型的继承关系。

      如下例子中,第一次调用,null其实可以匹配Object的,但是String作为Object的子类,会被编译器认为是更加贴切的选择,因此使用了第一个方法。

    public class TestOverload {
    
        void overloadMethod(Object objA, Object obj){
            System.out.println("objA");
        }
    
        void overloadMethod(String str, Object obj){
            System.out.println("str");
        }
        
        public static void main(String[] args) {
            TestOverload to = new TestOverload();
            to.overloadMethod(null, null);
            to.overloadMethod((Object)null, null);
        }
    }
    
    // result
    str
    objA

    注:实际上考虑到所有继承的方法都可能存在重写(包括部分重载的方法),Java 编译器会将所有对非私有实例方法的调用编译为需要动态绑定的类型,因此从 Java 虚拟机语境下,将重载称为静态绑定是不准确的。

      确切地说,Java 虚拟机中的静态绑定指的是在解析时便能够直接识别目标方法的情况,而动态绑定则指的是需要在运行过程中根据调用者的动态类型来识别目标方法的情况。

    待跟进:重写与多态静态绑定和动态绑定

     

    7、总结

      Java的方法定义和方法签名总结如下

    ~ 说明
    Java的方法定义

    方法定义即是方法的组成,必须要有的有:返回类型、方法名称、()+参数列表、{}+方法体

    通常情况下,按顺序上包含以下6个部分,即:访问权限修饰符 + 返回类型 + 方法名称 + 参数列表(且包含在()中,可为空) + 异常列表(非必要,可无) + 方法体(且包含在 {} 中,可为空)

    Java的方法签名 只包含方法定义中的方法名称和参数列表这2项

      【重载Overload】和【重写Override】的定义、注意点和差别总结如下

    总结项/区别/注意点 方法重载Overload 方法重写Override
    定义 方法名称相同但是方法签名不同的多个方法构成了重载 子类的实例方法与从父类继承过来的实例方法的方法签名一致 + 返回类型一样
    发生范围 整个类继承结构中 子类中,针对的是可继承的实例方法
    参数列表 不同 需要保持一致
    返回类型 无关,可随意修改 保持一致,或者返回子类型
    异常列表 无关,可随意修改 子类可以减少、抛出异常子类,但是不能抛出超纲的异常类型
    访问权限修饰符 无关,可随意修改

    子类方法 >= 父类方法,即 public > protected > 默认(什么都不写),

    子类重写的方法需要有相同或者更大的访问权限

    发生阶段

    可继承的实例方法都会在运行期动态绑定(存在重写的情况),

    其他的编译期静态绑定

    运行期动态绑定
    构造器 构造器也可以重载,不过只能是同个类中的构造器重载 构造器不是成员,没有继承关系,不存在重写
    注解 @Override,该注解可进行编译时的强制检查

    注:更多关于Java语言的描述/说明和编程实践指导,请参考Oracle官网的Java语言规范[1]https://docs.oracle.com/javase/specs/jls/se8/html/ 和 Java Tutorials[2]https://docs.oracle.com/javase/tutorial/。因官网资料比较多,笔者并没有一一详细研究,恐挂一漏万,本文如有说明/描述/引用有误的。请轻拍。

     

    8、参考

     

  • 相关阅读:
    网络摄像头Androi端显示(mjpeg)源码分析
    STM32F103 MQTT智能配网ESP07S WIFI 手机app控制继电器EMQ温湿度
    安装云服务器 ECS CentOS 7 图形化桌面
    PT100多路分布式LORA无线测温传感器工业设备老化温度监控记录仪
    把qt 程序打包成一个exe可以执行的文件
    STM32L051C8T6 HAL DMA IDLE串口不定长接收遇到的问题
    Project ERROR: Cannot run target compiler '/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang++'. Output:
    更换下平台
    H5单页面应用(SPA)架构总结
    财务系统功能开发要点概述
  • 原文地址:https://www.cnblogs.com/wpbxin/p/14337465.html
Copyright © 2011-2022 走看看