1. 什么是重载
方法名称相同,但它们的参数类型或个数不同,这样,方法在被调用时编译器就可以根据参数的类型与个数的不同加以区分,这就是方法的重载。
既然可以通过参数类型或参数个数来作为重载条件,那返回值是否可以作为重载的条件呢?
代码如下:
public int returnType() { return 2; } public float returnType() { return 2f; }
这时候如果这样调用此方法:
int x = returnType()
将会调用返回值是int类型的方法,这没什么好说的,如果这样调用:
returnType(); double d = returnType();
因为上面两个方法都符合条件,那么这就让编译器无法选择了,而且,方法在调用时还可以忽略其返回值。
所以方法的返回值类型不能作为重载的标准,原因就在于其不能提供给编译器足够的信息来识别应该调用哪个方法。
2. 基本类型下重载方法的调用
在重载方法调用时,如果形参列表与实参列表对应的参数类型不相同,但形参列表的参数类型可以兼容对应的实参列表的参数类型,那么这时候方法会怎么调用呢?
我们用一些简单的代码来说明:
public class Test { public static void main(String[] args) { Test test = new Test(); byte b = 20; test.select(b); } public void select(short b) { System.out.println("调用 short方法"); } public void select(char b) { System.out.println("调用 char方法"); } public void select(int b) { System.out.println("调用 int方法"); } public void select(float b) { System.out.println("调用 float方法"); } }
因为方法定义中没有形参为byte的类型,所以除了char参数的方法外,其他几个方法的参数都可以兼容byte类型,那byte会调用哪一个方法呢?
调用 short方法
同理:
public class Test { public static void main(String[] args) { Test test = new Test(); long b = 20L; test.select(b); } public void select(int b) { System.out.println("调用 int方法"); } public void select(float b) { System.out.println("调用 float方法"); } public void select(double b) { System.out.println("调用 double方法"); } }
结果为:
调用 float方法
大家可以使用这几种基本数值类型多试试看。
总结:在方法调用时,如果实参的类型与方法中形参的类型不相同,那么系统会调用形参类型可以兼容的实参类型,并且形参类型与实参类型最接近的方法。而基本数据类型顺序由低到高为:
所以,加入方法调用时实参的类型为char,而要调用的方法中不存在形参为char类型的,而分别为byte,short,int,long,float,double时,byte与short不能兼容char类型,不考虑。剩下的4个方法中int与char的类型最为接近,此时就会调用形参类型为int的方法。
3. 引用类型下重载方法的调用
我们用一个简单的例子来实验:
public class Test { public static void main(String[] args) { Test test = new Test(); Test3 test3 = new Test3(); test.select(test3); } public void select(Test1 test1) { System.out.println("调用 Test1方法"); } public void select(Test2 test2) { System.out.println("调用 Test2方法"); } public void select(Object obj) { System.out.println("调用 Object方法"); } } class Test1 { } class Test2 extends Test1 { } class Test3 extends Test2 { }
结果为:
调用 Test2方法
如果我们将参数为Test2的给去掉,则结果为:
调用 Test1方法
如果我们将参数为Test1的也给去掉,结果为:
调用 Object方法
由此可知,引用类型与基本类型的选择方式也是类似的,都是选择与实参类型最接近的。
总结:其实,不管是基本类型还是引用类型,重载方法调用时,在保证兼容性的情况下,都是会调用与实参类型最接近的方法。
4. 包装类与可变参数类型的重载方法调用
public class Test { public static void main(String[] args) { Test test = new Test(); test.select(5); } public void select(float f) { System.out.println("调用 float方法"); } public void select(double d) { System.out.println("调用 double方法"); } public void select(Integer i) { System.out.println("调用 Integer方法"); } public void select(Number number) { System.out.println("调用 Number方法"); } public void select(int... i) { System.out.println("调用 int... 方法"); } }
这种情况下,打印结果为:
调用 float方法
将float和double参数的两个方法注释掉,打印结果为:
调用 Integer方法
将前四个方法都注释掉,打印结果为:
调用 int... 方法
或许从结果中,大家也明白了包装类与可变参数类型的重载方法调用了吧;
总结:就 test.select(5)方法调用 来说:
包装类与可变参数类型的重载调用顺序,可以简记如下:
参数完全相同的方法(int) -> 形参兼容实参的方法(float -> double) -> 自动拆箱与封箱操作的方法(Integer) -> 自动拆箱与封箱的父类(Number -> Object) -> 可变参数类型的方法(int...)
5. 重载无法实现多态
看下面一段代码,并预测一下打印结果:
public class Test { public static void main(String[] args) { Test test = new Test(); Object obj = new Animal(); Animal animal = new Dog(); test.select(obj); test.select(animal); } public void select(Object number) { System.out.println("调用 Object方法"); } public void select(Animal animal) { System.out.println("调用 Animal 方法"); } public void select(Dog animal) { System.out.println("调用 Dog 方法"); } } class Animal { } class Dog extends Animal { }
打印结果为:
调用 Object方法
调用 Animal 方法
因为重载方法的调用是在编译时确定的,也可以说是“静态绑定”,如果实参的类型是引用类型,编译器会根据引用的类型来决定调用哪个重载方法。
这种方法调用不同于多态,多态方法的调用是根据运行时对象的实际类型来决定的,也称为“动态绑定”,重载方法的选择只是根据引用的类型来决定的,而与该引用所指向的对象类型无关,因为引用所指向的对象类型是在运行时才确定的,因此,使用重载无法实现多态。
6. 泛型方法的重载
对于泛型类的参数,因为编译器在编译期间会进行类型擦除,所以和普通的重载方法稍稍有些区别。
看以下例子:
public class JavaTest { public static void main(String[] args) { JavaTest test = new JavaTest(); Test2 test2 = new Test2(); test.print(test2); } public <T extends Test2> void print(T t) { System.out.println("<T extends Test2>方法"); } public <T> void print(T t) { System.out.println("<T>方法"); } public void print(Test1 test1) { System.out.println("Test1方法"); } } class Test1 { } class Test2 extends Test1 { }
打印结果为:
<T extends Test2>方法
有关类型擦除就说两点,其他的直接网上搜索就行:
- 将所有的泛型参数用其最左边界(最顶级的父类型)类型替换。
- 移除所有的类型参数。
如对于
public <T extends Test2> void print(T t) { System.out.println("<T extends Test2>方法"); } public <T> void print(T t) { System.out.println("<T>方法"); }
擦除之后的方法声明为:
public void print(Test2 t) {} public void print(Object t) {}
然后调用规则和普通的重载方法是相同的。
参考自:《细说Java》