Set集合里多个对象之间没有明显的顺序。具体详细方法请参考API文档,基本与Collection方法相同。只是行为不同(Set不允许包含重复元素)。
Set集合不允许重复元素,是因为Set判断两个对象相同不是使用==运算符,而是根据equals方法。即两个对象用equals方法比较返回true,Set就不能接受两个对象。
public class TestSet
{
public static void main(String[] args)
{
Set books = new HashSet();
//添加一个字符串对象
books.add(new String("Struts2权威指南"));
//再次添加一个字符串对象,
//因为两个字符串对象通过equals方法比较相等,所以添加失败,返回false
boolean result = books.add(new String("Struts2权威指南"));
System.out.println(result);
//下面输出看到集合只有一个元素
System.out.println(books);
}
}
程序运行结果:
false//说明没有加进去
[Struts2权威指南]
说明:程序中,book集合两次添加的字符串对象明显是同一个对象(程序通过new关键字来创建字符串对象),当使用==运算符判断返回false,使用equals方法比较返回true,所以不能添加到Set集合中,最后只能输出一个元素。
Set接口中的知识,同时也适用于HashSet、TreeSet和EnumSet三个实现类。
HashSet按Hash算法来存储集合的元素,因此具有很好的存取和查找性能。 hash算法它能保证通过一个对象快速查找到另一个对象。hash算法的价值在于速度,它可以保证查询得到快速执行。 当需要查询集合中某个元素时,hash算法直接根据该元素的值得到该元素保存位置,进而让程序快速找到该元素。当从HashSet中访问元素时,HashSet先计算该元素的hashCode值(也就是调用该对象的hashCode())方法的返回值),然后直接到该hashCode对应的位置去取出该元素。即也是快速的原因。HashSet中每个能存储元素的“曹位(slot)”通常称为“桶(bucket)”,如果多个元素的hashCode相同,但它们通过equals()方法比较返回false,就需要一个“桶”里放多个元素,从而导致性能下降。
HashSet的特点:
(1)HashSet不是同步的,多个线程访问是需要通过代码保证同步
(2)集合元素值可以使null。
HashSet集合判断两个元素相等的标准是两个对象通过equals方法比较相等,并且两个对象的hashCode()方法返回值也相等。
在java的set中,判断两个对象是否相等的规则是:
//类A的equals方法总是返回true,但没有重写其hashCode()方法
class A
{
public boolean equals(Object obj)
{
return true;
}
}
//类B的hashCode()方法总是返回1,但没有重写其equals()方法
class B
{
public int hashCode()
{
return 1;
}
}
//类C的hashCode()方法总是返回2,并重写其equals()方法
class C
{
public int hashCode()
{
return 2;
}
public boolean equals(Object obj)
{
return true;
}
}
public class TestHashSet
{
public static void main(String[] args)
{
HashSet
输出结果
[B@1, B@1, C@2, A@b5dac4, A@9945ce]
说明:
(0)因为C类实现了equals方法和hashcode方法,对每一个实例都一样,那么C类实例不能加第二个到set中。
(1)Object类提供的toString方法总是返回该对象实现类的类名+@+hashCode(16进制数)值,所以可以看到上面程序输出的结果。可以通过重写toString方法来输出自己希望的形式。同时也要注意的是Object类提供的equals()默认比较两个实例的内存地址, hashcode()默认返回内存地址。
(2)2个A对象通过equals比较返回true,但HashSet依然把它们当成2个对象;因为A类实例通过new创建的实例的hashcode值是不一样的。因为是隐式集成Object类的hashcode方法,所以判定不同。2个B对象的hashCode()返回相同值,但HashSet依然把它们当成2个对象。为什么?因为没有覆盖默认的equals方法,使得默认通过Object类的equals方法比较内存地址判定不一样。
即如果把一个对象放入HashSet中时,重写该对象equals()方法,也应该重写其hashCode()方法。其规则是:如果2个对象通过equals方法比较返回true时,这两个对象的hashCode也应该相同。
对于hashset有个有趣的现象
当向HashSet中添加一个可变对象后,并且后面程序修改了该可变对象的属性,可能导致它与集合中其他元素相同,这就可能导致HashSet中包含两个相同的对象。
package server.socket;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
//测试类
class R
{
int count;
public R(int count)
{
this.count = count;
}
public String toString()
{
return "R(count属性:" + count + ")";
}
public boolean equals(Object obj)
{
if (obj instanceof R)
{
R r = (R)obj;
if (r.count == this.count)
{
return true;
}
}
return false;
}
public int hashCode()
{
return this.count;
}
}
public class test {
public static void main(String[] args) {
HashSet hs = new HashSet();
hs.add(new R(5));
hs.add(new R(-3));
hs.add(new R(9));
hs.add(new R(-2));
//打印HashSet集合,
System.out.println(hs);
//取出第一个元素
Iterator it = hs.iterator();
R first = (R)it.next(); //first指向集合的第一个元素,不一定是那个,因为hashset是无序的
//为第一个元素的count属性赋值
first.count = -3; //first指向的元素值发生改变,地址并没有改变,使得hashset里面包含了两个元素的count属性为3
//再次输出集合
System.out.println(hs);
hs.remove(new R(-3));//删除集合中真正存放-3位置的元素
System.out.println(hs);
System.out.println("hs是否包含count为-3的R对象?" + hs.contains(new R(-3)));
//输出false,在set集合中真正的-3实例被删除了,剩下的一个-3是-2实例属性用-3更改后的,但是这个实例进入set集合是按-2进行寻找存储位置的。
//hash算法根据new R(-3)的hashcode找不到这个原来存放-2位置的实例存储位置。所以才会说set中不存在-3这样一个实例
System.out.println("hs是否包含count为-2的R对象?" + hs.contains(new R(-2)));//输出false,找到-2存储位置,发现equals不等。
说明:该程序重写了R类的equals()和hashCode()方法,这两个方法都是根据R对象的count属性来判断。从运行结果可以看出,HashSet集合中有完全相同元素,这表明set集合中两个元素已经重复,但因为HashSet在添加每个实例时没有重复元素所以把它们添加到了不同存储位置,所以HashSet完全可以容纳两个相同元素。至于第一个count为-3的R对象,它保存在count为-2的R对象对应的位置(地址)。所以向HashSet中添加可变对象时,必须十分小心。如果修改HashSet集合中的对象,有可能导致该对象与集合中其他对象相等,从而导致HashSet无法准确访问该对象。
HashSet还有一个子类LinkedHashSet,LinkedHashSet集合也根据元素hashCode值来决定元素存储位置,但它同时使用链表维护元素的次序,即当遍历LinkedHashSet集合元素时,HashSet将会按元素的添加顺序来访问集合里的元素。
Set<Integer> set = new LinkedHashSet<>();
set.add(9);
set.add(34);
set.add(23);
set.add(12);
set.add(1);
set.add(11);
set.add(344);
set.add(56);
set.add(55);
set.add(66);
for (Integer integer : set) {
System.out.println(integer);
}
TreeSet是SortedSet接口的唯一实现,TreeSet可以确保集合元素处于排序状态(元素是有序的)。
TreeSet提供的几个额外方法:
如果实例方法compareTo(Object obj)比较返回0,不任equals方法返回什么都是当做两个不同的实例。
所以TreeSet以对象的compareTo方法为判定依据
/**
Comparator comparator(): 返回当前Set使用的Comparator,或者返回null,表示以自然方式排序。
Object first():返回集合中的第一个元素。
Object last():返回集合中的最后一个元素。
Objiect lower(Object e):返回集合中位于指定元素之前的元素(即小于指定元素的最大元素,参考元素可以不是TreeSet的元素)。
Object higher(Object e):返回集合中位于指定元素之后的元素(即大于指定元素的最小元素,参考元素可以不需要TreeSet的元素)。
SortedSet subSet(fromElement, toElement):返回此Set的子集,范围从fromElement(包含大于等于)到toElement(不包含小于)。
下面这两个方法还有两个重载方法。制定是否要包含边界值
SortedSet headSet(toElement):返回此Set的子集,由小于toElement的元素组成。
SortedSet tailSet(fromElement):返回此Set的子集,由大于或等于fromElement的元素组成。
*/
public class TestTreeSetCommon
{
public static void main(String[] args)
{
TreeSet nums = new TreeSet();
//向TreeSet中添加四个Integer对象
nums.add(5);
nums.add(2);
nums.add(10);
nums.add(-9);
//输出集合元素,看到集合元素已经处于排序状态
System.out.println(nums);
//输出集合里的第一个元素
System.out.println(nums.first());
//输出集合里的最后一个元素
System.out.println(nums.last());
//返回小于4的子集,不包含4
System.out.println(nums.headSet(4));
//返回大于5的子集,如果Set中包含5,子集中还包含5
System.out.println(nums.tailSet(5));
//返回大于等于-3,小于4的子集。
System.out.println(nums.subSet(-3 , 4));
}
}
程序运行结果:
[-9, 2, 5, 10]
-9
10
[-9, 2]
[5, 10]
[2]
说明:由运行结果可以看出,TreeSet并不是根据元素的插入顺序进行排序,而是根据元素实际值来进行排序。TreeSet采用红黑树的数据结构对元素进行排序。