一. 方法重载
方法重载:当同一个类中有两个或两个以上相同命名的方法。
当方法重载时,必须要保证每两个方法之前的方法签名(Signature)是不同的。这意味着这两个方法有着不同数量的参数或者参数的类型不同,也可都不同。
方法签名:方法签名包括一个方法的名字以及参数列表。
我们来看下面这一段代码:
//return the max of two int values
public static int max(int num1, int num2) {
System.out.println("Method1 invoked.");
if (num1 > num2)
return num1;
else
return num2;
}
//return the max of two double values
public static double max(double num1, double num2) {
System.out.println("Method2 invoked.");
if (num1 > num2)
return num1;
else
return num2;
}
// return the max of three double values
public static double max(int num1, int num2, double num3) {
System.out.println("Method3 invoked.");
return max(max(num1, num2), num3);
}
第一个方法的方法签名就是 max(int, int),同理第二个方法的签名就是 max(double, double) 。
在这一段代码中的三个方法就在同一个类中,相同的命名,不同的方法签名,那么这就是所谓的方法重载了。那我们为什么要运用到方法重载呢?因为在不同情境中我们需要运用不同类型的参数,虽然这可以用泛类实现,但是有时候还需要不同数量的参数。最常见的方法重载的运用就是构造函数(Constructor)。
方法一求的是最大整数,方法二求的是最大双精度数,方法三是求三个双精度数中的最大值,这三个方法都有相同的命名max。
public static void main(String[] args) {
System.out.println(max(3, 4));
System.out.println(max(3.0, 5.5));
System.out.println(max(3, 5, 10.88));
}
当我们运行这个程序的时候,结果如下:
Method1 invoked.
4
Method2 invoked.
5.5
Method3 invoked.
Method1 invoked.
Method2 invoked.
10.88
前两个方法都一目了然,但第三个方法为什么分别调用了方法1和方法2呢。因为调用方法3时,传入的参数列表中前两个参数为int整数型。Java编译器搜寻方法max中最精确匹配的方法,因为方法1max(int, int) 比方法2max(double, double) 更精确,所以方法1被调用。之后返回数字5与10.88调用方法,此时我们发现没有一个方法是和此时的参数类型(int, double)完全相符的,但是为什么方法2成功被调用了呢。
这是因为Java的自动转换类型机制(Automatic Type Conversion),当Java找不到一个完全相符的方法时,会自动尝试类型转换。根据下图可以转换的方向,所以方法2被调用。
这种自动转换看起来很方便,但有时候也会引起问题。
歧义调用:当调用一个方法,有两个或两个以上的匹配时,Java编译器无法判断哪个是最精确的匹配。歧义调用会产生一个编译错误。
我们来看这样一段代码:
public static double max(int num1, double num2) {
if (num1 > num2)
return num1;
else
return num2;
}
public static double max(double num1, int num2) {
if (num1 > num2)
return num1;
else
return num2;
}
当我们尝试调用方法,比如说max(int, int), Java就无法判断哪一个方法更精确,从而导致了一个错误。通常IDE会提示错误并提示修改。
在方法重载的时候还需要注意:
-
返回类型不可以重载。方法签名并不包括返回类型,所以改变返回类型并不是方法重载。且Java不允许在同一个类中有两个相同方法签名不同返回类型的方法。
这是错误的:
public class Sample { public int computeSth(int n) { . . } public double computeSth(int n) { . . } }
-
运算符号不可以重载。虽然其他语音,像C++允许你重载符号(+, -, etc.), Java并不允许。
二. 方法重写
方法重写: 在子类中重新定义从父类中继承到的方法。
在子类中重写的方法需要使用和父类一样的方法签名以及返回类型。
通常来说重写的方法是不能改变返回类型的,而且也不能改变他是void还是可以返回值的。但是,在Java 5.0之后的版本中,如果返回的类型是一个类A,那么在重写的方法中返回的类型可以是所有继承了A类的子类的类型,这被称为协变返回类型(Covariant Return Type)。
这是被允许的:
public class BaseClass {
...
public Employee getS(int money)
...
}
public class DerivedClass extends BaseClass {
...
public partTimeEmployee getS(int money)
...
}
在重写方法的时候还需要注意的是访问权限的问题。在子类中重写的方法只能将他从低权限改变到高权限(private --> public)而并不能将他从高权限改变到低权限(publc --> private)。
如果不想父类中的方法被重写,那我可以在方法的前面加上final修饰符,这样该方法就不能在子类中被重新定义。如果在定义类的时候加上final修饰符,那么这个类将不能被继承。
请注意,静态方法(static)可以被继承,但不能被重写。如果在子类中静态方法被重新定义的话,那么父类中的该静态方法将会被隐藏,但仍可通过类名.方法名(SuperClassName.staticMethodName)的方式被调用。
三. 区别
方法重载时使用同样的名字不同的签名定义多个方法;重写是在子类中对一个方法进行新的实现(相同签名以及相同返回类型)。
请思考以下两段代码的输出是什么。
重写的例子:
public class TestOverriding {
public static void main(String[] args) {
A a = new A();
a.p(10);
a.p(10.0);
}
}
class B {
public void p(double i) {
System.out.println(i * 2);
}
}
class A extends B {
// overrides the method in B
@Override
public void p(double i) {
System.out.println(i);
}
}
重载的例子:
public class TestOverloading {
public static void main(String[] args) {
C c = new C();
c.p(10);
c.p(10.0);
}
}
class D {
public void p(double i) {
System.out.println(i * 2);
}
}
class C extends D {
// overloads the method in D
public void p(int i) {
System.out.println(i);
}
}
第一段段代码中main函数里的两行均调用了类A中的方法p(double i),所以输出为 10.0 和 10.0. 因为类A中的方法p重写了父类B中的方法p。
第二段段代码中main函数里的 c.p(10) 调用了类C中的方法p,c.p(10.0)调用了类D中的方法p。所以输出结果为 10 和 20.0。
如果做错的话就再回头看看方法重写和重载的定义。
还需注意的是,方法重写一定发生在继承相关的不同类中,但是方法重载可以发生在同一个类中也可以发生在继承关系的不同类中。在通用的IDE中都可以加入@override重写标记去提醒IDE和自己该方法被重写了。