本章节一开始就给了一个例子,泛型+数组类型的,
public class CollectionClassifier {
public static String classify(Collection<?> c) {
return "UnknownCollection";
}
public static String classify(List<?> lst) {
return "List";
}
public static String classify(Set<?> s) {
return "Set";
}
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));
}
}
我们可以看出,classify()方法有三个,这叫做重载。这个例子很明显, collections 数组里有三个不同类型的集合,我们想通过不同集合类型,打印相对应的值,通过for循环,一次打印 set list和 map 对应的字段,但很可以,打印出来的值都是"UnknownCollection", 这说明 for 循环中的三次方法调用,都调用了 classify(Collection<?> c) 方法,这是为什么呢?重载时,调用哪个函数,是在编译时期决定的,之前提到过,泛型是编译时擦除痕迹,尤其是和数组搭配时,很容易出问题,在这个地方,由于泛型的关系,所以数组里面能识别的都按照 Collection 类型了,这也是为什么打印了 三个 "UnknownCollection" 值。对于这种重载,要慎重。如果要使用,最好重载时,确保参数的个数不一致,不要用相同的参数个数,仅仅是改变了参数的类型,上面的例子算是个比较典型的反例。
说到了重载,还有一个叫做重写。就是一个父类有个方法,他的子类也有个相同的方法,参数和方法名都一样,子类重新实现了这个功能,这就叫做重写。书中也举了个例子,
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());
}
}
这个程序打印出来的结果是 "wine" "sparkling wine" "champagne",符合我们的预期。这是为什么呢,因为这些方法都是在class类内部,编译时都是 Wine 对象,但是这里涉及到了多态,还是会找到自己的具体的类型,所以找到的都是真实类型的对象,而非是父类,编译的类型不会有影响,因此打印出了正确的log。
重载和重写的概念不太一样,对于上面重载的反例,如果我们想加以识别,可以用 instanceof 来识别具体的类型,来做判断
public static String classify(Collection<?> c) {
return c instanceof Set ? "Set" : c instanceof List ? "List" : "Unknown Collection";
}
这样,根据 instanceof 关键字,就能识别了。重载和重写一般是被一块提起的,对于重载,我们一般慎用,或者通过不同参数个数来控制,记住上面重载的反例,下面还有个例子
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,-1,0,1,2 六个数, 我们希望是依次删除0,1,2,留下3,-2,-1,但打印结果是 [-3, -2, -1] [-2, 0, 2] 。set 删除的结果符合预期,list则不一样,为什么呢?看源代码,set实际是 TreeSet, 看它的remove()方法, TreeSet 实际上是有个 TreeMap 来替它保存数据,
public boolean remove(Object o) {
return m.remove(o)==PRESENT;
}
这里的 m 其实就是一个 TreeMap,因为上面的new TreeSet<Integer>() 调用的是无参构造,里面使用的是默认的 TreeMap,此时,调用的是 TreeMap 的remove()方法
public V remove(Object key) {
TreeMapEntry<K,V> p = getEntry(key);
if (p == null)
return null;
V oldValue = p.value;
deleteEntry(p);
return oldValue;
}
final TreeMapEntry<K,V> getEntry(Object key) {
// Offload comparator-based version for sake of performance
if (comparator != null)
return getEntryUsingComparator(key);
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
TreeMapEntry<K,V> p = root;
while (p != null) {
int cmp = k.compareTo(p.key);
if (cmp < 0)
p = p.left;
else if (cmp > 0)
p = p.right;
else
return p;
}
return null;
}
看到这里就明白了,set 删除时,把 int 值穿进去,然后进行了自动装箱,把int变为了Integer,然后重载了 remove()方法,此时,Set 里面装的对象也是装箱类型的,所以就正确的执行了我们希望的逻辑,再看看List 为什么出错,在这里,List 对应的是 ArrayList ,看看它的源码,
public E remove(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
modCount++;
E oldValue = (E) 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;
}
看到这,发现里面有两个 remove()方法,我们知道,int类型放入集合时,实际上是自动装箱的,也就是说ArrayList和TreeSet中都是以Integer的格式存在的,我们再看看这两个重载的方法,remove(int index) 是根据索引删除的,此时,传进去的 int 值是该数组的索引;remove(Object o) 这个方法才是删除对象的,此时穿进去如果是 Integer的话,会调用这个方法,此时根据对象的
值来找到相应的索引,然后根据索引删除数据。上面list删除时,调用的是 remove(int index) 方法,传进去的int值没有自动装箱,所以原始数据是 [-3,-2,-1,0,1,2], 删除 remove(0),把索引为0的删除了,也就是删除了-3,所以数组变为 [-2,-1,0,1,2], 然后 remove(1),把索引为1的元素删除了,也就是 -1, 所以变为了 [-2,0,1,2], 接着remove(2),把索引为2的删除了,也就是删
除了1,所以剩余数组是 [-2, 0, 2],与上述一致。 所以,这个具体是怎么删除,还是要看源代码的,通俗来说,是重载使用的不对,容易造成误判。如果想要list也删除对象,可以对int进行装箱,如下
for (int i = 0; i < 3; i++) {
set.remove(i);
list.remove((Integer)i);
}
remove()时,把int包装为 Integer ,这样,就执行 remove(Object o) 方法,就可以了。为了以防万一,慎用重载;使用重载,切记要参数个数不同,不要写出方法个数相同的重载。