今天公司的L&L,同事介绍了Java的Reflection机制。最后提到了覆盖object对象的hashCode方法可能会导致内存泄漏,这里结合网上的其他资料来简单总结一下。
首先,假设我们定义了一个ReflectPoint类,定义两个成员变量记录点的x和y坐标。object类的equals方法用来比较两个对象的内存地址是否相等(与==运算符等价),而我们此时希望两个实例对象,如果x和y分别相等,则调用equals方法后返回true。此时我们需要覆盖object类的equals方法:
@Override
public booleanequals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final ReflectPoint other = (ReflectPoint) obj;
if(x != other.x)
return false;
if(y != other.y)
return false;
return true;
}
之后,我们覆盖object类的hashCode方法,使得x和y分别相等的点的散列值也一样(以下代码假设x和y都不超过30):
@Override
public inthashCode() {
final intprime = 31;
int result = 1;
result = prime * result + x;
result = prime * result + y;
return result;
}
此时,假如我们定义三个ReflectPoint类的实例,然后把他们全部添加到一个HashSet中:
Collection collections=new HashSet();
ReflectPoint pt1=new ReflectPoint(3,3);
ReflectPoint pt2=new ReflectPoint(5,5);
ReflectPoint pt3=new ReflectPoint(3,3);
collections.add(pt1);
collections.add(pt2);
collections.add(pt3);
collections.add(pt1);
此时,该集合中的元素数量显然是2,因为pt1、pt3和后来再添加的pt1的hashCode相同,而HashSet不允许有散列值相同的元素插入。
那么,加入我们随后修改了pt1的值,比如:
pt1.y = 7; //引起内存泄漏的地方
collections.remove(pt1);
这样,pt1就Remove不掉了,因为hashCode已经改变,在调用Remove方法时,计算得到的新散列值在集合中找不到,就会导致修改前的pt1对象仍然存在于集合中。
总的来说,当一个对象被存储进hashSet集合以后,就不能再修改对象中的那些参与计算哈希算法的那些字段,否则会造成内存泄露。表面上程序代码在不断增加对象,删除对象,但实际内存中并没有删除,该对象以后不再用了,可是内存中却一直存在,造成浪费,最终导致内存泄露。
附录1:HashMap实现代码(部分)
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
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;
}
附录2:HashSet实现代码(部分)
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
{
// 使用 HashMap 的 key 保存 HashSet 中所有元素
private transient HashMap<E,Object> map;
// 定义一个虚拟的 Object 对象作为 HashMap 的 value
private static final Object PRESENT = new Object();
...
// 初始化 HashSet,底层会初始化一个 HashMap
public HashSet()
{
map = new HashMap<E,Object>();
}
// 以指定的 initialCapacity、loadFactor 创建 HashSet
// 其实就是以相应的参数创建 HashMap
public HashSet(int initialCapacity, float loadFactor)
{
map = new HashMap<E,Object>(initialCapacity, loadFactor);
}
public HashSet(int initialCapacity)
{
map = new HashMap<E,Object>(initialCapacity);
}
HashSet(int initialCapacity, float loadFactor, boolean dummy)
{
map = new LinkedHashMap<E,Object>(initialCapacity
, loadFactor);
}
// 调用 map 的 keySet 来返回所有的 key
public Iterator<E> iterator()
{
return map.keySet().iterator();
}
// 调用 HashMap 的 size() 方法返回 Entry 的数量,就得到该 Set 里元素的个数
public int size()
{
return map.size();
}
// 调用 HashMap 的 isEmpty() 判断该 HashSet 是否为空,
// 当 HashMap 为空时,对应的 HashSet 也为空
public boolean isEmpty()
{
return map.isEmpty();
}
// 调用 HashMap 的 containsKey 判断是否包含指定 key
//HashSet 的所有元素就是通过 HashMap 的 key 来保存的
public boolean contains(Object o)
{
return map.containsKey(o);
}
// 将指定元素放入 HashSet 中,也就是将该元素作为 key 放入 HashMap
public boolean add(E e)
{
return map.put(e, PRESENT) == null;
}
// 调用 HashMap 的 remove 方法删除指定 Entry,也就删除了 HashSet 中对应的元素
public boolean remove(Object o)
{
return map.remove(o)==PRESENT;
}
// 调用 Map 的 clear 方法清空所有 Entry,也就清空了 HashSet 中所有元素
public void clear()
{
map.clear();
}
...
}