重载方法匹配规则
一、方法重载简介
1、方法重载: 当两个(或多个)方法的名称相同,而参数的对应类型或个数不同时,我们就说方法重载了。
2、编译器是如何识别调用了哪个方法?答:通过方法名
和参数列表
来识别调用哪个方法。重载方法选择也是通过方法名
和参数列表
来判断的。
3、注意,编译器不会通过返回类型来确定调用哪个方法(即,如果两个方法的方法名和参数列表一样,但是返回值不一样,编译器会认为是一个方法的,所以重载的定义中没有涉及到返回值类型)。
4、参数列表区分(下面三个添加有任何一个不同,那么参数列表就是不同的):
- 参数的个数
- 参数的顺序
- 参数的类型(int和Integer是不一样的类型哈,int和long也是不一样的类型哈,所以f(int i)、f(Integer i)、f(long i)是重载关系。但是f(int i)、f(Integer i)、f(long i)三个方法都可能被调用f(12)的时候选择到。这里就是我们今天主要想讲的内容了。如果存在f(int i)、f(Integer i)、f(long i)三个方法,那么调用f(12)的时候会选择f(int i)进行执行,如果存在f(Integer i)、f(long i)两个方法,那么调用f(12)的时候会选择f(long i)进行执行。
例子:
package com.yimeng.chongzai;
public class MyTest {
public void f(int i){
System.out.println("f(int i)"+i);
}
public void f(Integer i){
System.out.println("f(Integer i)"+i);
}
public void f(long i){
System.out.println("f(long i)"+i);
}
public static void main(String[] args) {
MyTest myTest = new MyTest();
myTest.f(12);
}
}
执行结果:f(int i)12
package com.yimeng.chongzai;
public class MyTest2 {
// public void f(int i){
// System.out.println("f(int i)"+i);
// }
public void f(Integer i){
System.out.println("f(Integer i)"+i);
}
public void f(long i){
System.out.println("f(long i)"+i);
}
public static void main(String[] args) {
MyTest2 myTest = new MyTest2();
myTest.f(12);
}
}
执行结果:f(long i)12
package com.yimeng.chongzai;
public class MyTest3 {
// public void f(int i){
// System.out.println("f(int i)"+i);
// }
public void f(Integer i){
System.out.println("f(Integer i)"+i);
}
// public void f(long i){
// System.out.println("f(long i)"+i);
// }
public static void main(String[] args) {
MyTest3 myTest = new MyTest3();
myTest.f(12);
}
}
执行结果:f(Integer i)12
为什么是上面的结果呢?我们看到在f(int i)存在的情况下,一定不会选择f(Integer i)和f(long i)。在f(long i)存在的情况下,一定不会选择f(Integer i)。在只有f(Integer i)存在的情况下才会选择f(Integer i)。虽然,myTest.f(12);调用时,选择f(int i)、f(Integer i)、f(long i)任何一个都是可以的,但是它的选择好像是有偏向性的。【myTest.f(12);可以选择f(long i)是因为jvm可以自动转型,myTest.f(12);可以选择f(Integer i)是因为jvm可以自动拆箱和装箱。】
下面我们来研究研究!
二、java重载函数匹配原则
方法重载后,方法调用处可能会遇到应该选择哪个重载方法的问题,如果只有唯一个重载方法可以匹配,那么就没问题,肯定选择这个唯一匹配的重载方法进行执行。但是,如果是有多个重载方法可以匹配调用的实参,那么这时候就会选择最合适的重载方法去执行。那么jvm选择的规则是什么呢?
一个方法调用能匹配多个方法声明的原因:
- 基本数据类型的类型转换,所以方法的形参可以和实参不完全一样,形参可以比实参范围大,比如long可以接受int的实参。
- 可以用接口或者父类来接收子类或者实现类的实参。多态的原因。
- 拆箱装箱。实参是基本类型,形参是包装类型,或者实参是包装类型,形参是基本类型,那么可以自动拆箱和装箱。
- 可变参数。
结论1:精确匹配是优先级最高的。
package com.yimeng.chongzai1;
public class MyTest {
public void f(int i){
System.out.println("f(int i)"+i);
}
public void f(Integer i){
System.out.println("f(Integer i)"+i);
}
public void f(long i){
System.out.println("f(long i)"+i);
}
public void f(int... i){
System.out.println("f(int... i)"+i);
}
public static void main(String[] args) {
MyTest myTest = new MyTest();
myTest.f(12);
}
}
结果:f(int i)12
结论2:转型的优先级比拆箱装箱和可变参数高
package com.yimeng.chongzai1;
public class MyTest {
// public void f(int i){
// System.out.println("f(int i)"+i);
// }
public void f(Integer i){
System.out.println("f(Integer i)"+i);
}
public void f(long i){
System.out.println("f(long i)"+i);
}
public void f(int... i){
System.out.println("f(int... i)"+i);
}
public static void main(String[] args) {
MyTest myTest = new MyTest();
myTest.f(12);
}
}
结果:f(long i)12
结论3:拆箱装箱的优先级比可变参数高
package com.yimeng.chongzai1;
public class MyTest {
// public void f(int i){
// System.out.println("f(int i)"+i);
// }
public void f(Integer i){
System.out.println("f(Integer i)"+i);
}
// public void f(long i){
// System.out.println("f(long i)"+i);
// }
public void f(int... i){
System.out.println("f(int... i)"+i);
}
public static void main(String[] args) {
MyTest myTest = new MyTest();
myTest.f(12);
}
}
结果:f(Integer i)12
结论4:可变参数的优先级是最低的
package com.yimeng.chongzai1;
public class MyTest {
// public void f(int i){
// System.out.println("f(int i)"+i);
// }
// public void f(Integer i){
// System.out.println("f(Integer i)"+i);
// }
// public void f(long i){
// System.out.println("f(long i)"+i);
// }
public void f(int... i){
System.out.println("f(int... i)"+i);
}
public static void main(String[] args) {
MyTest myTest = new MyTest();
myTest.f(12);
}
}
结果:f(int… i)[I@1b6d3586
因为可变参数是的形参你可以理解为一个数组,所以打印i的结果是[I@1b6d3586
结论5:jvm不能做到,对实参既类型转换,又进行拆箱装箱再去匹配方法。当然哈,也不能既类型转换,又进行拆箱装箱去匹配可变参数的方法。
package com.yimeng.chongzai1;
public class MyTest {
// public void f(int i){
// System.out.println("f(int i)"+i);
// }
// public void f(Integer i){
// System.out.println("f(Integer i)"+i);
// }
// public void f(long i){
// System.out.println("f(long i)"+i);
// }
// public void f(int... i){
// System.out.println("f(int... i)"+i);
// }
public void f(Long i){
System.out.println("f(Long i)"+i);
}
public void f(Long... i) {
System.out.println("f(Long... i)" + i);
}
public static void main(String[] args) {
MyTest myTest = new MyTest();
// myTest.f(12);报错
}
}
结论6:匹配拆箱装箱后的可变参数的方法是可以的。
package com.yimeng.chongzai1;
public class MyTest {
// public void f(int i){
// System.out.println("f(int i)"+i);
// }
// public void f(Integer i){
// System.out.println("f(Integer i)"+i);
// }
// public void f(long i){
// System.out.println("f(long i)"+i);
// }
// public void f(int... i){
// System.out.println("f(int... i)"+i);
// }
// public void f(Long i){
// System.out.println("f(Long i)"+i);
// }
// public void f(Long... i) {
// System.out.println("f(Long... i)" + i);
// }
public void f(Integer... i) {
System.out.println("f(Integer... i)" + i);
}
public static void main(String[] args) {
MyTest myTest = new MyTest();
myTest.f(12);
}
}
结果:f(Integer… i)[Ljava.lang.Integer;@1b6d3586
结论7:进行类型转换后的可变参数匹配的方法也是可以的
package com.yimeng.chongzai1;
public class MyTest {
// public void f(int i){
// System.out.println("f(int i)"+i);
// }
// public void f(Integer i){
// System.out.println("f(Integer i)"+i);
// }
// public void f(long i){
// System.out.println("f(long i)"+i);
// }
// public void f(int... i){
// System.out.println("f(int... i)"+i);
// }
// public void f(Long i){
// System.out.println("f(Long i)"+i);
// }
// public void f(Long... i) {
// System.out.println("f(Long... i)" + i);
// }
// public void f(Integer... i) {
// System.out.println("f(Integer... i)" + i);
// }
public void f(long... i) {
System.out.println("f(long... i)" + i);
}
public static void main(String[] args) {
MyTest myTest = new MyTest();
myTest.f(12);
}
}
结果:f(long… i)[J@1b6d3586
结论8:对实参拆箱装箱后,通过多态匹配的方法也能可以。
package com.yimeng.chongzai1;
public class MyTest {
// public void f(int i){
// System.out.println("f(int i)"+i);
// }
// public void f(Integer i){
// System.out.println("f(Integer i)"+i);
// }
// public void f(long i){
// System.out.println("f(long i)"+i);
// }
// public void f(int... i){
// System.out.println("f(int... i)"+i);
// }
//
// public void f(Long i){
// System.out.println("f(Long i)"+i);
// }
// public void f(Long... i) {
// System.out.println("f(Long... i)" + i);
// }
//
// public void f(Integer... i) {
// System.out.println("f(Integer... i)" + i);
// }
//
// public void f(long... i) {
// System.out.println("f(long... i)" + i);
// }
public void f(Number i){
System.out.println("f(Number i)"+i);
}
public static void main(String[] args) {
MyTest myTest = new MyTest();
myTest.f(12);
}
}
结果:f(Number i)12
当然Object也是int装箱后的父类,所以下面这样也行:
package com.yimeng.chongzai1;
public class MyTest {
// public void f(int i){
// System.out.println("f(int i)"+i);
// }
// public void f(Integer i){
// System.out.println("f(Integer i)"+i);
// }
// public void f(long i){
// System.out.println("f(long i)"+i);
// }
// public void f(int... i){
// System.out.println("f(int... i)"+i);
// }
//
// public void f(Long i){
// System.out.println("f(Long i)"+i);
// }
// public void f(Long... i) {
// System.out.println("f(Long... i)" + i);
// }
//
// public void f(Integer... i) {
// System.out.println("f(Integer... i)" + i);
// }
//
// public void f(long... i) {
// System.out.println("f(long... i)" + i);
// }
// public void f(Number i){
// System.out.println("f(Number i)"+i);
// }
public void f(Object i){
System.out.println("f(Object i)"+i);
}
public static void main(String[] args) {
MyTest myTest = new MyTest();
myTest.f(12);
}
}
结果:f(Object i)12
结论9:装箱后有多个多态方法能匹配,那么理装箱后类型的血缘关系近的优先级高。即,离装箱后类型继承链最近的那个多态优先级高。
package com.yimeng.chongzai1;
public class MyTest {
// public void f(int i){
// System.out.println("f(int i)"+i);
// }
// public void f(Integer i){
// System.out.println("f(Integer i)"+i);
// }
// public void f(long i){
// System.out.println("f(long i)"+i);
// }
// public void f(int... i){
// System.out.println("f(int... i)"+i);
// }
//
// public void f(Long i){
// System.out.println("f(Long i)"+i);
// }
// public void f(Long... i) {
// System.out.println("f(Long... i)" + i);
// }
//
// public void f(Integer... i) {
// System.out.println("f(Integer... i)" + i);
// }
//
// public void f(long... i) {
// System.out.println("f(long... i)" + i);
// }
public void f(Number i){
System.out.println("f(Number i)"+i);
}
public void f(Object i){
System.out.println("f(Object i)"+i);
}
public static void main(String[] args) {
MyTest myTest = new MyTest();
myTest.f(12);
}
}
结果:f(Number i)12
结论10:继承关系对比血缘关系无法比较的时候,jvm无法判断,所以会报错。
package com.yimeng.chongzai1;
public class MyTest {
// public void f(int i){
// System.out.println("f(int i)"+i);
// }
// public void f(Integer i){
// System.out.println("f(Integer i)"+i);
// }
// public void f(long i){
// System.out.println("f(long i)"+i);
// }
// public void f(int... i){
// System.out.println("f(int... i)"+i);
// }
//
// public void f(Long i){
// System.out.println("f(Long i)"+i);
// }
// public void f(Long... i) {
// System.out.println("f(Long... i)" + i);
// }
//
// public void f(Integer... i) {
// System.out.println("f(Integer... i)" + i);
// }
//
//
// public void f(long... i) {
// System.out.println("f(long... i)" + i);
// }
// public void f(Object i){
// System.out.println("f(Object i)"+i);
// }
public void f(Comparable<Integer> i) {
System.out.println("f(Comparable<Integer> i)" + i);
}
public void f(Number i){
System.out.println("f(Number i)"+i);
}
public static void main(String[] args) {
MyTest myTest = new MyTest();
// myTest.f(12);// 报错,说两个f(Number i)、f(Comparable<Integer> i) 方法的优先级无法比较,因为Integer的声明是这样的:public final class Integer extends Number implements Comparable<java.lang.Integer> {},Number和Comparable<java.lang.Integer>无法比较与Integer的谁血缘关系比较近
}
}
有人可能会怀疑f(Comparable i)能不能接受int的类型。我测试下面的代码是可以执行的,所以f(Comparable i)能接受int的类型:
package com.yimeng.chongzai1;
public class MyTest {
// public void f(int i){
// System.out.println("f(int i)"+i);
// }
// public void f(Integer i){
// System.out.println("f(Integer i)"+i);
// }
// public void f(long i){
// System.out.println("f(long i)"+i);
// }
// public void f(int... i){
// System.out.println("f(int... i)"+i);
// }
//
// public void f(Long i){
// System.out.println("f(Long i)"+i);
// }
// public void f(Long... i) {
// System.out.println("f(Long... i)" + i);
// }
//
// public void f(Integer... i) {
// System.out.println("f(Integer... i)" + i);
// }
//
//
// public void f(long... i) {
// System.out.println("f(long... i)" + i);
// }
// public void f(Object i){
// System.out.println("f(Object i)"+i);
// }
public void f(Comparable<Integer> i) {
System.out.println("f(Comparable<Integer> i)" + i);
}
// public void f(Number i){
// System.out.println("f(Number i)"+i);
// }
public static void main(String[] args) {
MyTest myTest = new MyTest();
myTest.f(12);// 报错,说两个f(Number i)、f(Comparable<Integer> i) 方法的优先级无法比较,因为Integer的声明是这样的:public final class Integer extends Number implements Comparable<java.lang.Integer> {},Number和Comparable<java.lang.Integer>无法比较与Integer的谁血缘关系比较近
}
}
结果:f(Comparable i)12
结论11:各种情况中优先级如下。其中只进行类型转换的优先级最高,类型转换会优先选择字节小的类型,然后就是拆箱装箱,然后就是拆箱装箱后的继承最近原则去找多态匹配的,最后才是可变参数。其中要注意的是,可变参数的f(int… i)和f(Integer… i)优先级等价。
package com.yimeng.chongzai1;
public class MyTest {
// 1
public void f(int i){
System.out.println("f(int i)"+i);
}
// 2
public void f(long i){
System.out.println("f(long i)"+i);
}
// 3
public void f(double i){
System.out.println("f(double i)"+i);
}
// 4
public void f(Integer i){
System.out.println("f(Integer i)"+i);
}
// 5
public void f(Number i){
System.out.println("f(Number i)"+i);
}
// 5
public void f(Comparable<Integer> i) {
System.out.println("f(Comparable<Integer> i)" + i);
}
// 6
public void f(Object i){
System.out.println("f(Object i)"+i);
}
// 7
public void f(int... i){
System.out.println("f(int... i)"+i);
}
// 7
public void f(Integer... i) {
System.out.println("f(Integer... i)" + i);
}
// 8
public void f(long... i) {
System.out.println("f(long... i)" + i);
}
// 无法匹配。
public void f(Long... i) {
System.out.println("f(Long... i)" + i);
}
public static void main(String[] args) {
MyTest myTest = new MyTest();
myTest.f(12);
}
}
总共有下面几个匹配原则:
- 精确匹配原则:如果重载版本的参数与实际参数完全相同,则选择该版本。
- 继承最近原则:选择实参最近的祖宗匹配。
- 基本类型自动类型转换原则:如果没有精确匹配的版本,那么编译器会尝试将实际参数自动地转换成重载版本所需要的参数类型,然后再进行方法选择。转换的优先级顺序为:byte → short → int → long → float → double 和 char。
- 自动拆箱和装箱匹配原则:允许拆箱和装箱。
- 允许可变参数匹配原则:允许可变参数参与匹配。
继承最近原则是原本就有的,即,不用类型变化就存在的规则。所以,精确匹配找不到,会先看继承最近是不是能找到,拆箱装箱后也是会先看精确匹配能不能找到,不能找到再看继承最近能不能找到。
比如下面这个例子:
package com.yimeng.chongzai1;
public class MyTest {
// 1
public void f(Integer i){
System.out.println("f(Integer i)"+i);
}
// 2
public void f(Number i){
System.out.println("f(Number i)"+i);
}
// 2
public void f(Comparable<Integer> i) {
System.out.println("f(Comparable<Integer> i)" + i);
}
// 3
public void f(Object i){
System.out.println("f(Object i)"+i);
}
// 4
public void f(int i){
System.out.println("f(int i)"+i);
}
// 5
public void f(long i){
System.out.println("f(long i)"+i);
}
// 6
public void f(double i){
System.out.println("f(double i)"+i);
}
// 7
public void f(int... i){
System.out.println("f(int... i)"+i);
}
// 7
public void f(Integer... i){
System.out.println("f(Integer... i)"+i);
}
// 8
public void f(double... i){
System.out.println("f(double... i)"+i);
}
// 8
public void f(Number... i){
System.out.println("f(Number... i)"+i);
}
// 无法匹配
public void f(Double... i){
System.out.println("f(double... i)"+i);
}
public static void main(String[] args) {
MyTest myTest = new MyTest();
myTest.f(new Integer(12));
}
}
所以结论是,优先级如下,拿下面这个条目一个个去比对就可以判断哪个方法最先被匹配到:
- 不进行拆箱装箱,不类型自动转换,不允许可变参数的匹配的前提下,精确匹配
- 不进行拆箱装箱,不类型自动转换,不允许可变参数的匹配的前提下,继承最近匹配(实参是引用类型的才有继承可言)
- 放开类型自动转换限制进行匹配(实参是基本类型的才会把实参进行自动转换再去匹配)
- 允许拆箱装箱。拆箱后,精确匹配。精确匹配不到,就拆箱后类型自动转换匹配。装箱后,精确匹配,如果精确匹配不到,那么就继承匹配。
- 允许可变参数参与匹配。把实参进行拆箱装箱后,和不拆箱装箱的实参,一起进行匹配,如果都找到了,那么就找到两个优先级一样高的方法,那么就会报错,不知道选择哪个。如果一起匹配找不到,再把包装类型的结果进行继承最近匹配,同时把基本类型进行类型转换匹配,如果同时匹配到,那么就找到两个优先级一样高的方法,那么就会报错,不知道选择哪个。如果都没有找到,那么就报错了,因为没有重载方法。
如果有多个重载方法可以匹配调用者的实参,那么这个调用语句执行的时候,一定会选择最匹配的重载方法进行执行,不是最匹配的重载方法一定不会被执行(当然哈,这个重载方法在实参为某个情况它的匹配等级不高,执行的优先级不高,但是如果换一种实参,换一个调用场景,那么它匹配度可能就高了,执行的优先级也就高了,所以不是说某个重载方法一定不会执行的)。
如果是多个参数的情况呢?
例子1:
package com.yimeng.chongzai1;
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) {
System.out.println("调用了m(int,Short)");
}
public static void m(float f,short s) {
System.out.println("调用了m(float,short)");
}
}
执行结果:调用了m(float,short)
结论:先不解开拆箱转型和可变参数的限制,使用精确匹配,如果精确匹配不到,再用继承或者自动类型转换匹配。
例子2:
package com.yimeng.chongzai1;
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) {
System.out.println("调用了m(int,Short)");
}
public static void m(float f,short s) {
System.out.println("调用了m(float,short)");
}
public static void m(int a,double b) {
System.out.println("调用了m(int,double)");
}
}
结论:如果第一个参数匹配某个方法,第二个参数最匹配另一个方法,那么就会编译不通过,编译器无法进行选择。
例子3:
package com.yimeng.chongzai1;
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) {
System.out.println("调用了m(int,Short)");
}
public static void m(float f,short s) {
System.out.println("调用了m(float,short)");
}
public static void m(int a,Double b) {
System.out.println("调用了m(int,Double)");
}
}
执行结果:调用了m(float,short)
结论:上面这里编译可以通过。我的理解是,一开始匹配的时候,编译器没有放开拆箱装箱匹配,所以,m(int a,Short b)和m(int a,Double b)没有被匹配到,没有和m(float f,short s)进行竞争。所以没有混淆。
泛型方法的重载会怎么样?
泛型方法的重载规则: 将泛型方法的类型变量擦除,然后与非泛型方法一样,按照上面所说的规则一一匹配
package com.yimeng.chongzai1;
public class MyTest {
public static void main(String[] args) {
//创建Runnable对象
Runnable r = new Runnable() {
@Override
public void run() {
}
};
//调用泛型方法
m(r);
}
public static <T> void m(T t) {//m1
System.out.println("调用了<T> void m(T)");
}
public static <T extends Runnable> void m(T t) {//m2
System.out.println("调用了<T extends Runnable> void m(T t)");
}
}
执行结果:调用了 void m(T t)
上面的两个泛型方法m(T t)
进行类型擦除后是:
public static void m(Object t);
public static void m(Runnable t);
显然,调用方法应该是m2,与运行结果相符;
综上,我的理解是:
选择的规则:精确匹配绝对是任何情况下最高的。类型自动转换和继承最近原则的优先级是一样的,其中类型自动转换是针对实参或者转换后的实参是基本类型的,继承最近原则是针对实参或者转换后的实参是引用类型的。
上面的规则是编译器的选择规则。
还就是放开限制的规则:它的规则是,能不放开限制就不放开限制,不放开限制的情况下用上面的选择规则来找。如果不放开限制找不到,那么把拆箱和装箱放开,再用上面的选择规则来找,拆箱装箱放开后,实参是int,它会把实参当作是int或Integer,然后如果用int或者Integer同时匹配到的,那么就是优先级一样。如果还是找不到,那么就放开可变参数,因为在放开可变参数前,已经放开了拆箱和装箱,所以它也是用int和Integer同时匹配的。
证明一下,”拆箱装箱放开后,实参是int,它会把实参当作是int或Integer,然后如果用int或者Integer同时匹配到的,那么就是优先级一样“这个观点。
package com.yimeng.chongzai1;
public class MyTest {
public static void main(String[] args) {
int a = 5;
short s = 8;
// m(a,s);// 编译不通过
}
public static void m(long a,Short b) {
System.out.println("调用了m(int,Short)");
}
public static void m(Number a,Short b) {
System.out.println("调用了m(int,Double)");
}
}
上面这个例子,一开始不拆箱装箱,去匹配,就算你使用了类型自动转换和继承最近原则,你也不能找到对应的方法。所以编译器会放开拆箱装箱,用int和Integer同时匹配,第二个参数使用short和Short同时匹配。但是编译器发现,拆箱装箱后,没有找到精确匹配的,所以只能用自动转换和继承最近原则来选择了,但是编译器发现int通过类型转换匹配到了m(long a,Short b),Integer通过继承最近原则,找到了m(Number a,Short b),并且第二个参数也是匹配的,所以m(long a,Short b)和m(Number a,Short b)他们两个方法的优先级一样。所以编译不通过。
总结
这些规则可能总结的也不是很准,但是实际上,我们只要看idea的提示就可以知道某些方法是不是构成了优先级一样的情况,我们只要通过idea的ctrl+左键,就可以知道这个实参会选择哪个重载方法进行调用了。
对于我们来说只要知道有这样一个事情就行:就是一个调用方法的上下文,可能能匹配到多个符合条件的重载方法,程序会去找和实参最匹配的方法去执行的,不是最匹配的方法不会被执行。为什么可以匹配多个重载方法呢?因为方法形参中,可以进行类型自动转型,可以自动进行拆箱和装箱,还存在一个可变参数东西,这些都会使得方法能匹配的内容变得更广了。其实具体的匹配规则还是比较复杂的,但是你实战遇到的机会又很少,所以没有必要花很多时间去研究它,我们稍微了解一下就行了。