阿里巴巴Java开发手册:编程规约.集合处理

1.【强制】关于hashCode和equals的处理,遵循如下规则:

1) 只要重写equals,就必须重写hashCode。

2) 因为Set存储的是不重复的对象,依据hashCode和equals进行判断,所以Set存储的对象必须重写这两个方法。

3) 如果自定义对象作为Map的键,那么必须重写hashCode和equals。

说明:String重写了hashCode和equals方法,所以我们可以非常愉快地使用String对象作为key来使用。

当你把对象加入HashSet时,HashSet会先计算对象的hashcode值来判断对象加入的位置,同时也会与该位置其他已经加入的对象的hashcode值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同hashcode值的对象,会调用equals()方法来检查hashcode相等的对象是否真的相同。如果两者相同,HashSet就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。这样我们就大大减少了equals的次数,相应就大大提高了执行速度。

hashCode与equals:

如果两个对象相等,则 hashcode 一定也是相同的

两个对象有相同的hashcode值,它们不一定是相等的

两个对象相等,对两个对象分别调用equals方法都返回true

因此,equals方法被覆盖过,则hashCode方法也必须被覆盖

hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)

2.【强制】ArrayList的subList结果不可强转成ArrayList,否则会抛出ClassCastException异常,即java.util.RandomAccessSubList cannot be cast to java.util.ArrayList。

说明:subList返回的是ArrayList的内部类SubList,并不是ArrayList而是ArrayList的一个视图,对于SubList子列表的所有操作最终会反映到原列表上。该方法返回的是父list的一个视图,从fromIndex(包含),到toIndex(不包含)。fromIndex=toIndex 表示子list为空。

父子list做的非结构性修改(non-structural changes)都会影响到彼此:所谓的“非结构性修改”,是指不涉及到list的大小改变的修改。相反,结构性修改,指改变了list大小的修改。对于结构性修改,子list的所有操作都会反映到父list上。但父list的修改将会导致返回的子list失效。

3.【强制】在subList场景中,高度注意对原集合元素的增加或删除,均会导致子列表的遍历、增加、删除产生ConcurrentModificationException 异常。

说明:父list的修改将会导致返回的子list失效,产生异常

4.【强制】使用集合转数组的方法,必须使用集合的toArray(T[] array),传入的是类型完全一样的数组,大小就是list.size()。

正例:

List list = new ArrayList(2);
list.add("guan");
list.add("bao");
String[] array = new String[list.size()];
array = list.toArray(array);

反例:直接使用toArray无参方法存在问题,此方法返回值只能是Object[]类,若强转其它类型数组将出现ClassCastException错误。

说明:使用toArray带参方法,入参分配的数组空间不够大时,toArray方法内部将重新分配内存空间,并返回新数组地址;如果数组元素个数大于实际所需,下标为[ list.size()]的数组元素将被置为null,其它数组元素保持原值,因此最好将方法入参数组大小定义与集合元素个数一致。

5.【强制】使用工具类Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,它的add/remove/clear方法会抛出UnsupportedOperationException异常。

说明:asList的返回对象是一个Arrays内部类,并没有实现集合的修改方法,包括add/remove/clear。Arrays.asList体现的是适配器模式,只是转换接口,后台的具体实现仍是数组。

String[] str = new String[] { "you", "wu" }; 
List list = Arrays.asList(str); 

第一种情况:list.add(“yangguanbao”);
运行时异常。

第二种情况:str[0]=“gujin”;那么list.get(0)也会随之修改。

6.【强制】泛型通配符来接收返回的数据,此写法的泛型集合不能使用add方法,而不能使用get方法,作为接口调用赋值时易出错。

说明:扩展说一下PECS(Producer Extends Consumer Super)原则:

第一、频繁往外读取内容的,适合用


List apples = new ArrayList();
List fruits = apples; //works, apple is a subclass of Fruit.
fruits.add(new Strawberry());        //compile error

fruits是一个Fruit的子类的List,由于Apple是Fruit的子类,因此将apples赋给fruits是合法的,但是编译器会阻止将Strawberry加入fruits。因为编译器只知道fruits是Fruit的某个子类的List,但并不知道究竟是哪个子类,为了类型安全,只好阻止向其中加入任何子类。那么可不可以加入Fruit呢?很遗憾,也不可以。事实上,不能够往一个使用了? extends的数据结构里写入任何的值。

但是,由于编译器知道它总是Fruit的子类型,因此我们总可以从中读取出Fruit对象:

第二、经常往里插入的,适合用

Fruit fruit = fruits.get(0);
List fruits = new ArrayList();
List = fruits;
fruits.add(new Apple());                 //work
fruits.add(new RedApple());              //work
fruits.add(new Fruit());                 //compile error 
fruits.add(new Object());                //compile error

这里的fruits是一个Apple的超类(父类,superclass)的List。同样地,出于对类型安全的考虑,我们可以加入Apple对象或者其任何子类(如RedApple)对象,但由于编译器并不知道List的内容究竟是Apple的哪个超类,因此不允许加入特定的任何超类型。

而当我们读取的时候,编译器在不知道是什么类型的情况下只能返回Object对象,因为Object是任何Java类的最终祖先类。

具体可见:https://blog.51cto.com/flyingcat2013/1616068

7.【强制】不要在foreach循环里进行元素的remove/add操作。remove元素请使用Iterator方式,如果并发操作,需要对Iterator对象加锁。

正例:

List list = new ArrayList<>();
list.add("1");
list.add("2");
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
    String item = iterator.next();
    if (删除元素的条件) {
        iterator.remove();
    }
}

反例:

for (String item : list) {
    if ("1".equals(item)) {
        list.remove(item);
    }
}

说明:以上代码的执行结果肯定会出乎大家的意料,那么试一下把“1”换成“2”,会是同样的结果吗?

为“1”时,能够正常删除,为“2”时,报错java.util.ConcurrentModificationException

具体原因可见:https://blog.csdn.net/yangbaggio/article/details/89920938

8. 【强制】 在JDK7版本及以上,Comparator实现类要满足如下三个条件,不然Arrays.sort,Collections.sort会报IllegalArgumentException异常。

说明:三个条件如下

1) x,y的比较结果和y,x的比较结果相反。

2) x>y,y>z,则x>z。

3) x=y,则x,z比较结果和y,z比较结果相同。

这点我们需要格外注意,因为在平常写算法的过程中我们可能会用经常用到自定义比较器,在写项目代码时需要格外注意是否满足这三个条件,以免发生错误。

反例:下例中没有处理相等的情况,实际使用中可能会出现异常:

new Comparator() {
    @Override
    public int compare(Student o1, Student o2) {
        return o1.getId() > o2.getId() ? 1 : -1;
    }   
};

10. 【推荐】集合初始化时,指定集合初始值大小。

正例:initialCapacity = (需要存储的元素个数 / 负载因子) + 1。注意负载因子(即loader factor)默认为 0.75,如果暂时无法确定 初始值大小,请设置为16(即默认值)。

反例: HashMap需要放置1024个元素,由于没有设置容量初始大小,随着元素不断增加容量7次被迫扩大, resize需要重建hash表,严重影响性能。

说明:HashMap使用HashMap(int initialCapacity) 初始化。如果没有指定初始大小,HashMap默认的初始化大小为16。之后每次扩充,容量变为原来的2倍,需要进行reszie,影响性能。

11.【推荐】使用entrySet遍历Map类集合KV,而不是keySet方式进行遍历。

说明:keySet其实是遍历了2次,一次是转为Iterator对象,另一次是从hashMap中取出key所对应的value。而entrySet只是遍历了一次就把key和value都放到了entry中,效率更高。如果是JDK8,使用Map.foreach方法。

正例:values()返回的是V值集合,是一个list集合对象;keySet()返回的是K值集合,是一个Set集合对象;entrySet()返回的是K-V值组合集合。

你可能感兴趣的:(Java,java)