一. Java方法声明的众多属性

在讨论重载和覆写的区别之前,先补充一下Java方法的背景知识,看下面最为常见的main方法声明,为了完整介绍,自行加了异常的抛出:

public static  void  main(String[] args)  throws  Exception {...}

①        ②       ③      ④            ⑤                          ⑥

各属性说明如下:

①:访问权限(修饰符)

②:static/finally

③:返回值

④:方法名

⑤:参数列表

⑥:异常

值得注意的是,在Java语法层面,只有④⑤(⑤包括了形参的个数,类型,顺序)构成方法签名即方法唯一性标识,换言之,如果一个类中两个方法的方法签名一摸一样,则即使①②③⑥不同(特别是返回值)编译器仍然会认为这两个方法重复了,从而报错。

引申来说,其实在Java字节码层面是支持返回值可以作为方法签名的一部分,这是为了支持其他语言编译成class文件,Java语法层面并不支持。

二. 重载(OverLoad)

再看方法:

public static  void  main(String[] args)  throws  Exception {...}

①        ②       ③      ④            ⑤                          ⑥

先对方法重载有个认识,overload其实就是在一个类中写一个名字同已经存在的方法名一样的方法,但又要保证使用不同方式调用方法时JVM能识别出这是两个不同的方法,e.g.

new Student("Tom") VS new Student("Tom", 14)

所以,问题变成:在方法名④相同的前提下,①②③⑤⑥哪些变化能保证JVM能识别出这是不同的方法,哪些又是“无关紧要”——对是否重载没有影响呢?

这就引出了方法重载的充要条件:

方法名④要一样,参数列表⑤的个数,类型和顺序至少有一个不同

除此之外,权限修饰符①,static/final②,返回值③,抛出异常⑥都不影响,e.g.

原方法:public Boolean init(String name,Integer order){...}

合法重载1:public Boolean init(String name){...}(个数不同)

合法重载2:public Boolean init(Integer order,String name){...}(顺序不同)(一般不提倡,因为容易混淆)

合法重载3:public Boolean init(Integer name,Integer order){...}(类型不同)

也可以看几个反例:

不合法重载1:public String init(Integer order,String name){...}  (只修改方法返回值类型,NO)

不合法重载2:private Boolean init(String name,Integer order){...}  (只修改方法访问权限,NO)

可以这么理解,因为重载就是重新写一个方法,这个方法同已经存在的方法名相同,但是参数列表(个数,类型和顺序)不同。那么,其他的属性①②③⑥现在就是属于这个新方法的属性,和原方法没有一点关系 —— 方法签名④⑤已经是保证了方法是一个新方法,所以①②③⑥随便选的。

另外,一个要注意的问题是,重载的原方法可以来自于本类,也可以来自于父类或者任何祖先类——只要对本类可见即可。因为根据第一节已经探究过的访问修饰符问题,如果对子类可见,即代表子类可以直接操作该方法,就如同父类方法”写在本类的最前方“一样。

三. 覆写(Override)

再看方法:

public static  void  main(String[] args)  throws  Exception {...}

①        ②       ③      ④            ⑤                          ⑥

先对方法覆写有个认识,Override就是子类覆盖父类的方法,最实用的目的:多态,e.g.

即,在父类引用指向子类对象后,此时父类引用调用一个被覆写的方法的时,JVM要执行子类的方法,不能执行父类的方法。就好像父类的方法被“覆盖”了——果然是法如其名Override。

这也能看出重载和覆写在行为上的区别——重载要让JVM识别出这是不同的方法,覆写要让JVM识别出这就是父类已有的方法。

回顾重载的充要条件——方法签名不能一样,那么现在保证子类方法不是单独写的方法,而是覆写的父类的方法,就一定要保证方法签名(④⑤)要一模一样。其他还有限制,请看:

public static  void  main(String[] args)  throws  Exception {...}

①        ②       ③      ④            ⑤                          ⑥

1)方法名④和参数列表⑤:必须相同

2)权限修饰符①:必须比父类方法大

3)static/final②:static和final方法都是不可以被继承的,因此,想要被覆写的父类方法一定不能被这两个修饰符修饰(上面举的main方法的例子只是拿了一个常见又”齐全“的方法来介绍问题,main方法是不能被继承的)。

4)返回值③:必须比父类方法相同或小,即返回值类型所属的类必须和父类一样,或者是其子类,eg:

父类:private Object init(String name){...}

子类:public String init(String name){...}

5)抛出异常③:子类方法不能抛出比父类方法更多的异常【1】,即子方法抛出的异常就是父方法抛出的异常或者其子类。

可简记为“两同两小一大”【2】

如何理解呢,这和之前说的覆写的目的——多态,有很大的关系,再看例子:

很明显,JVM要成功执行grandpa.print("Sam"),就必须保证子类覆写的print()方法上有一定的限制,“就像”grandpa在调用自己的方法一样,即,保证从外观上,调用子类方法和调用父类方法没有区别(又称为“里氏替换原则(Liskov Substitution principle)”),二者只是方法体中行为的不同,e.g.

对应到之前的种种要求,一一分析:

public static  void  main(String[] args)  throws  Exception {...}

①        ②       ③      ④            ⑤                          ⑥

1)”方法名④和参数列表⑤:必须相同 “

保证grandpa.print("Sam")时,不管grandpa的真正指向,调用方式都是一样的

2)”权限修饰符①:必须大于等于父类方法“

grandpa指向父类都可以调用,子类方法权限修饰符必然要大于等于父类,才能保证指向子类时调用方法不会因权限不够而出错,e.g.

Son在”覆写“父类方法print时,将权限修饰符改小了—— public缩小为private,此时在Test的main方法中,f指向父类对象Father()时,f.print()顺利执行,但是指向子类对象Son()时,f.print就会报错——因为此时调用的是Son的print()方法,而Son的print()是private即本类可见的。

但其实上面代码是”纸上谈兵“,因为根本不会等到运行时报错,在Java语法层面即报错(因为覆写规则属于Java语法层面,编写时IDE即能检测),看信息:

print(String)' in 'fatherpackage.Son' clashes with 'print(String)' in 'fatherpackage.Father'; attempting to assign weaker access privileges ('private'); was 'public

给父类public方法指定了更低的权限,❌

可见,子类覆写的方法权限修饰符要大于等于父类。将这点应用到异常同理,具体可见下文5)。

3)”static和final方法都是不可以被继承的,因此,想要被覆写的父类方法一定不能被这两个修饰符修饰(上面举的main方法的例子只是拿了一个常见又”齐全“的方法来介绍问题)“

实际上,private,构造方法也不能被继承,当然地,也不能被覆写,不过他们之间有所区别,这一点在其他专题文章中专门有介绍。

4)"返回值③:必须比父类方法相同或小,即返回值类型所属的类必须和父类一样,或者是其子类。"

这个同样可以用”外观上,用调用父类方法的方式调用子类方法,语法层面不会出错“的思路理解:假设父类方法返回值是String,子类方法返回值是Object,那么假设子类方法体中最终真的return了一个Object(类型 > 父类返回值),或者Integer类型对象引用(类型完全不同于父类返回值类型),像之前的grandpa.print("Tom")就会出现问题一样,调用者从面向接口的思维调用方法,很自然地会这么做:

String str = grandpa.print("Tom");

因为考虑到多态的思想——具体行为由运行时对象控制,但是外观上——返回值都是一样的,在调grandpa.print("Tom")时很自然地认为返回值就是String,这也是多态的意义所在。当然可以去查子类具体的返回值,用对应的类型接收,但是这样就毫无意义了。很明显,用父类的返回值类型(String类型)变量接收调用结果,当子类碰到子类返回值比父类大(Object),或者完全不同时(Integer)—— 即接收父方法的变量接收不了子方法的返回值,肯定会发生错误。

另一方面,如果子类方法返回值类型是父类方法返回值类型的父类(完全不同类型的就不用考虑了),如Object和String,也能通过在子类方法体中return做强制类型转换——如上述错误例子 return (String)XXX来达到统一接口的目的,但是一来这样绕一圈弯子给自己找麻烦毫无意义,二来Java的设计者认为把这个安全性交给客户端程序员自己实现是不安全的,所以直接在语法层面”写死了“—— 子类方法返回值类型只能小于等于父类方法。

5)“异常⑥:子类方法不能抛出比父类方法更多的异常【1】。子方法抛出的异常就是是父方法抛出的异常或者其子类。”

异常的基本知识可见其他专题,这里就用到一点,先看方法:

public static  void  main(String[] args)  throws  Exception1, Exception2... {...}

①        ②       ③      ④            ⑤                          ⑥

这个声明的意思是指:main方法没有能力处理方法体中可能出现的类型为Exception1, Exception2...的异常,从而抛出了(throws),交由上一层调用者来捕获处理。当然,调用main方法的方法(调用者)只有两个选择:要么在使用时使用try...catch语句调用main方法,以便捕获处理可能出现的Exception1, Exception2...异常,要么继续抛出即在方法声明时throwsException1, Exception2...。

了解完方法声明中关于异常即⑥的含义,再回顾覆写原则——”外观上,用调用父类方法的方式调用子类方法,语法层面不会出错“,假设一下,如果子方法抛出了与父方法不同的异常,或者是父方法异常的父类,则调用处的处理会出问题——方法调用者还是按处理父方法异常的方法处理子方法。如果子方法抛出了更多的异常,那么调用者对于这些多出来的情况可能没有进行捕获处理(catch),同样的会导致错误。故这一条规定也是符合逻辑的。

四. 总结

Java的方法签名由方法名称+参数列表(个数,类型和顺序)构成的。

重载的目的在于让Java识别出这是不同的方法,故方法签名不同就是充要条件。而覆写的目的在于让Java识别出这是同一个方法,故方法签名一定要相同,同时,为了满足Java多态符合逻辑的实现,覆写对子方法进行了多重限制:访问修饰符扩大,返回值缩小,异常缩小(都是带等号的)。

关于记忆问题,重载不用多说,可以直接用平时最常使用的不同构造方法来记忆,e.g.

覆写,”两同两小一大“原则。

【1】 子类方法返回值应比父类返回值更小或相等
【2】 两同两小一大

Java面试基础问题之(七)—— 方法的重载 VS 覆写规则相关推荐

  1. 关于java方法的重载(Overloading),覆写(Override)以及final 方法的几点说明

    昨天在一个群里面一个群友问道final 方法可不可以重载,我不假思索的说final 方法不能继承不能重载.后来晚上睡不着觉想想总觉得不对头,翻翻书一看自己简直就是胡说八道,才意识到这些基础概念的东西时 ...

  2. java gui中文变方块_150道Java面试基础题(含答案)

    1)Java 中能创建 volatile 数组吗? 能,Java 中可以创建 volatile 类型数组,不过只是一个指向数组的引用,而不是整个数组.我的意思是,如果改变引用指向的数组,将会受到 vo ...

  3. java面试基础题整理(二)

    java面试基础题整理 文章目录 java面试基础题整理 前端技术(HTML.CSS.JS.JQuery等) 在js中怎么样验证数字? js中如何给string这个类型加方法? 谈谈js的定时器? 请 ...

  4. Java面试基础知识III

    Java面试基础知识: 1.C++或Java中的异常处理机制的简单原理和应用. 当JAVA 程序违反了JAVA的语义规则时,JAVA虚拟机就会将发生的错误表示为一个异常.违反语义规则包括2种情况.一种 ...

  5. Java面试基础篇之集合

    文章目录 你知道的集合都有哪些? 哪些集合是线程安全的? Collection 集合类和数组有什么不同? Collection和Collections有什么区别? 如何确保一个集合不能被修改? Lis ...

  6. java面试基础(三)

    java面试基础(三) 十.设计模式 88.说一下你熟悉的设计模式? 89.简单工厂和抽象工厂有什么区别? 90.为什么要使用 spring? 91.解释一下什么是 aop? 92.解释一下什么是 i ...

  7. java final 方法重载_java方法重载和覆写的定义,static和final修饰符的讲解,java面试题...

    方法重载的规则: 1.必须发生在同一个类的下面 2.必须保证方法名字相同 3.参数列表不同 参数的类型不同 参数的个数不同 参数的顺序不同 4.返回值:和返回值无关 5.修饰符:和修饰符无关 6.抛出 ...

  8. java 重载 大于_详解java重载与覆写的区别

    很多同学对于overload和override傻傻分不清楚,建议不要死记硬背概念性的知识,要理解着去记忆. 先给出我的定义: 首先我们来讲讲:重载(Overloading) (1) 方法重载是让类以统 ...

  9. Java面试知识点(全)- Java面试基础部分一

    Java面试知识点(全) :https://nanxiang.blog.csdn.net/article/details/130640392 Java基础 语法基础 面向对象 封装 利用抽象数据类型将 ...

最新文章

  1. 全球最厉害的14位程序员!
  2. java jdk 观察者模式_java观察者模式实现和java观察者模式演化
  3. JSP、EL和JSTL-学习笔记02【MVC】
  4. linux 程序占内存,linux下,一个运行中的程序,究竟占用了多少内存
  5. centos 安装java_自己动手基于centos7安装docker及如何发布tomcat镜像
  6. python跳一跳编程构造_Python + 新手 制作“跳一跳”辅助程序
  7. SQL Server跨库访问
  8. 2020华为软挑总结
  9. Android控件 TextView属性大全
  10. 没有U盘纯硬盘安装linux之manjaro
  11. 倚天鸿蒙系统,倚天屠龙记强化系统详解
  12. 几个比较好看的几个颜色
  13. MySQL数据库灵魂拷问
  14. 草图大师素材是如何快速导入到模型中的呢?草图溜溜来替你解答
  15. 数值分析 解线性方程组的直接法(一)
  16. 豆瓣8.0分,尺度堪比色戒,一部让人绝望的电影
  17. 基于自适应形态学的探月雷达噪声压制方法
  18. 网卡VXLAN的offload技术介绍
  19. HD7刷android2.2全教程
  20. SQL语句判断奇偶数

热门文章

  1. (超详细的Suse15安装指南) 一步一步安装SUSE15操作说明
  2. jquery图片查看插件,支持旋转、放大、缩小、拖拽、缩略图(仿qq图片查看)
  3. openid 获取失败 errcode 40029 errmsg “invalid code, rid: 643e7e48-3d5b7ec3-66ca1f03“
  4. 实验八无线城域网WiMax仿真实验
  5. java保存文件filedialog保存路径 文件名_Winform控件SaveFileDialog用于保存文件
  6. 最真的梦——文/林清玄
  7. 南京理工大学第八届校赛 J-water1
  8. grub 加载不了配置文件不出菜单的情况处理
  9. vue项目和jeccg开源文档
  10. 用QXDM抓取小米6短信包,手机收到短信,但QXDM找不到lte nas emm plain ota incoming message。求问大神指点