重载和重写详解

技术博客 (85) 2023-12-20 18:01:01

前言

我经常喜欢搞清楚一些细节性的东西,如果放在生活中,就是杠精,但是在代码中,就不是了,跑偏了。

今天要说的是重载和重写,其实重载和重写是两个完全不同的概念,只是在中文中,这两个名词有点像,所以经常被拿来进行比较。

重载或是重写要满足一些条件才能成立,但是这些条件,如果死记硬背,不太好记,而且很容易记混。但是如果我们搞清楚他们之间的他们的本质,或者是为了解决什么问题,那么就不会搞混了,再结合顺口溜,就很容易记住。

重载

重载相对简单一些,先说重载

原理

重载是为了解决什么问题呢?其实重载是为了解决同一个类中功能类似方法的命名问题,简单来来说,就是我一个类中,有多个方法的宫内都是类似的,只是方法参数不同,那我就要分别给他们起不同的方法名字。举个简单的例子:我写了一个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源码中的例子
重载和重写详解 (https://mushiming.com/) 技术博客 第1张

这是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方法,就有非常多的重载形式,我们天天都在用。
重载和重写详解 (https://mushiming.com/) 技术博客 第2张

所以我们打印字符串、数字、字符或是对象,都是用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方法的实现
重载和重写详解 (https://mushiming.com/) 技术博客 第3张
我们在重写equals时大部分情况都是用工具直接生成的,或许很多同学都忘了我们重写这个方法的初衷:我们自己来定义我们自己的类,在什么情况下是相等的。其实有时候工具生成的并不准确,比如:我定义了一个学生类,有很多属性:学号、姓名、年龄、性别等等,如果工具生成,一定是去比较每个属性,每个属性相同才算相同,但其实,在同一个学校里,学号相同就判断为是相同的。

规则

说了这么多原理,该说规则了,虽然我们经常都在重写方法,Object中方法,或者是service和dao层。但其实重写的规则里面有很多细节,只是平时没有注意,下面我来梳理一下:

老规矩,先来个顺口溜:两同两小一大,这是浓缩起来的精华,下面我细细的说:

1. 两同:

方法名相同,参数列表相同

这个没的说,就字面的意思。忘了说一个前提,重写是在具有继承关系的两个类之间。

2.两小:

子类的返回值类型和抛出的异常必须比父类"小"或相等

这里要分别解释一下

  • 返回值类型:对于引用数据类型,这里的小指的是,子类的返回值类型必须是父类返回值类型的子类,相等就同种类型。对于基本数据类型,必须相同才可以。
  • 抛出的异常:子类抛出的异常必须是父类抛出异常子类或者同类,对于抛出的个数没有限制,只要保证类型是子类或者相同就可以。注意:这里的异常指的是只编译时异常,运行时异常不再此列,随便抛都可以。就算父类方法没抛异常,子类重写都可以抛运行时异常

3.一大:

子类的访问权限修饰符比父类更高或者相等

这个也没什么好解释的,字面上的意思

细节

另外还有一些细节需要注意,

  1. 覆盖只是针对方法,和属性无关
  2. 私有方法无法覆盖,构造方法无法被继承,所以也无法覆盖
  3. 方法覆盖是针对实例方法而言,静态方法覆盖没有意义。因为静态方法和对象无关,只和类有关,在多态下,它不会看你的对象是谁,只会取决于类,使用对象的引用去调用静态方法的时候,系统会自动的将引用换成类名。静态方法可以被继承,但是,不能被覆盖重写。如果父类中定义的静态方法在子类中被重新定义,那么在父类中定义的静态方法将被隐藏。可以使用语法:父类名.静态方法调用隐藏的静态方法。如果父类中含有一个静态方法,且在子类中也含有一个返回类型、方法名、参数列表均与之相同的静态方法,那么该子类实际上只是将父类中的该同名方法进行了隐藏,而非重写。换句话说,父类和子类中含有的其实是两个没有关系的方法,它们的行为也并不具有多态性。通过一个指向子类对象的父类引用变量来调用父子同名的静态方法时,只会调用父类的静态方法,而不是像多态那样去调用子类的同名的静态方法。

这些都是我自己在学习的过程中总结的,当然也包含在网上看到的别人的观点。有什么不对或不懂,欢迎留言指出

THE END

发表回复