我经常喜欢搞清楚一些细节性的东西,如果放在生活中,就是杠精,但是在代码中,就不是了,跑偏了。
今天要说的是重载和重写,其实重载和重写是两个完全不同的概念,只是在中文中,这两个名词有点像,所以经常被拿来进行比较。
重载或是重写要满足一些条件才能成立,但是这些条件,如果死记硬背,不太好记,而且很容易记混。但是如果我们搞清楚他们之间的他们的本质,或者是为了解决什么问题,那么就不会搞混了,再结合顺口溜,就很容易记住。
重载相对简单一些,先说重载
重载是为了解决什么问题呢?其实重载是为了解决同一个类中功能类似方法的命名问题,简单来来说,就是我一个类中,有多个方法的宫内都是类似的,只是方法参数不同,那我就要分别给他们起不同的方法名字。举个简单的例子:我写了一个MyMath类,里面有一系列的求和方法,两个参数的,三个参数的,四个参数的……如果没有重载,那么就得怎么写:
/** * @author ql * @date 2021/6/27 21:24 **/
public class MyMath {
public int addTwo(int a, int b) {
return a + b;
}
public int addThree(int a, int b, int c) {
return a + b + c;
}
public int addFour(int a, int b, int c, int d) {
return a + b + c + d;
}
}
addTwo、addThree、addFour……,每个的方法方法名都不同,不方便记忆,也不方便调用者调用。当然,这里我举的例子方法的参数具有很强的规律性,所以很好记。看个mybatis-plus源码中的例子
这是mybatis-plus中com.baomidou.mybatisplus.extension.service.IService中的方法,如果没有重载,这三个功能类似方法需要起三个不同的方法名。起名就很令人头大,更别说调用者了调用了。
所以,如果利用重载,上面的MyMath类就可以写成:
/** * @author ql * @date 2021/6/27 21:50 **/
public class MyMath {
public int add(int a, int b) {
return a + b;
}
public int add(int a, int b, int c) {
return a + b + c;
}
public int add(int a, int b, int c, int d) {
return a + b + c + d;
}
}
所有的方法都叫add,因为他们的功能都是求和的嘛,无论我需要传入多少个参数,都调用add方法(前提是类中定义了)。方法的命名者和调用者都很省心、方便。(ps:其如果真要写这个例子,应该用可变参数来,无论传入多少个参数都可以,我这里是为了演示重载的作用)
其实我们经常用的System.out.println();这个println方法,就有非常多的重载形式,我们天天都在用。
所以我们打印字符串、数字、字符或是对象,都是用System.out.println(),而不是System.out.printlnString()、System.out.printlnInt()、System.out.printlnChar()、System.out.printlnObject()
我们搞清楚了重载的原理之后,再来梳理构成重载的规则,总结来说就是两同三不同
两同:
三不同(指方法参数):
注意事项:仅仅是返回值类型不同和修饰符不同都不构成方法的重载。仅仅是方法参数的名称不同,也不构成重载
搞清楚了原理,上面这几条规则都很好理解吧。或许有的人要说,方法参数顺序不同不能构成重载。我也正好想提醒大家这儿,严格来说,应是:参数类型不同时顺序不同,能构成重载,参数类型相同,顺序不同,不构成重载,什么意思呢?talk is cheap. show me the code,光说没用,给你两个示例就明白了。
示例1:
// 下面两个方法构成重载
public double add(int a, double b) {
return a + b;
}
public double add(double a, int b) {
return a + b;
}
上面示例1中,两个方法参数顺序不同构成重载。
示例2
// 下面两个方法不构成重载,会报错
public double add(double a, double b) {
return a + b;
}
public double add(double b, double a) {
return a + b;
}
示例2中的两个方法不构成重载,甚至会报错,因为这两个方法根本就是同一个方法。
说完了重载,再来说重写。
重写也叫覆盖重写,重写是为了解决什么问题呢?其实重载是为了子类能有自己不同的实现形式,说白了就是多态。比如:定义了一个动物类,其中有一个进食的方法,动物类有两个子类:猫类和狗类,猫类应该对这个进食的方法重写,具体为:猫吃鱼,而狗类则应重写为:狗啃骨头。因为猫和狗进食的具体内容都不同,所以需要重写。
最常见的莫过于:Object中的toString和equals了,我们写的类,会默认即Object,即便我们没有写。我们经常会在自己写的类中重写toString和equals方法,无论是自己重写还是通过工具生成(大部分情况是通过工具生成的)。但是有没有想过,为啥要重写呢?不重写有什么影响吗?
不重写是可以的,但是会有影响。重写的初衷是什么?具体的子类对父类的方法有具体的实现形式。toString方法的初衷是什么呢,我们在System.out.println()我们的对象时,能看到我们希望看到的内容,而不是Object里面默认实现的,打印一串地址值。
同样,equals方法也是如此,说到这里,我想到了面试经常问的一个问题:==和equals方法有什么区别,可能有的同学很熟悉,信手拈来:对象之间,==比较的是地址,equals比较的是内容。其实我觉得这只说对了一半。有个前提条件,就是比较的对象必须重写了equals方法。不信你看Object中equals方法的实现
我们在重写equals时大部分情况都是用工具直接生成的,或许很多同学都忘了我们重写这个方法的初衷:我们自己来定义我们自己的类,在什么情况下是相等的。其实有时候工具生成的并不准确,比如:我定义了一个学生类,有很多属性:学号、姓名、年龄、性别等等,如果工具生成,一定是去比较每个属性,每个属性相同才算相同,但其实,在同一个学校里,学号相同就判断为是相同的。
说了这么多原理,该说规则了,虽然我们经常都在重写方法,Object中方法,或者是service和dao层。但其实重写的规则里面有很多细节,只是平时没有注意,下面我来梳理一下:
老规矩,先来个顺口溜:两同两小一大,这是浓缩起来的精华,下面我细细的说:
方法名相同,参数列表相同
这个没的说,就字面的意思。忘了说一个前提,重写是在具有继承关系的两个类之间。
子类的返回值类型和抛出的异常必须比父类"小"或相等
这里要分别解释一下
子类的访问权限修饰符比父类更高或者相等
这个也没什么好解释的,字面上的意思
另外还有一些细节需要注意,
这些都是我自己在学习的过程中总结的,当然也包含在网上看到的别人的观点。有什么不对或不懂,欢迎留言指出
上一篇
下一篇