谨慎使用方法重载和可变参数
立即解锁
发布时间: 2025-08-18 00:25:50 阅读量: 2 订阅数: 4 

# 谨慎使用方法重载和可变参数
## 1. 谨慎使用方法重载
### 1.1 方法重载的问题示例
先来看一个尝试根据集合类型(集合、列表或其他类型)对集合进行分类的程序:
```java
// 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));
}
}
```
你可能期望这个程序依次输出 `Set`、`List` 和 `Unknown Collection`,但实际上它三次都输出 `Unknown Collection`。这是因为 `classify` 方法是重载的,调用哪个重载方法是在编译时决定的。在循环的三次迭代中,参数的编译时类型都是 `Collection<?>`,虽然每次迭代的运行时类型不同,但这并不影响重载方法的选择。由于参数的编译时类型是 `Collection<?>`,所以每次迭代都会调用 `classify(Collection<?>)` 这个重载方法。
### 1.2 重载与重写的区别
方法重载和重写是不同的机制。方法重写时,正确的方法版本是在运行时根据调用方法的对象的运行时类型来选择的。例如:
```java
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) {
Wine[] wines = {
new Wine(), new SparklingWine(), new Champagne()
};
for (Wine wine : wines)
System.out.println(wine.name());
}
}
```
在这个程序中,`name` 方法在 `Wine` 类中声明,并在 `SparklingWine` 和 `Champagne` 类中被重写。尽管在循环的每次迭代中,实例的编译时类型都是 `Wine`,但程序会依次输出 `wine`、`sparkling wine` 和 `champagne`。这表明,当调用重写方法时,对象的编译时类型对执行哪个方法没有影响,“最具体”的重写方法总是会被执行。而方法重载则是在编译时根据参数的编译时类型来选择重载方法,对象的运行时类型对重载方法的选择没有影响。
### 1.3 解决方法
在 `CollectionClassifier` 示例中,要想根据参数的运行时类型自动调度到合适的方法重载来识别参数类型,方法重载无法实现这个功能。如果需要一个静态方法,最好的解决办法是用一个进行显式 `instanceof` 检查的单一方法来替代 `classify` 的三个重载方法:
```java
public static String classify(Collection<?> c) {
return c instanceof Set
? "Set" :
c instanceof List ? "List" : "Unknown Collection";
}
```
### 1.4 避免混淆的重载使用
由于重写是常态,重载是例外,重写会让人们对方法调用的行为产生预期。而方法重载很容易混淆这些预期,编写可能会让程序员困惑的代码是不好的做法,对于 API 来说更是如此。如果 API 的普通用户不知道对于给定的一组参数会调用哪个方法重载,使用该 API 就可能会导致错误,这些错误可能会在运行时表现为不稳定的行为,而且很多程序员可能无法诊断这些错误。因此,应该避免使用容易引起混淆的方法重载。
一个安全、保守的策略是永远不要导出具有相同参数数量的两个重载方法。如果一个方法使用了可变参数,保守的策略是除特殊情况外,根本不要对其进行重载。如果遵循这些限制,程序员就不会对任何一组实际参数适用哪个重载方法产生疑问。这些限制并不是很苛刻,因为你总是可以给方法取不同的名字,而不是进行重载。例如,`ObjectOutputStream` 类为每个基本类型和几个引用类型都有一个 `write` 方法的变体,这些变体的签名如 `writeBoolean(boolean)`、`writeInt(int)` 和 `writeLong(long)`,而不是对 `write` 方法进行重载。与重载相比,这种命名模式的一个额外好处是可以提供具有相应名称的 `read` 方法,如 `readBoolean()`、`readInt()` 和 `readLong()`。
### 1.5 构造函数的重载
对于构造函数,你不能选择使用不同的名字,因为一个类的多个构造函数总是重载的。在很多情况下,你可以选择导出静态工厂方法而不是构造函数。而且,对于构造函数,你不必担心重载和重写之间的相互作用,因为构造函数不能被重写。不过,你可能会有导出具有相同参数数量的多个构造函数的情况,所以了解如何安全地进行构造函数重载是有必要的。
### 1.6 不会引起混淆的重载情况
如果对于任何一组实际参数,总是很清楚哪个重载方法会适用,那么导出具有相同参数数量的多个重载方法不太可能会让程序员感到困惑。当每对重载方法中至少有一个对应的形式参数在两个重载方法中的类型“截然不同”时,就属于这种情况。如果显然不可能将一个类型的实例转换为另一个类型,那么这两个类型就是截然不同的。在这种情况下,对于给定的一组实际参数,哪个重载方法适用完全由参数的运行时类型决定,而不受其编译时类型的影响,这样就消除了主要的混淆来源。例如,`ArrayList` 有一个接受 `int` 类型参数的构造函数和一个接受 `Collection` 类型参数的构造函数,很难想象在任何情况下调用这两个构造函数会产生混淆。
### 1.7 自动装箱带来的问题
在 Java 1.5 版本之前,所有基本类型与所有引用类型都是截然不同的,但引入自动装箱后情况就不同了,这会导致一些实际问题。例如:
```java
public class SetList {
public static void main(String[] args) {
Set<Integer> set = new TreeSet<Integer>();
List<Integer> list = new ArrayList<Integer>();
for (int i = -3; i < 3; i++) {
set.add(i);
list.add(i);
}
for (int i = 0; i < 3; i++) {
set.remove(i);
list.remove(i);
}
System.out.println(set + " " + list);
}
}
```
这个程序将 -3 到 2 的整数添加到一个有序集合和一个列表中,然后对集合和列表都进行三次相同的 `remove` 调用。你可能期望程序从集合和列表中移除非负值(0、1 和 2),并输出 `[-3, -2, -1] [-3, -2, -1]`,但实际上程序从集合中移除了非负值,从列表中移除了奇数,并输出 `[-3, -2, -1] [-2, 0, 2]`,这种行为非常令人困惑。
原因是 `set.remove(i)` 调用选择了 `remove(E)` 这个重载方法,其中 `E` 是集合的元素类型(`Integer`),并将 `i` 从 `int` 自动装箱为 `Integer`,这符合预期,所以程序最终从集合中移除了正值。而 `list.remove(i)` 调用选择了 `remove(int i)` 这个重载方法,它会从列表中移除指定位置的元素。如果从列表 `[-3, -2, -1, 0, 1, 2]` 开始,依次移除第 0 个、第 1 个和第 2 个元素,最终会得到 `[-2, 0, 2]`。要解决这个问题,可以将 `list.remove` 的参数强制转换为 `Integer`,以强制选择正确的重载方法,或者调用 `Integer.valueOf(i)` 并将结果传递给 `list.remove`。
```java
for (int i = 0; i < 3; i++) {
set.remove(i);
list.remove((Integer) i);
// or remove(Integer.valueOf
```
0
0
复制全文
相关推荐









