阿里巴巴Java开发手册v1.2.0读后感

本文深入探讨了Java编码规范的应用,包括String处理、hashCode与equals的正确实现、以及ArrayList的操作建议。通过具体示例,解释了如何避免常见陷阱,确保集合类如HashSet和HashMap的正确使用。

        下载地址:http://download.youkuaiyun.com/detail/brycegao321/9888683


        读完该pdf写下感受, 感觉自己的编码习惯大都跟规范相符合, 暗爽一下大笑, 大概也得益于看了众多开源框架吧。

        有几处知识盲区,我调试了一下加深了对Java的理解。

1、 String的split方法是用正则表达式实现的, 返回值是Pattern.compile(regex).split(this, limit)。

        String str = "a,b,c,,";
        String[] ary = str.split(",");   //同String[] ary = str.split(",", 0);
        System.out.println(ary.length);
   输出3 惊讶   


2、 以前对hashCode和equals没有足够的理解, 看到下面规则后就在想HashSet是怎么保证数据唯一的? HashMap怎么保证只有一个Key的(参见HashMap源码分析)?  以前确实没仔细看过HashSet源码,看到该规则后正好分析一下。

【强制】关于 hashCodeequals的处理,遵循如下规则:

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

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

   3) 如果自定义对象做为Map的键,那么必须重写hashCodeequals

  

    其实HashSet里有个变量map,数据类型是HashMap。这下就明白了吧, 在HashSet里添加/删除的对象其实就是HashMap的key, 而value是都是同一个Object对象!!!

public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable
{
    static final long serialVersionUID = -5024744406713321676L;

    private transient HashMap<E,Object> map;  

    // Dummy value to associate with an Object in the backing Map
    private static final Object PRESENT = new Object();
......
}
      向HashSet添加对象实际上就是向HashMap添加对象。

   public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }
     

      看下面示例代码, 自定义类Car并重写hashCode和equals方法。 PS:如果不重写,那么set的大小是8,不会判重。

    static class Car {
        String band;
        int price;

        private int hash; // Default to 0

        Car(String band, int price) {
            this.band = band;
            this.price = price;
        }

        @Override
        public boolean equals(Object anObject) {
            if (this == anObject) {
                return true;
            }
            if (anObject instanceof Car) {
                Car another = (Car) anObject;
                if ((this.band == another.band || this.band.equalsIgnoreCase(another.band))   //考虑band参数可能为空
                        && this.price == another.price) {
                    return true;
                }
            }
            return false;
        }

        //参考String类的hashCode方法,  哈希值算法直接影响寻址效率,具体可参考HashMap源码
        @Override
        public int hashCode() {
            int h = hash;
            if (h == 0) {
                if (band != null && band.length() > 0) {  //考虑band参数可能为空
                    for (int i = 0; i < band.length(); i++) {
                        h = 31 * h + band.charAt(i);
                    }
                    hash = h;
                } else {
                    hash = price;
                    h = price;
                }
            }
            return h;
        }

    }
        Set<Car> sets = new HashSet<>();
        sets.add(new Car("BYD", 1));      //第一行
        sets.add(new Car("Auti", 2));     //第二行
        sets.add(new Car("Jetta", 3));    //第三行
        sets.add(new Car(null, 4));       //第四行
        sets.add(new Car("BYD", 1));      //第五行
        sets.add(new Car("Auti", 2222));  //第六行
        sets.add(new Car("Jet", 3));      //第七行
        sets.add(new Car(null, 4));       //第八行

        System.out.println("Set大小:" + sets.size());   //大小是6


     问题:  如果2个对象出现哈希碰撞而且equals方法为真, 向HashSet插入第2个对象时会怎样?

     答: HashMap认为key相同, 只会更新value。 所以HashMap里只有第一个对象, 没有第二个对象!

实例验证一下上述说法,修改Car类的equals和hashCode方法, 再次运行sets的大小是1, 而且是第一行的对象。

    static class Car {
        String band;
        int price;

        private int hash; // Default to 0

        Car(String band, int price) {
            this.band = band;
            this.price = price;
        }

        @Override
        public boolean equals(Object anObject) {
            return true;
        }

        //参考String类的hashCode方法,  哈希值算法直接影响寻址效率,具体可参考HashMap源码
        @Override
        public int hashCode() {
            int h = hash;
            if (h == 0) {
                hash = 1;
                h = 1;
            }
            return h;
        }

    }




3、 为什么不推荐使用for循环删除或添加ArrayList的对象? 而要用迭代器iterator?

  1. 【强制】不要在 foreach循环里进行元素的remove/add操作。remove元素请使用Iterator

    方式,如果并发操作,需要对 Iterator对象加锁。

    正例:

    Iterator<String> it = a.iterator();while (it.hasNext()) {

    String temp = it.next();if (删除元素的条件) {

                 it.remove();
              }
    

    transient Object[] elementData;

    /**
     * The size of the ArrayList (the number of elements it contains).
     *
     * @serial
     */
    private int size;
          ArrayList其实就是个可变数组,remove方式就是使用内存拷贝方法arrayCopy把后面的对象前移,并且执行--size即数组大小要减1,最后一个对象赋空(由GC回收内存)。 
    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;
    }


       如果使用for循环删除ArrayList的多个对象, 那么要考虑下标越界问题。 如果明确知道ArrayList里只有一个要删除的对象, 那么使用for循环是没问题的, 执行完remove方法后调用break跳出循环就可以了。

        

        为什么阿里的手册中推荐使用Iterator呢? 看源码, 主要是limit可以减1, 有效避免了下标越界问题。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值