方法重载的概念
方法重载是指在一个类中定义多个同名的方法,但要求每个方法具有不同的参数的类型或参数的个数。调用重载方法时,Java编译器能通过检查调用的方法的参数类型和个数选择一个恰当的方法。方法重载通常用于创建完成一组任务相似但参数的类型或参数的个数或参数的顺序不同的方法。Java的方法重载,就是在类中可以创建多个方法,它们可以有相同的名字,但必须具有不同的参数,即或者是参数的个数不同,或者是参数的类型不同。调用方法时通过传递给它们的不同个数和类型的参数,以及传入参数的顺序来决定具体使用哪个方法。
重载的应用
在Java的日常开发中,重载随处可见,一个简单的输出语句它就有很多的方法重载。
java
public static void main(String[] args) {
System.out.println(1);
System.out.println("java");
System.out.println(3.14);
System.out.println(new ArrayList<>());
}
上面的四条输出语句都不会报错,而它的功能远不止如此,各种数据类型输出语句都能够接纳,那么这个方法为何如此强大呢?我们看看它的源码。 一个输出语句竟然有这个多的重载方法,那么它有这么多的功能也就不足为奇了。
重载引发的问题
在开发过程中,难免会产生一些异常和错误,这些问题都能够很快解决,因为开发工具会帮助我们定位问题所在。
java
public static void main(String[] args) {
System.out.println(1);
System.out.println("java");
System.out.println(3.14);
int a = 1 / 0;
System.out.println(new ArrayList<>());
}
运行结果为:
php
1
java
3.14
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.itcast_03.TestDemo.main(TestDemo.java:11)
开发工具很精准地帮助我们定位到了问题,它就是第11行的代码,int a = 1 / 0; 然而乱用方法重载引发的问题就就没有这么幸运了,因为它压根就不是异常和错误。在运行之后并不会产生任何问题,但是运行得出的结果却不是我们想要的。我们可以想象,在一个庞大的项目结构中,出现了这样的问题是多么可怕,它需要我们耗费大量的时间精力去调试程序才能找出问题。 下面举个例子来体会一下方法重载引发的问题:
java
public class TestDemo {
public static void main(String[] args) {
Collection<?> cet[] = {
new ArrayList<String>(),
new HashSet<String>(),
new HashMap<String, String>().values()
};
for (Collection<?> collection : cet) {
System.out.println(identify(collection));
}
}
public static String identify(ArrayList<String> arrayList) {
return "ArrayList";
}
public static String identify(HashSet<String> hashSet) {
return "HashSet";
}
public static String identify(Collection<?> collection) {
return "Collection";
}
}
在这段程序中,首先初始化了一个Collection类型的数组,里面存放了该类型的子类对象,然后重载了三个判断类型的方法,如果是ArrayList类型就会自动调用该类型参数的方法,然后返回 "ArrayList" 字符串;如果是HashSet类型就会自动调用该类型参数的方法,然后返回 "HashSet" 字符串。最后,我们遍历数组,调用判断方法,按理说,输出的结果应该为:
ArrayList
HashSet
Collection
但当我们运行程序之后,真正的结果为:
Collection
Collection
Collection
那么为什么会出现这样的问题呢? 重载有一个特性,就是选择调用哪个重载方法是在编译期就决定了的,而我们在循环调用的时候,关于collection变量到底指向哪个对象在编译期是无法知晓的,它既可以指向ArrayList,也可以指向HashSet,还可以指向Collection,这是Java的多态。因为要在编译期决定collection类型,所以Java认定collection为Collection类型,那么结果就可想而知了。 所以要想解决这一问题,我们就不能将判断类型的任务交给重载去完成,而是自己判断:
java
public class TestDemo {
public static void main(String[] args) {
Collection<?> cet[] = {
new ArrayList<String>(),
new HashSet<String>(),
new HashMap<String, String>().values() };
for (Collection<?> collection : cet) {
System.out.println(identify(collection));
}
}
public static String identify(Collection<?> collection) {
return collection instanceof HashSet ? "HashSet" : collection instanceof ArrayList ? "ArrayList" : "Collection";
}
}
运行结果为:
ArrayList
HashSet
Collection
重载引发的问题可远不止如此,甚至在官方的API中也会出现很多问题,我们来看一看:
java
public static void main(String[] args) {
Set<Integer> set = new HashSet<Integer>();
List<Integer> list = new ArrayList<Integer>();
for (int i = -4; i < 4; i++) {
set.add(i);
list.add(i);
}
for (int i = 0; i < 4; i++) {
set.remove(i);
list.remove(i);
}
System.out.println(set);
System.out.println(list);
}
这段程序中,首先初始化了两个集合,然后分别往两个集合中添加元素,又将集合中的0和正数去除,所以,按照我们的想法,运行结果应该为:
css
[-1, -2, -3, -4]
[-4, -3, -2, -1]
但真正的运行结果却是这样的:
css
[-1, -2, -3, -4]
[-3, -1, 1, 3]
我们首先来分析一下HashSet,关于HashSet的remove()方法,来看看源码:
java
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}
会发现,HashSet里只有一个remove()方法,所以在删除元素的时候直接通过这个方法就删除掉了,我们再看ArrayList的remove()方法:
java
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
可以看到,ArrayList实现了两个remove()方法的重载,那么在调用该方法的时候问题就来了,它到底调用的是哪个方法呢?其实它调用的是参数为int类型的方法,那么在删除的时候它就是根据索引值来删除的,所以结果为[-3, -1, 1, 3],那么要想避免这个问题,我们应该怎么做呢?其实也很简单,指明调用哪个方法即可:
java
list.remove(Integer.valueOf(i));
这样它调用的就是参数为Object类型的remove()方法,从而按照元素删除,运行结果为:
css
[-1, -2, -3, -4]
[-4, -3, -2, -1]
预防重载引发的问题
既然方法的重载会产生这么多的问题,那我们就会去想,如何预防重载引发问题呢?
1. 不使用重载
那有人就要说了,你这不是跟我扯呢,不用重载要它干嘛?确实,重载要用,但是不是什么地方都能用,只有说不得已用的时候才去用,我们看一个例子: 可以看到,对于ObjectOutPutStream的write()方法,官方并没有使用重载,而是通过不同的方法名告诉你程序具体调用的是哪个方法。 所以对于一组API,应该要做到让调用者清除地知道自己的程序将会调用哪个方法。
2. 参数的数目不要相同
对于无法避免使用重载的情况,应该设计多个参数不同的重载,避免发生不知道调用哪个方法的情况,。
3. 至少有一个参数在不同的重载方法中是不同的类型
在方法名和参数数目都相同的情况下,应该有一个或多个参数在不同的方法里是不同的类型,目的还是使程序能够清晰地调用重载方法。
4. 让不同的重载方法执行相同的逻辑
当方法名、参数数目和参数类型都相同的情况下(参数类型属于父子关系),此时可以让不同的重载方法都去执行相同的逻辑,此时所谓的重载也就子虚乌有了。 例如: 可以看到,String类的contentEquals()方法有两个重载,但我们查看源码会发现:
java
public boolean contentEquals(StringBuffer sb) {
return contentEquals((CharSequence)sb);
}
假设你传入StringBuffer类型,该方法会将其转换为CharSequence然后重新调用contentEquals()方法,所以不管你传入的是StringBuffer还是CharSequence类型,它都将调用参数为CharSequence类型的contentEquals()方法,这样就避免了由于重载引发的问题。
在看完本篇文章后,希望你对方法重载会有一个更清晰的认识,在开发过程中能够更灵活地运用。