重载是实现多态非常重要的手段之一,但是在使用重载时,往往也容易出现预料之外的结果,因此在使用重载,尤其是方法的重载时要尤其的谨慎。
使用方法重载可能带来的问题
- 方法的重载和继承时的覆盖不同
继承时的覆盖机制是:选择被覆盖的方法的正确版本是在运行时进行的,选择的依据是被调用方法所在对象的运行时类型。
因此
class Wine {
String name() { return "wine"; }
}
class SparklingWine extends Wine {
@Override
String name() { return "sparkling wine"; }
}
class Champagne extends SparklingWine {
@Override
String name() { return "champagne"; }
}
public class Overriding {
public static void main(String[] args) {
List<Wine> wineList = List.of(new Wine(), new SparklingWine(), new Champagne());
for (Wine wine : wineList)
System.out.println(wine.name());
}
}
的输出是:“wine,sparking wine 和 champagne”
二方法的重载机制则,要调用哪个方法是在编译时做出决定的。如下面这段代码
// Broken! - What does this program print?
public class CollectionClassifier {
public static String classify(Set<?> s) {
return "Set";
}
public static String classify(List<?> lst) {
return "List";
}
public static String classify(Collection<?> c) {
return "Unknown Collection";
}
public static void main(String[] args) {
Collection<?>[] collections = {
new HashSet<String>(),
new ArrayList<BigInteger>(),
new HashMap<String, String>().values()
};
for (Collection<?> c : collections)
System.out.println(classify(c));
}
}
最后只会输出三次“Unknown Collection”。因为参数编译时的类型都是Collection<?>.
Java5 引入自动装箱之后,重载尤其尤其需要谨慎
如List 的remove(Integer e) 和remove(int i) 就是两个完全不同的方法。前者是移除列表中的指定对象,后者是移除列表中指定位置的对象。lambda 和方法引用也可能引起方法重载的混乱
因为lambda 和方法引用时的函数式方法本身也可能被重载,其返回结果可能会超出我们的预料,与重载搭配使用可能出现混乱。如下面这段代码:
new Thread(System.out::println).start();
ExecutorService exec = Executors.newCachedThreadPool();
exec.submit(System.out::println);
submit 方式其实带有一个Callable<T>的重载,尽管我们认为println 方法没有返回值,可是万一println也被重载了,那么结果就会超出我们的预料了。
解决方法
由此可见,使用方法重载很可能会出现预料之外的结果,因此,非必要情况下尽量不要使用方法的重载。可以避开的方式有:
- 对于相同名称的方法,使用不同数量的入参
- 入参相同时,更改方法名称
- 是在无法更改方法名称的场景(构造器),如果遇到入参数量相同时,考虑使用静态工厂模式初始化类
思考
非必要不重载。因为重载会降低代码的可读性,让程序员无法在编写和阅读代码时就能快速准确的判断出到底调用的是哪个方法。由此可能产生错误。