下载地址: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源码,看到该规则后正好分析一下。
【强制】关于 hashCode和equals的处理,遵循如下规则:
1) 只要重写equals,就必须重写hashCode。
2) 因为Set存储的是不重复的对象,依据hashCode和equals进行判断,所以Set存储的对象必须重写这两个方法。
3) 如果自定义对象做为Map的键,那么必须重写hashCode和equals。
其实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?
-
【强制】不要在 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, 有效避免了下标越界问题。
本文深入探讨了Java编码规范的应用,包括String处理、hashCode与equals的正确实现、以及ArrayList的操作建议。通过具体示例,解释了如何避免常见陷阱,确保集合类如HashSet和HashMap的正确使用。
429

被折叠的 条评论
为什么被折叠?



