您的位置:首页 > 编程语言 > Java开发

Java随笔-Set集合(上)

2015-10-29 20:31 351 查看
Set集合类似于一个罐子,集合里的多个对象之间没有明显的顺序关系.Set集合与Collection一样,没有提供额外的方法.

Set接口继承于Collection接口.

Set集合里不允许有相同的元素.

Set判断两个对象是否相同的时候使用的是equals方法,而不是==.

示例代码:

import java.util.Set;
import java.util.HashSet;

public class SetTest
{
public static void main(String[] args)
{
Set books=new HashSet();
//添加一个字符串对象
books.add(new String("哈利波特"));
//再次添加一个字符串对象
//因为两个字符串对象通过equals方法比较相等
//所以添加失败,返回false
boolean result=books.add(new String("哈利波特"));
//从下面可以看出输出集合中只有一个元素
System.out.println(result+"--->"+books);
}
}


代码中,books两次添加对象不是一个对象,但是equals比较返回了true,所以添加失败,最后的输出结果也就只有一个结果.

接下来介绍Set集合接口的三个具体实现类,HashSet, TreeSet, EnumSet.

一. HashSet类

HashSet是Set接口典型实现,大多数时候使用Set集合就是使用这个实现类.

HashSet具有以下特点:

不能保证元素的排列顺序

HashSet不是同步的,多个线程访问一个HashSet时如果有修改操作必须有代码确保HashSet同步

集合的元素值可以是null

HashSet是通过调用hashCode()方法调用hashCode值进行equals比较,判断是否相同的.

下面代码重写了equals()和hashCode()两个方法的一个或者全部:

import java.util.HashSet;

//类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 HashSetTest
{
public static void main(String[] args)
{
HashSet books=new HashSet();
//分别向books集合中添加两个A对象,两个B对象,两个C对象
books.add(new A());
books.add(new A());
books.add(new B());
books.add(new B());
books.add(new C());
books.add(new C());
System.out.println(books);
}
}


代码中,分别添加了两个A对象,两个B对象,和两个C对象,其中,C重写了equals()总是返回true和hashCode()方法总是返回2,因此HashSet把两个C对象当成了同一个对象,只能添加一个.但是A和B的两个对象都能够添加.

所以,一定要注意:当需要重写类中的equals()方法时,一定要对应着重写HashCode()方法,原则是:如果两个对象通过equals()方法比较返回是true,则两个对象的hashCode值也要一致.

当向HashSet中添加可变的可变对象时,必须十分小心,如果修改HashSet集合中的对象,有可能导致该对象与集合中的其他对象相等,从而导致HashSet无法准确的访问对象!

二. LinkedHashSet类

LinkedHashSet是Has后Set的子类,它也用hashCode值决定元素的存储位置,但是同时使用链表保持元素的插入次序.性能略低于hashCode.

import java.util.LinkedHashSet;

public class LinkedHashSetTest
{
public static void main(String[] args)
{
LinkedHashSet books=new LinkedHashSet();
books.add("哈利波特");
books.add("小王子");
System.out.println(books);
//删除 哈利波特
books.remove("哈利波特");
//重新添加 哈利波特
books.add("哈利波特");
System.out.println(books);
}
}


输出LinkedHashSet集合的元素时,元素顺序总是与添加顺序一致.

三. TreeSet类

TreeSet是SortedSet接口的实现类,可以确保集合元素处于排序状态.

部分功能代码如下:

import java.util.TreeSet;

public class TreeSetTest
{
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));
}
}


从上面代码的结果中可以看出,TreeSet不是根据元素插入的顺序排序的,而是根据元素的实际值的大小来排序的.TreeSet支持两种排序方法:自然排序和定制排序,默认为自然排序.

1.自然排序.

TreeSet是调用的Comparable接口中的compareTo(Object obj)方法来比较元素之间的大小关系,然后将集合元素按照升序排列,这就是自然排序.所以,如果试图将一个对象添加到TreeSet中时,该对象所属类必须实现Comparable接口,否则会抛出异常!

同时,向TreeSet集合中添加的对象必须是同一类型的对象,否则也会引发异常,因为不同的类型对象不具有可比性.

当然,两个通过compareTo(Object obj)方法进行比较后相等的对象,新对象也是无法添加到TreeSet集合中的.

实例代码如下:

import java.util.TreeSet;

class Z implements Comparable
{
int age;
public Z(int age)
{
this.age=age;
}
//重写equals方法,总是返回true
public boolean equals(Object obj)
{
return true;
}
//重写了compareTo(Object obj)方法,总是返回正整数
public int compareTo(Object obj)
{
return 1;
}
}

public class TreeSetTest2
{
public static void main(String[] args)
{
TreeSet set=new TreeSet();
Z z1=new Z(6);
set.add(z1);
//输出true,表明添加成功
System.out.println(set.add(z1));
//下面输出set集合,将看到有两个元素
System.out.println(set);
//修改set集合的第一个元素的age变量
((Z)(set.first())).age=9;
//输出set集合的最后一个元素的age变量,将看到也变成了9
System.out.println(((Z)(set.last())).age);
}
}


虽然代码中添加了两个z1对象,但是因为z1对象的compareTo(Object obj)方法总是返回1,虽然它的equals()方法总是返回true,但TreeSet会认为z1对象和它自己也不是相等的,所以可以添加两个.但因为始终是z1一个元素,所以一旦z1中的成员的值被改变了,无论添加了几次,都会发生变化.

所以,当需要把一个对象放入TreeSet中时,重写equals()方法和compareTo(Object obj)方法应保持一致的结果,其规则为:如果两个对象通过equals()方法比较返回true时,这两个对象通过compareTo()方法返回的值应该为0.

同时,在TreeSet集合中放入可变类对象一旦对象中的值发生改变,TreeSet在处理这些对象时将变得非常复杂,容易出错.所以,为了程序的健壮性,推荐在使用HashSet和TreeSet集合中只放入不可变量.

2.定制排序.

TreeSet自然排序是根据元素大小按升序排序.但有时想定制排序方法,如降序,则要通过Comparator接口的帮助.该接口中的int compare(T o1,T o2)方法,如果该方法返回正整数,则o1大于o2;如果该方法返回0,则o1和o2相等;如果返回负整数,则o1小于o2.

实现定制排序时,需要在创建TreeSet对象时提供一个Comparator对象与该TreeSet集合关联,由Comparator对象管理排序逻辑.

代码如下:

import java.util.TreeSet;
import java.util.Comparator;

class M
{
int age;
public M(int age)
{
this.age=age;
}
public String toString()
{
return "M[age:"+age+"]";
}
}
public class TreeSetTest4
{
public static void main(String[] args)
{
TreeSet ts=new TreeSet(new Comparator()
{
//根据M对象的age属性来决定大小
public int compare(Object o1,Object o2)
{
M m1=(M)o1;
M m2=(M)o2;
return m1.age>m2.age?-1:m1.age<m2.age?1:0;
}
});
ts.add(new M(5));
ts.add(new M(-3));
ts.add(new M(9));
System.out.println(ts);
}
}


代码中,创建了一个Comparator接口的匿名内部类,负责对象ts集合的排序.当把M对象添加到ts集合中时,无需M类实现Comparator接口,而是由与TreeSet关联的Comparator对象来负责排序.

参考资料: 《疯狂Java讲义》
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息