你是人间的四月天

第五章 泛型

  • 二三 请不要在新代码中使用原生态类型
    • 泛型信息可以在运行时被擦除
  • 二四. 消除非受验警告
  • 二五. 列表优先于数组
  • 二六. 优先考虑泛型
  • 二七. 优先考虑泛型方法
  • 二八. 利用有限制通配符来提升API的灵活性
    • PECS规则: producer-extends,consumer-super (PECS)
    • 所有的comparable 和 comparator 都是消费者
  • 二九. 优先考虑类型安全的异构容器

    第六章 枚举和注解

  • 三十. 用enum代替 int 常量

  • 三一. 用实例域代替序数

  • 三二. 用EnumSet代替位域(不看,用到了再说)

  • 三三. 用EnumMap代替序数索引(同上)

  • 三四. 用接口模拟可伸缩的枚举

    • 虽然无法编写可扩展的枚举类型,却可以通过编写接口以及实现该接口的基础枚举类型,对它进行模拟。
  • 三五. 注解优先于命名模式

  • 三六. 坚持使用Override注解

  • 三七. 用标记接口定义类型

    第七章 方法

  • 三八. 检查参数的有效性

  • 三九. 必要时进行保护性拷贝

  • 四十. 谨慎设计方法签名

    • 谨慎的选择方法的名称
    • 不要过于追求提供便利的方法
    • 避免过长的参数列表。 目标是四个参数,或者更少。
  • 四一. 慎用重载(overload)

    • 要调用哪个重载(overloading)方法是在编译时做出决定的
    • 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 "Unknow 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,Unknow Collection,然而结果却并不如意。实际打印出来的是三个Unknow Collection

    • 也就是说对于重载方法(overload)是在编译时刻决定的,对于被覆写的方法(override),是在运行时刻决定的

    • 以下是一个现在仍存在的问题(主要由ArrayList集合类的remove方法重载导致)

  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] [-2, 0, 2]
  • Set的输出结果如同我们想的一样,但是List的结果不一样。实际发生的情况是:set.remove(E),选择重载方法的参数实际上是Integer,这里进行了自动装箱,把int装箱成了Integer;对于List,有两个重载函数,这里直接重载了list.remove(i),并没有重载到list.remove(E),是从list的指定位置进行remove,所以先移除了第0个,也就是-3,list中所有元素前移;再移除第1个,也就是list中当前第2个,也就是-1;以此类推,最后得到-2,0,2。我们可以在源码中看到: 来源: http://blog.csdn.net/a921122/article/details/54834858

这里List方法调用的是 remove(int )而不是 remove(Object)查看两者的源码:

  • remove(int )

    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;  
    }

    在每次移除index位置上的元素后,会将list前移。 过程为,第一次移除0号元素-3,返回新的list-2,-1,0,1,2;第二次移除1号元素-1,返回-2,0,1,2依次往下进行,最后得到上述出乎意料的答案。
    改进方法:

  • list.remove(Integer)i);

  • list.remove(Integer.valueOf(i))

即可得到正确返回值。