类与接口(四)方法重载解析

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

转载声明:转载请注明出处,本技术博客是本人原创文章

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> 类与接口(四)方法重载解析

一、方法重载简介

方法重载: 当两个(或多个)方法的名称相同,而参数的对应类型或个数不同时,我们就说方法重载了。当然,编译器也能识别出来。

编译器是如何识别调用了哪个方法?

  在往下讲前,我们先来了解一下:编译器是怎么才能识别出程序调用了那个方法。其实,这个问题就是在问:在调用方法处,编译器能得到调用方法的什么信息,从而能找到对应的方法?我们一般的方法调用是这样的:


method( vars );

也就是说,方法调用处,一共为编译器提供两个信息:方法名、参数列表。 所以,编译器只能通过 方法名 和 参数列表 来识别调用方法

有一道面试题问:为什么不能通过返回类型来重载方法? 就是上面所说的,方法调用处并没有提供返回类型的信息,所以当多个方法只有返回类型不一样时,编译器就不知道调用了那个方法了。

我们已经知道了编译器是怎么识别方法的了,而对于方法重载,其要求方法名是一样的,那么我们只需要关注 参数列表 便可以了参数列表区分,或者说重载方法的区分:

  • 参数的个数- 参数的类型- 参数的顺序

    二、方法重载的匹配选择

  方法重载后,方法调用处可能会遇到应该选择哪个重载方法的问题,如果只有唯一个重载方法可以匹配,那么就没问题。然而,大部分情况却是有多个重载方法是可以匹配的,那么这时候就应该选择最合适的重载方法.

匹配最合适、最明确的重载方法,其实就是实参列表去匹配当前重载方法中形参列表,寻找与实参列表最相近的形参列表

1、基本类型之间的重载

对于基本类型来说,从“短类型”扩展成“长类型”是默认允许、自动进行的,这就可能造成了实参可能匹配到多个“长类型”的形参,看个简单例子:


 public static void main(String[] args) {
    short s = 4;
    m(s);
}

 public static void m(int x){<!-- -->//方法一
    System.out.println("重载方法一");
}

 public static void m(float x){<!-- -->//方法二
    System.out.println("重载方法二");
}

运行结果

重载方法一

short类型 可以默认自动转换成 int、’float’类型。但 m(s)真正匹配选择的是 m(int x)方法,而不是形参长度更长的m(float x)。所以可以看出,基本类型的形参匹配规则是: 如果没有匹配到精确类型的形参,则优先匹配 存储长度(范围)大于且是最接近实参的存储长度的形参,从而确定调用哪个重载方法

2、引用类型间的重载

  对于引用类型来说,可以匹配到多个重载方法的原因是:引用类型的对象进行类型上转也是JVM默认自动进行的,那么就可能匹配多个祖先类型的形参看下面的例子:


public class Test_3 {<!-- -->

public static void main(String[] args)  {

   Children children = new Children();
   someMethod(children);
}   

public static void someMethod(Ancestor an) {<!-- -->//重载方法1
    System.out.println("this is Ancestor Method!");
}


public static void someMethod(Parent an) {<!-- -->//重载方法2

    System.out.println("this is Parent Method!");
}
}

//3个具有继承关系的类
class Ancestor{<!-- -->//祖先类    
}

class Parent extends Ancestor{<!-- -->//父类,继承于Ancestor  
}

class Children extends Parent{<!-- -->//子类,继承于Parent    
}

运行结果

this is Parent Method!

  可以看出,引用类型与基本类型一样,都是选择”最明确的方法“, 引用类型间选择最明确的重载方法的规则是: 如果找不到重载方法的形参的引用类型与实参一致,则实参优先匹配 在继承树结构上,离实参类型最近的形参,则此形参所在的重载方法便是最明确的重载方法。

3、自动装箱拆箱、可变参数类型

  装箱拆箱、以及可变参数列表的处理都是由编译器自动处理,也就是说是默认自动进行的,这同样会让实参列表可以匹配多个形参列表 ,可以匹配多个重载方法

  此小节将会涉及到基本类型、引用类型、自动装箱拆箱可变参数的重载方法匹配的优先级。

看下面的例子,这个例子包括很多情况:


public class Test_3 {

public static void main(String[] args)  {

   short s = 5; 
   overloadMethod(s);// test1

   Integer i = 10;
   overloadMethod(i);//test2

   overloadMethod(s,s);//test3
}   

public static void overloadMethod(int a) { //m1

    System.out.println("调用  overloadMethod(int)");
}

public static void overloadMethod(Short in) {<!-- -->//m2

    System.out.println("调用  overloadMethod(short)");
}

public static void overloadMethod(int a,int b) {<!-- -->//m3

    System.out.println("调用  overloadMethod(int,int)");
}

public static void overloadMethod(short... s) { //m4

    System.out.println("调用  overloadMethod(short...)");
}

public static void overloadMethod(Integer... i) {<!-- -->//m5

    System.out.println("调用  overloadMethod(Integer...)");
}
}

运行结果

调用 overloadMethod(int) 调用 overloadMethod(int) 调用 overloadMethod(int,int)

我们来分析一下上面的例子中,方法调用处可以匹配到的方法:

  • test1 处的方法调用可以匹配的重载方法有:m1(基本类型的短类型自动转为长类型)、m2(自动装箱)、m4(可变参数列表)- test2 处的方法调用可以匹配的重载方法有:m1(自动拆箱)、m5(可变参数列表);- test3 处的方法调用可以匹配的重载方法有:m3(基本类型的短类型自动转换成长类型)、m4(可变参数列表)
    查看输出结果,发现:test1处选择了m1、test2选择了m1,test3选择了m3。 根据这样的结果,也就是这几种形参匹配规则还是有个匹配的顺序的。对重载方法的选择作以下总结:
  • 先按照实参的类型(基本类型或引用类型)对应匹配规则,进行查找最相近的形参列表,从而找到最明确的重载方法;找不到,则执行第二步;- 对实参进行装箱或拆箱转换(前提是实参是基本类型或者是包装类),再安按照转换得到的类型进行匹配形参的类型(形参类型与转换类型要一致,特别注意基本类型);找不到,则执行第三步;- 匹配形参是可变参数的重载方法,此时,形参的类型可以是 实参的类型以及通过 基本类型的短转长、自动装箱拆箱、祖先类型 得到的转换类型。
    将上面的总结再简化一下,可以简化成 重载方法的形参匹配规则的优先级:

当前类型(基本类型或引用类型)的匹配规则 > 自动装箱拆箱 > 可变参数列表

再看一个例子:


public class MyTest {
    public static void main(String[] args) {
      int a = 5;
      short s = 8;
      m(a,s);
    }

public static void m(int a,Short b) {<!-- -->//m1
    System.out.println("调用了m(int,Short)");
}

public static void m(float f,short s) {<!-- -->//m2
    System.out.println("调用了m(float,short)");
}   
}

运行结果

调用了m(float,short)

分析: 实参都是基本类型,优先考虑形参列表都是基本类型的重载方法,找不到才考虑自动装箱拆箱

4、泛型方法的重载

泛型方法的重载规则: 将泛型方法的类型变量擦除,然后与非泛型方法一样,按照上面所说的三种规则一一匹配


public static void main(String[] args)  {
//创建Runnable对象
Runnable r = new Runnable() { public void run(){} };
//调用泛型方法
  m(r); 
}

public static &lt;T&gt; void m(T t) {<!-- -->//m1
    System.out.println("调用了&lt;T&gt; void m(T)");
}

public static &lt;T extends Runnable&gt; void m(T t) {<!-- -->//m2
    System.out.println("调用了&lt;T extends Runnable&gt; void m(T t)");
}

运行结果

调用了 void m(T t)

上面的两个泛型方法 m(T t)进行类型擦除后是:


public static void m(Object t);

public static void m(Runnable t);

显然,调用方法应该是m2,与运行结果相符;

5. 没法确定的重载方法调用

  尽管编译器会按照上面所说的三种优先级别去让实参匹配形参,然而匹配的结果却不一定是唯一的,也就是说会匹配到多个方法,从而无法确定调用那个方法,编译失败

情况一: 实参列表的所有最佳匹配的形参不在同一个方法中


public class MyTest {
    public static void main(String[] args) {
      int aa = 5;
      short ss = 8;
      m(aa,ss);//编译不通过,无法确定调用了那个重载方法
    }

public static void m(int a,double b) {<!-- -->//m1
    System.out.println("调用了m(int,Short)");
}

public static void m(float f,int c) {<!-- -->//m2
    System.out.println("调用了m(float,short)");
}
}

分析

m(aa,ss)的调用编译失败,因为实参 aa的最佳匹配 m(int,double)的第一个形参,而实参ss的最佳匹配则是 m(float,short)的第二个形参。 因此,实参列表的 (aa,ss)的最佳形参类型匹配分开在了两个重载方法中。 注意一下,即使某个重载方法的形参列表包含最多的最相近的形参类型,只要不是全部,那么依旧无法确定调用了哪个重载方法。

情况二可变参数列表的特殊性 – 无法根据可变参数的类型来重载方法


public static void m(short... s) {}

public static void m(Short... s) {}

public static void m(int... s) {}

调用测试例子:


short s = 8;
Short sl = 10;
m(s,s);//编译不通过
m(s,sl);//编译不通过
m(sl,sl);//编译不通过

重写 与 重载的区别

  • 重写是针对父类与子类间的方法,即必须先得继承父类的方法。而重载则没有这种限制。- 重写要求方法的 而方法重载则只需要 方法名相同,参数列表不同就行了。- 方法重载时,方法的调用是在编译时期就已经确定了调用那个方法;方法重写,则要在运行时,才能确定调用的是子类还是父类的方法。

    作者:jinggod 出处:

原文地址:https://sihai.blog.csdn.net/article/details/79435950

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

转载声明:转载请注明出处,本技术博客是本人原创文章

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> 类与接口(四)方法重载解析


 上一篇
类与接口(五)java多态、方法重写、隐藏 类与接口(五)java多态、方法重写、隐藏
一、Java多态性面向对象的三大特性:封装、继承、多态。 多态的类型,分为以下两种: 编译时多态: 指的是 方法重载。编译时多态是在编译时确定调用处选择那个重载方法,所以也叫 静态多态,算不上真正的多态。所以,一般说的多态都是运行时的多态
2021-04-04
下一篇 
java后端程序员1年工作经验总结 java后端程序员1年工作经验总结
戳上面的蓝字关注我们哦!  精彩内容    1.引言   毕业已经一年有余,这一年里特别感谢技术管理人员的器重,以及同事的帮忙,学到了不少东西。这一年里走过一些弯路,也碰到一些难题,也受到过做为一名开发却经常为系统维护和发布当救火队
2021-04-04