先记下两次的解决方法。
-
实现Cloneable 接口,重写clone方法。
场景:现有一个类Person[sex,age],存放在ArrayList中。要求得到一个ArrayList,存放sex=男和sex=女的年龄总和。
思路:重写Person的hashCode和equals方法,使用sex判断。遍历ArrayList l1,将ArrayList存到Set中,再转回ArrayList l2,获取去重后的性别。再遍历l2,内部遍历l1,判断sex相等的,将age加到l2中。
for(int i=0;i<l2.size();i++){
for (int j=0;i<l1.size();f++){
if(l2.i.sex=l1.j.sex){
l2.age=l2.age+l1.age;
}
}
}
问题:在改变l2中person对象的age时,l1的person对象也发生了变化。
原因:经查看HashSet的add方法,内部调用了HashMap的put方法。
private static final Object PRESENT = new Object();
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
查看HashMap的put方法
public V put(K key, V value) { if (table == EMPTY_TABLE) { inflateTable(threshold); } if (key == null) return putForNullKey(value); int hash = hash(key); int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i); return null; }
可以看到上述代码,在hashmap中找到hash相同的Entry,然后判断key相等的,对value进行交换,即hashMap后边put的会覆盖之前put的value。而对于hashSet而言,传入的value是空的Object,因此Key没有发生变化,即遍历ArrayList l1,将ArrayList存到Set中,这一步,Set中存放的sex=男 的这个对象是l1中第一条sex=男 的对象,后边的add直接跳过。而再Set转回l2后,对l2的person对象的age属性进行操作的同时,l1中的相同的person对象也发生了变化。
错误思路:使用Collections.copy(newList, oldList);创建一个新的l3代替l1,然后进行转set。结果:无效,经查看
Collections.copy创建了新的list对象,但是list中存放的person对象还是原person对象,因此无效。
正确思路:重写person的clone方法。在set转l2的时候,存入l2的存放person的克隆对象,这样修改l2的
person对象的时候不会影响到l1的person对象。
注意:默认的clone方法,其实现还是person层的浅拷贝,若person中有引用类型的属性,而对这个
引用类型的属性进行操作时,还应注意。由此引出下一条。
2.new 一个新对象,挨着set值。
不再赘述,一句话总结:浅拷贝,包含clone的默认实现或者Collections.copy,获取的对象是
新的对象,但是对象内部的引用类型属性的地址是相同的。可以重新指定新对象的引用对象的地址,但是
直接修改引用对象的属性,则原对象的引用对象的属性也会改变。