1 写在开头
Java 对方法重载(Overloading)的定义:如果有两个方法的方法名相同,但参数不一致,那么可以说一个方法是另一个方法的重载。具体说明如下:
方法名相同
方法的参数类型,参数个不一样
方法的返回类型可以不相同
方法的修饰符可以不相同
main 方法也可以被重载
2 集合方法的重载
我们看个例子 1,根据一个集合的实现类型对其进行分类:
/**
* <p>
* overloaded,需要调用哪个重载方法是在编译时做出的觉得。对于三个重载方法,他们的参数编译时类型(类型擦除)都是一样的:Collection<?> s
* 重载(overloaded method)区别于覆盖方法(overridden method):
* 重载方法选择是静态的,覆盖方法选择是动态的。(被覆盖的方法是在运行时决定,依据是被调用方法所在对象的运行时类型)
* </p>
*/
public class CollectionClassifier {
//重载1
public static String classify(Set<?> s) {
return "set";
}
//重载2
public static String classify(List<?> s) {
return "list";
}
//重载3
public static String classify(Collection<?> s) {
return "Unknown Collection";
}
public static void main(String[] args){
Collection<?>[] collections = {
new HashSet<String>(),
new ArrayList<String>(),
new HashMap<String, String>().values(),
};
for (Collection<?> c : collections) {
System.out.println(classify(c));
}
}
}
复制代码
输出结果:
Unknown Collection
Unknown Collection
Unknown Collection
复制代码
代码分析:
重载方法选择是静态的,要调用哪个重载方法是在编译时做出的决定。
我们反编译下.java 文件,可以看到编译后的.class 文件内容
JD-GUI 打开 CollectionClassifier.class:
对于三个重载方法,他们的参数编译时类型(类型擦除)都是一样的:Collection<?>,因此,最终执行的重载方法是第三个:
//重载3
public static String classify(Collection<?> s) {
return "Unknown Collection";
}
复制代码
3 方法重载与方法覆盖的区别
通过例子 2,可以比较出方法覆盖与方法重载的区别:
public class Overriding {
public static void main(String[] args) {
Wine[] wines = {new Wine(), new SparklingWine(), new Champagne()};
for (Wine wine : wines) {
System.out.println(wine.name());
}
}
}
class Wine{
String name(){return "wine";}
}
class SparklingWine extends Wine{
@Override
String name() {
return "SparklingWine";
}
}
class Champagne extends SparklingWine{
@Override
String name() {
return "Champagne";
}
}
复制代码
输出结果:
wine
SparklingWine
Champagne
复制代码
结论:
重载方法选择是静态的,覆盖方法选择是动态的(被覆盖的方法是在运行时决定,依据是被调用方法所在对象的运行时类型)。
这里说明下,当一个子类包含的方法声明与祖先类中的方法声明具有相同的签名时,方法就被覆盖。如果实例方法在子类中被覆盖了,而且这个方法是在子类的实例被调用的,那么子类的覆盖方法将被执行,不管该子类实例的编译类型是什么。
得出结论:与方法重载相比,对象运行时类型并不影响“哪个重载方法版本将被执行”;选择方法是在编译时进行,完全基于参数的编译时类型。
4 覆盖机制 &重载机制的区别
覆盖进制是规范,而重载机制是例外。所以,覆盖机制满足人们对于方法调用方法行为的期望。如果编写出来的代码行为可能使得程序员困惑,那就是糟糕的实现。
5 自动装箱 &泛型 影响了重载机制
所有的基本类型都根本不同于所有的引用类型,但是当自动装箱出现之后,就不再如此了,它会导致真正的麻烦。见下面的例子 3:
public class SetList {
public static void main(String[] args) {
TreeSet<Integer> set = new TreeSet<>();
ArrayList<Integer> list = new ArrayList<>();
for (int i = -3; i < 3; i++){
set.add(i);
list.add(i);
}
System.out.println(set);
System.out.println(list);
for (int i = 0; i<3; i++){
set.remove(i); //i:object to be removed from this set, if present
list.remove(i); // i:the index of the element to be removed,每次操作,底层数组的元素下标都会变化
}
System.out.println(set + " " + list);
}
}
复制代码
输出结果:
[-3, -2, -1, 0, 1, 2]
[-3, -2, -1, 0, 1, 2]
[-3, -2, -1] [-2, 0, 2]
复制代码
实际情况是:set.remove(i) 选择重载方法 remove(E),这里 E 是集合的元素类型(Integer),将 i 自动装箱到 Integer 中;list.remove(i)调用选择重载方法 remove(i),从指定位置去除元素。
在 Java 1.5 发行版中被泛型化前,List 接口有一个 remove(Object) 而不是 remove(E),参数类型是:Object 和 int
有了自动装箱和泛型之后,这两种参数类型就不再根本不同了。即是说,Java 语言添加了泛型和自动装箱破坏了 List 接口。
幸运的是,Java 类库几乎没有 API 受到同样的破坏。但也说明了,我们需要谨慎重载显得更加重要了。
6 总结
“能够重载方法”并不意味着就“应该重载方法”。一般情况下,多个具有相同参数数目的方法来说,应该尽量避免重载方法。
至少避免这种情形:同一组参数只需经过类型转换就可以被传递给不同的重载方法。
如果不能避免这种情形,例如为正在改造的一个现有的类以实现新的接口,就应该保证:当传递同样的参数时,所有重载方法的行为必须一致。如果不能做到这一点,程序员就难有效使用被重载的方法或者构造器,因为不能理解它为什么不能正常的工作。
评论