Java面试基础问题之(七)—— 方法的重载 VS 覆写规则
一. 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 覆写规则相关推荐
- 关于java方法的重载(Overloading),覆写(Override)以及final 方法的几点说明
昨天在一个群里面一个群友问道final 方法可不可以重载,我不假思索的说final 方法不能继承不能重载.后来晚上睡不着觉想想总觉得不对头,翻翻书一看自己简直就是胡说八道,才意识到这些基础概念的东西时 ...
- java gui中文变方块_150道Java面试基础题(含答案)
1)Java 中能创建 volatile 数组吗? 能,Java 中可以创建 volatile 类型数组,不过只是一个指向数组的引用,而不是整个数组.我的意思是,如果改变引用指向的数组,将会受到 vo ...
- java面试基础题整理(二)
java面试基础题整理 文章目录 java面试基础题整理 前端技术(HTML.CSS.JS.JQuery等) 在js中怎么样验证数字? js中如何给string这个类型加方法? 谈谈js的定时器? 请 ...
- Java面试基础知识III
Java面试基础知识: 1.C++或Java中的异常处理机制的简单原理和应用. 当JAVA 程序违反了JAVA的语义规则时,JAVA虚拟机就会将发生的错误表示为一个异常.违反语义规则包括2种情况.一种 ...
- Java面试基础篇之集合
文章目录 你知道的集合都有哪些? 哪些集合是线程安全的? Collection 集合类和数组有什么不同? Collection和Collections有什么区别? 如何确保一个集合不能被修改? Lis ...
- java面试基础(三)
java面试基础(三) 十.设计模式 88.说一下你熟悉的设计模式? 89.简单工厂和抽象工厂有什么区别? 90.为什么要使用 spring? 91.解释一下什么是 aop? 92.解释一下什么是 i ...
- java final 方法重载_java方法重载和覆写的定义,static和final修饰符的讲解,java面试题...
方法重载的规则: 1.必须发生在同一个类的下面 2.必须保证方法名字相同 3.参数列表不同 参数的类型不同 参数的个数不同 参数的顺序不同 4.返回值:和返回值无关 5.修饰符:和修饰符无关 6.抛出 ...
- java 重载 大于_详解java重载与覆写的区别
很多同学对于overload和override傻傻分不清楚,建议不要死记硬背概念性的知识,要理解着去记忆. 先给出我的定义: 首先我们来讲讲:重载(Overloading) (1) 方法重载是让类以统 ...
- Java面试知识点(全)- Java面试基础部分一
Java面试知识点(全) :https://nanxiang.blog.csdn.net/article/details/130640392 Java基础 语法基础 面向对象 封装 利用抽象数据类型将 ...
最新文章
- 全球最厉害的14位程序员!
- java jdk 观察者模式_java观察者模式实现和java观察者模式演化
- JSP、EL和JSTL-学习笔记02【MVC】
- linux 程序占内存,linux下,一个运行中的程序,究竟占用了多少内存
- centos 安装java_自己动手基于centos7安装docker及如何发布tomcat镜像
- python跳一跳编程构造_Python + 新手 制作“跳一跳”辅助程序
- SQL Server跨库访问
- 2020华为软挑总结
- Android控件 TextView属性大全
- 没有U盘纯硬盘安装linux之manjaro
- 倚天鸿蒙系统,倚天屠龙记强化系统详解
- 几个比较好看的几个颜色
- MySQL数据库灵魂拷问
- 草图大师素材是如何快速导入到模型中的呢?草图溜溜来替你解答
- 数值分析 解线性方程组的直接法(一)
- 豆瓣8.0分,尺度堪比色戒,一部让人绝望的电影
- 基于自适应形态学的探月雷达噪声压制方法
- 网卡VXLAN的offload技术介绍
- HD7刷android2.2全教程
- SQL语句判断奇偶数
热门文章
- (超详细的Suse15安装指南) 一步一步安装SUSE15操作说明
- jquery图片查看插件,支持旋转、放大、缩小、拖拽、缩略图(仿qq图片查看)
- openid 获取失败 errcode 40029 errmsg “invalid code, rid: 643e7e48-3d5b7ec3-66ca1f03“
- 实验八无线城域网WiMax仿真实验
- java保存文件filedialog保存路径 文件名_Winform控件SaveFileDialog用于保存文件
- 最真的梦——文/林清玄
- 南京理工大学第八届校赛 J-water1
- grub 加载不了配置文件不出菜单的情况处理
- vue项目和jeccg开源文档
- 用QXDM抓取小米6短信包,手机收到短信,但QXDM找不到lte nas emm plain ota incoming message。求问大神指点