集合
概要:
集合类是一个比较大的类常用的ArrayList、HashMap、HashSet,也有不常用的Stack、Queue,有线程安全的Vector、HashTable,也有线程不安全的LinkedList、TreeMap等!
集合的组织关系图
Collection接口:
Collection"是集合类(Collection)的顶级接口,然而”Collections“是一个提供了一系列静态方法的集合工具类,Collection所代表的是一种规则,它所包含的元素都必须遵循一条或者多条规则。如有些允许重复而有些则不能重复、有些必须要按照顺序插入而有些则是散列,有些支持排序但是有些则不支持。在Java中所有实现了Collection接口的类都必须提供两套标准的构造函数,一个是无参,用于创建一个空的Collection,一个是带有Collection参数的有参构造函数,用于创建一个新的Collection,这个新的Collection与传入进来的Collection具备相同的元素。
List接口:
继承自Collection的“子接口”如List和Set。List是有序的Collection,即用某种特定的插入顺序维护元素顺序,类似于数组可以按索引访问实现List接口的集合有Arraylist、LinkList、Stack与Vector。以下对这几个集合逐一详细介绍。
ArrayList:
它是实现List接口下一个非同步动态数组,即长度大小可变,元素可以为任意类型包括null,数组有初始容量当增加新元素时会检查容量是否足够,默认初始值为10,可以自定义一个初始容量减少扩容数据拷贝问题,在添加大量元素前,应用程序也可以使用ensureCapacity操作来增加ArrayList实例的容量,这可以减少递增式再分配的数量。
构造函数:
1)ArrayList():默认构造函数,提供初始容量为10的空列表。2)ArrayList(int initialCapacity):构造一个具有指定初始容量的空列表。3)ArrayList(Collection<? extends E> c):构造一个包含指定 collection 的元素的列表,这些元素是按照该 collection 的迭代器返回它们的顺序排列的。
ArrayList操作:
ArrayList底层是数组实现的,所以基本操作都是对数组的操作,private transient Object[] elementData;transient java语言的关键字,变量修饰符,如果用transient声明一个实例变量,当对象存储时,它的值不需要维持。换句话来说就是,用transient关键字标记的成员变量不参与序列化过程。
ArrayList的增加元素函数: add(E e):将指定的元素添加到此列表的尾部。
public boolean add(E e) {
ensureCapacity(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
先进行扩容操作,然后将列表尾元素指向e;add(int index, E element):将指定的元素插入此列表中的指定位置。
public void add(int index, E element) {
//判断索引位置是否正确
if (index > size || index < 0)
throw new IndexOutOfBoundsException(
"Index: "+index+", Size: "+size);
//扩容检测
ensureCapacity(size+1);
/*
* 对源数组进行复制处理(位移),从index + 1到size-index。
* 主要目的就是空出index位置供数据插入,
* 即向右移动当前位于该位置的元素以及所有后续元素。
*/
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//在指定位置赋值
elementData[index] = element;
size++;
}
指定位置的插入带来了数组元素的右移,如果大量的插入操作使用Linklist较好。addAll(Collection<? extends E> c):按照指定 collection 的迭代器所返回的元素顺序,将该 collection 中的所有元素添加到此列表的尾部。
addAll(int index, Collection<? extends E> c):从指定的位置开始,将指定 collection 中的所有元素插入到此列表中。set(int index, E element):用指定的元素替代此列表中指定位置上的元素。
删除元素:
remove(int index):移除此列表中指定位置上的元素。
public E remove(int index) {
//位置验证
RangeCheck(index);
modCount++;
//需要删除的元素
E oldValue = (E) elementData[index];
//向左移的位数
int numMoved = size - index - 1;
//若需要移动,则想左移动numMoved位
if (numMoved > 0)
System.arraycopy(elementData, index + 1, elementData, index,
numMoved);
//置空最后一个元素
elementData[--size] = null; // Let gc do its work
return oldValue;
}
remove(Object o):移除此列表中首次出现的指定元素(如果存在)
public boolean remove(Object o) {
//因为ArrayList中允许存在null,所以需要进行null判断
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
//移除这个位置的元素
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
fastRemove()方法用于移除指定位置的元素。
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // Let gc do its work
}
removeRange(int fromIndex, int toIndex):移除列表中索引在 fromIndex(包括)和 toIndex(不包括)之间的所有元素
removeAll():是继承自AbstractCollection的方法,ArrayList本身并没有提供实现。
public boolean removeAll(Collection<?> c) {
boolean modified = false;
Iterator<?> e = iterator();
while (e.hasNext()) {
if (c.contains(e.next())) {
e.remove();
modified = true;
}
}
return modified;
}
查找:
ArrayList提供了get(int index)用读取ArrayList中的元素。由于ArrayList是动态数组,根据下标来获取ArrayList中的元素,速度快擅长于随机访问。
扩容:
ensureCapacity()方法扩容,使用ensureCapacity来手动增加ArrayList实例的容量,以减少递增式再分配的数量
public void ensureCapacity(int minCapacity) {
//修改计时器
modCount++;
//ArrayList容量大小
int oldCapacity = elementData.length;
/*
* 若当前需要的长度大于当前数组的长度时,进行扩容操作
*/
if (minCapacity > oldCapacity) {
Object oldData[] = elementData;
//计算新的容量大小,为当前容量的1.5倍
int newCapacity = (oldCapacity * 3) / 2 + 1;
if (newCapacity < minCapacity)
newCapacity = minCapacity;
//数组拷贝,生成新的数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
}
Linklist:
LinkedList是List接口链表的实现。基于链表实现的方式在插入和删除时更优于ArrayList,而随机访问不好。Linklist允许所有元素包括null值。LinkedList继承AbstractSequentialList,实现List、Deque、Cloneable、Serializable。
其增加和移除元素的算法get,remov,insert等可以实现用于栈和队列的使用。与ArrayList一样,非同步的。Linklist有两个基本属性,size和header,size是Linklist的大小,header链表的表头
构造方法:
两个构造方法:LinkedList提高了两个构造方法:1) LinkedList()构造一个空列表。里面没有任何元素,仅仅只是将header节点的前一个元素、后一个元素都指向自身。 c)。2)LinkedList(Collection<? extends E> c): 构造一个包含指定 collection 中的元素的列表,这些元素按其 collection 的迭代器返回的顺序排列。该构造函数首先会调用LinkedList(),构造一个空列表,然后调用了addAll()方法将Collection中的所有元素添加到列表中。
添加:
add(E e): 将指定元素添加到此列表的结尾。
public boolean add(E e) {
addBefore(e, header);
return true;
}
该方法调用addBefore方法,然后直接返回true,对于addBefore()而已,它为LinkedList的私有方法。
private Entry<E> addBefore(E e, Entry<E> entry) {
//利用Entry构造函数构建一个新节点 newEntry,
Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);
//修改newEntry的前后节点的引用,确保其链表的引用关系是正确的
newEntry.previous.next = newEntry;
newEntry.next.previous = newEntry;
//容量+1
size++;
//修改次数+1
modCount++;
return newEntry;
}
add(int index, E element):在此列表中指定的位置插入指定的元素。
addAll(Collection<? extends E> c):添加指定 collection 中的所有元素到此列表的结尾,顺序是指定 collection 的迭代器返回这些元素的顺序。
addAll(int index, Collection<? extends E> c):将指定 collection 中的所有元素从指定位置开始插入此列表。
AddFirst(E e): 将指定元素插入此列表的开头。
addLast(E e): 将指定元素添加到此列表的结尾。
删除:
remove(Object o):从此列表中移除首次出现的指定元素(如果存在)该方法首先会判断移除的元素是否为null,然后迭代这个链表找到该元素节点,最后调用remove(Entry<E> e),remove(Entry<E> e)为私有方法,是LinkedList中所有移除方法的基础方法。
clear() | 从此列表中移除所有元素。 |
remove() | 获取并移除此列表的头(第一个元素)。 |
remove(int index) | 移除此列表中指定位置处的元素。 |
remove(Objec o) | 从此列表中移除首次出现的指定元素(如果存在)。 |
removeFirst() | 移除并返回此列表的第一个元素。 |
removeFirstOccurrence(Object o) | 从此列表中移除第一次出现的指定元素(从头部到尾部遍历列表时)。 |
removeLast() | 移除并返回此列表的最后一个元素。 |
查找:
就是迭代,比对,然后就是返回当前值。
get(int index):返回此列表中指定位置处的元素。
getFirst():返回此列表的第一个元素。
getLast():返回此列表的最后一个元素。
indexOf(Object o):返回此列表中首次出现的指定元素的索引,如果此列表中不包含该元素,则返回 -1。
lastIndexOf(Object o):返回此列表中最后出现的指定元素的索引,如果此列表中不包含该元素,则返回 -1。
HashMap:
定义:
基于哈希表的 Map 接口的实现,以key-value的形式存在,key-value总是会当做一个整体来处理,系统会根据hash算法来来计算key-value的存储位置。继承自AbstractMap。
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
构造函数:
1)HashMap():构造一个具有默认初始容量 (16) 和默认加载因子 (0.75) 的空 HashMap。2)HashMap(int initialCapacity):构造一个带指定初始容量和默认加载因子 (0.75) 的空 HashMap。3)HashMap(int initialCapacity, float loadFactor):构造一个带指定初始容量和加载因子的空 HashMap。
初始容量:表示哈希表中桶的容量,加载因子表示哈希表其容量自动增加之前可以达到多满的一种尺度,衡量散列表空间的使用程度。负载因子越大表示散列表的装填程度越高,反之愈小,使用链表法的散列表查找一个元素的平均时间是O(1+a),如果负载因子越大,对空间的利用更充分,后果是查找效率的降低;如果负载因子太小,那么散列表的数据将过于稀疏,对空间造成严重浪费。系统默认负载因子为0.75,一般情况无需修改。
内部结构:
Hashmap是一个链表散列支持快速存取的结构;Hashmap 的底层是数组,但是数组的每一项都是链表,(HashMap的构造函数)
public HashMap(int initialCapacity, float loadFactor) {
//初始容量不能<0
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: "
+ initialCapacity);
//初始容量不能 > 最大容量值,HashMap的最大容量值为2^30
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
//负载因子不能 < 0
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: "
+ loadFactor);
// 计算出大于 initialCapacity 的最小的 2 的 n 次方值。
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;
this.loadFactor = loadFactor;
//设置HashMap的容量极限,当HashMap的容量达到该极限时就会进行扩容操作
threshold = (int) (capacity * loadFactor);
//初始化table数组
table = new Entry[capacity];
init();
}
initialCapacity代表了该数组的长度,每次新建一个HashMap时会初始化一个table数组。table数组的元素为Entry节点。
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
final int hash;
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
.......
}
Entry为HashMap的内部类,它包含了键key、值value、下一个节点next,以及hash值,正是由于Entry才构成了table数组的项为链表。
HashMap的存操作:
存数据源码:
public V put(K key, V value) {
//当key为null,调用putForNullKey方法,保存null与table第一个位置中,这是HashMap允许为null的原因
if (key == null)
return putForNullKey(value);
//计算key的hash值
int hash = hash(key.hashCode()); ------(1)
//计算key hash 值在 table 数组中的位置
int i = indexFor(hash, table.length); ------(2)
//从i出开始迭代 e,找到 key 保存的位置
for (Entry<K, V> e = table[i]; e != null; e = e.next) {
Object k;
//判断该条链上是否有hash值相同的(key相同)
//若存在相同,则直接覆盖value,返回旧value
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value; //旧值 = 新值
e.value = value;
e.recordAccess(this);
return oldValue; //返回旧值
}
}
//修改次数增加1
modCount++;
//将key、value添加至i位置处
addEntry(hash, key, value, i);
return null;
}
存储过程是。先判断要保存的数据key是否为null,为null调用putForNullKey方法,不为空计算key的哈希值,根据哈希值搜索数组中索引位置,如果数组中该位置有元素,比较key值知否相同,如果相同覆盖原key下的value(确保没有相同的key),如果不同保存在链头,最先保存的元素在链尾,若数组中没有该元素直接保存。
HashMap的取操作:
public V get(Object key) {
// 若为null,调用getForNullKey方法返回相对应的value
if (key == null)
return getForNullKey();
// 根据该 key 的 hashCode 值计算它的 hash 码
int hash = hash(key.hashCode());
// 取出 table 数组中指定索引处的值
for (Entry<K, V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) {
Object k;
//若搜索的key与查找的key相同,则返回相对应的value
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
}
return null;
}
通过key的hash值找到在table数组中的索引处的Entry,然后返回该key对应的value
HashSet
HashSet继承AbstractSet类,实现Set、Cloneable、Serializable接口,底层用HashMap来保存元素。
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();
}
...
}
HashSet 的实现其实非常简单,它只是封装了一个 HashMap 对象来存储所有的集合元素,所有放入 HashSet 中的集合元素实际上由 HashMap 的 key 来保存,而 HashMap 的 value 则存储了一个 PRESENT,它是一个静态的 Object 对象。
HashSet 的绝大部分方法都是通过调用 HashMap 的方法来实现的,因此 HashSet 和 HashMap 两个集合在实现本质上是相同的。
iterator()方法返回对此 set 中元素进行迭代的迭代器。返回元素的顺序并不是特定的。底层调用HashMap的keySet返回所有的key,这点反应了HashSet中的所有元素都是保存在HashMap的key中,value则是使用的PRESENT对象,该对象为static final。size()返回此 set 中的元素的数量(set 的容量)。底层调用HashMap的size方法,返回HashMap容器的大小。isEmpty(),判断HashSet()集合是否为空,为空返回 true,否则返回false。contains(),判断某个元素是否存在于HashSet()中,存在返回true,否则返回false。更加确切的讲应该是要满足这种关系才能返回true:(o==null ? e==null : o.equals(e))。底层调用containsKey判断HashMap的key值是否为空。add()如果此 set 无此元素则添加此元素。如果此Set没有包含满足(e==null ? e2==null : e.equals(e2)) 的e2时,则将e2添加到Set中,否则不添加且返回false。由于底层使用HashMap的put方法将key = e,value=PRESENT构建成key-value键值对,当此e存在于HashMap的key中,则value将会覆盖原有value,但是key保持不变,所以如果将一个已经存在的e元素添加中HashSet中,新添加的元素是不会保存到HashMap中,所以这就满足了HashSet中元素不会重复的特性。remove如果指定元素存在于此 set 中,则将其移除。底层使用HashMap的remove方法删除指定的Entry。clear从此 set 中移除所有元素。底层调用HashMap的clear方法清除所有的Entry。
HashTable
解析:
HashTable继承Dictionary类,实现Map接口。其中Dictionary类是任何可将键映射到相应值的类的抽象父类。Hashtable中有几个比较重要的参数:1)table:为一个Entry[]数组类型,Entry代表了“拉链”的节点,每一个Entry代表了一个键值对,哈希表的"key-value键值对"都是存储在Entry数组中的。2)loadFactor:加载因子。3)count:HashTable的大小,是指他所包含Entry键值对的数量。不是指容器大小。4)threshold:Hashtable的阈值,用于判断是否需要调整Hashtable的容量。threshold的值="容量*加载因子"。5)modCount:用来实现“fail-fast”机制的,所谓快速失败就是在并发集合中,其进行迭代操作时,若有其他线程对其进行结构性的修改,这时迭代器会立马感知到,并且立即抛出ConcurrentModificationException异常,而不是等到迭代完成之后才告诉你出错。
构造函数:
1)public Hashtable() { this(11, 0.75f); }构造初始容量11,与加载因子0.75的哈希表。2)指定初始容量和默认加载因子 this(initialCapacity, 0.75f);3)public Hashtable(int initialCapacity, float loadFactor)指定两个参数。4)public Hashtable(Map<? extends K, ? extends V> t) { //设置table容器大小,其值==t.size * 2 + 1 this(Math.max(2*t.size(), 11), 0.75f); putAll(t); } 构造一个与给定的 Map 具有相同映射关系的新哈希表。
Hashtable的存与取方法:
public synchronized V put(K key, V value) {//向哈希表中添加键值对
// Make sure the value is not null
if (value == null) {//确保值不能为空
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry tab[] = table;
int hash = hash(key);//根据键生成hash值---->若key为null,此方法会抛异常
int index = (hash & 0x7FFFFFFF) % tab.length;//通过hash值找到其存储位置
for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {/遍历链表
if ((e.hash == hash) && e.key.equals(key)) {//若键相同,则新值覆盖旧值
V old = e.value;
e.value = value;
return old;
}
}
modCount++;
if (count >= threshold) {//当前容量超过阈值。需要扩容
// Rehash the table if the threshold is exceeded
rehash();//重新构建桶数组,并对数组中所有键值对重哈希,耗时!
tab = table;
hash = hash(key);
index = (hash & 0x7FFFFFFF) % tab.length;//这里是取摸运算
}
// Creates the new entry.
Entry<K,V> e = tab[index];
//将新结点插到链表首部
tab[index] = new Entry<>(hash, key, value, e);//生成一个新结点
count++;
return null;
}
Hasbtable:1)并不允许值和键为空(null),若为空,会抛空指针put方法在开始处仅对value进行判断,并未对key判断,因为当调用hash方法时,若key为空,依然会抛出空指针异常。2)HashMap计算索引的方式是h&(length-1),而Hashtable用的是模运算,效率上是低于HashMap的。3)另外Hashtable计算索引时将hash值先与上0x7FFFFFFF,这是为了保证hash值始终为正数。4)特别需要注意的是这个方法包括下面要讲的若干方法都加了synchronized关键字,也就意味着这个Hashtable是个线程安全的类,这也是它和HashMap最大的不同点.
添加元素流程为:
计算key的hash值根据hash值获得key在table数组中的索引位置,然后迭代该key处的Entry链表,若该链表中存在一个这个的key对象,那么就直接替换其value值即可,否则在将改key-value节点插入该index索引位置处。再添加元素是会进行扩容操作,扩容代码为:
protected void rehash() {
int oldCapacity = table.length;//记录旧容量
Entry<K,V>[] oldMap = table;//记录旧的桶数组
// overflow-conscious code
int newCapacity = (oldCapacity << 1) + 1;//新容量为老容量的2倍加1
if (newCapacity - MAX_ARRAY_SIZE > 0) {
if (oldCapacity == MAX_ARRAY_SIZE)//容量不得超过约定的最大值
// Keep running with MAX_ARRAY_SIZE buckets
return;
newCapacity = MAX_ARRAY_SIZE;
}
Entry<K,V>[] newMap = new Entry[newCapacity];//创建新的数组
modCount++;
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
boolean currentAltHashing = useAltHashing;
useAltHashing = sun.misc.VM.isBooted() &&
(newCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
boolean rehash = currentAltHashing ^ useAltHashing;
table = newMap;
for (int i = oldCapacity ; i-- > 0 ;) {//转移键值对到新数组
for (Entry<K,V> old = oldMap[i] ; old != null ; ) {
Entry<K,V> e = old;
old = old.next;
if (rehash) {
e.hash = hash(e.key);
}
int index = (e.hash & 0x7FFFFFFF) % newCapacity;
e.next = newMap[index];
newMap[index] = e;
}
}
}
Hashtable每次扩容,容量都为原来的2倍加2,而HashMap为原来的2倍。获取代码为:
public synchronized V get(Object key) {//根据键取出对应索引
Entry tab[] = table;
int hash = hash(key);//先根据key计算hash值
int index = (hash & 0x7FFFFFFF) % tab.length;//再根据hash值找到索引
for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {//遍历entry链
if ((e.hash == hash) && e.key.equals(key)) {//若找到该键
return e.value;//返回对应的值
}
}
return null;//否则返回null
}
Hashtable特点
如果你传的参数为null,是会抛空指针。
1)Hashtable是个线程安全的类(HashMap线程不安全);
2)Hasbtable并不允许值和键为空(null),若为空,会抛空指针(HashMap可以);
3)Hashtable不允许键重复,若键重复,则新插入的值会覆盖旧值(同HashMap);
4)Hashtable同样是通过链表法解决冲突;
5)Hashtable根据hashcode计算索引时将hashcode值先与上0x7FFFFFFF,这是为了保证hash值始终为正数;
6)Hashtable的容量为任意正数(最小为1),而HashMap的容量始终为2的n次方。Hashtable默认容量为11,HashMap默认容量为16;
7)Hashtable每次扩容,新容量为旧容量的2倍加2,而HashMap为旧容量的2倍;
8)Hashtable和HashMap默认负载因子都为0.75;
哈希码
集合是分两类List与set,list有序可重复,set无须不重复,再添加元素是每次equals方法判断,在大量元素是会造成效率低下,哈希码是散列算法是将数据依特定算法直接指定到一个地址上,当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。Java对于eqauls方法和hashCode方法是这样规定的:
1、如果两个对象相同,那么它们的hashCode值一定要相同;
2、如果两个对象的hashCode相同,它们并不一定相同。
hashcode与equals的使用
equals()是对两个对象的地址值进行的比较(即比较引用是否相同)。
hashCode()是一个本地方法,它的实现是根据本地机器相关的。
Java语言对equals()的要求如下,这些要求是必须遵循的:
A 对称性:如果x.equals(y)返回是“true”,那么y.equals(x)也应该返回是“true”。
B 反射性:x.equals(x)必须返回是“true”。
C 类推性:如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”,那么z.equals(x)也应该返回是“true”。
D 一致性:如果x.equals(y)返回是“true”,只要x和y内容一直不变,不管你重复x.equals(y)多少次,返回都是“true”。
任何情况下,x.equals(null),永远返回是“false”;x.equals(和x不同类型的对象)永远返回是“false”。
equals()相等的两个对象,hashcode()一定相等;反过来:hashcode()不等,一定能推出equals()也不等;hashcode()相等,equals()可能相等,也可能不等。