Java集合之Set
正文
Set的特点?
-
它与List(关于List可以参考Java集合之List)不同,它是一个不包含重复元素的collection(Set所具有的方法和Collection所具有的方法一致)。
-
它是无序的,但当向集合中存储元素的顺序与Set内部存储元素的顺序一致时就可能会出现有序的情况,只不过这种情况出现的概率很小。
Set的常用子类?
关于这一部分我们的讲解思路与List相同:先明确各自的特点,知道具体的场景该用哪一种;再分析各自所具有的一些方法。这两者都可以通过查看jdk文档获悉。
HashSet?
HashSet内部的数据结构是哈希表;它是不同步的。
那么哈希表是什么呢?顾名思义,它就是一张使用哈希算法得出的表。通过哈希算法,我们存取元素的过程就变成了这样:假如我们要存储元素A,先根据哈希算法算出A元素的哈希值,再根据该哈希值得出元素A存储到集合中的位置,根据该位置存储即可;假如我们要从集合中取出元素B,还是先根据哈希算法算出B元素的哈希值,再根据该哈希值到集合中指定的位置查找,如果该位置上的元素是B,就直接取出,如果不是,就说明该集合中没有B元素。
由于哈希表的这种特点,所以哈希表中的元素是不能重复的。哈希表确定两个元素是否相同的依据是:先用hashCode()算出两个元素的哈希值,如果二者相等再使用equals()判断两者的内容,如果两者内容也相同,那么哈希表就认为这两个元素是相同的。
由于Object类中有hashCode(),所以Java中的所有对象都有哈希值。并且Object类中的hashCode()被native修饰是底层实现的。但是我们可以自定义该方法的实现。
当两个不同的元素算出的哈希值却相等时,就出现了哈希冲突。这种情况下如何进行存取元素就会视具体的哈希算法而定。
当向HashSet中存储自定义对象并且我们对这些对象是否相等有自己的逻辑时,就可以在对象所属的类中覆盖hashCode()和equals()。假设现在有这样的需求:向HashSet集合中存储Person对象并且如果姓名和年龄相同就视为同一个人。代码如下:
import java.util.HashSet;
import java.util.Iterator;
class Person {
private String name;
private int age;
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
/**
* 覆盖hashCode()和equals()实现自己的判断逻辑
* @return
*/
@Override
public int hashCode() {
return name.hashCode() + age * 27;
}
@Override
public boolean equals(Object obj) {
if(this == obj)
return true;
if(!(obj instanceof Person))
throw new ClassCastException("类型错误");
Person p = (Person)obj;
return this.name.equals(p.name) && this.age == p.age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String toString(){
return name+":"+age;
}
}
class Demo {
public static void main(String[] args) {
HashSet hs = new HashSet();
hs.add(new Person("lisi4",24));
hs.add(new Person("lisi7",27));
hs.add(new Person("lisi1",21));
hs.add(new Person("lisi7",27));
Iterator it = hs.iterator();
while(it.hasNext()){
Person p = (Person)it.next();
System.out.println(p);
}
}
}
我们要注意List接口中的contains()和remove()内部使用的都是equals()进行判断,即是:如果我们需要使用contains()或equals()实现自定义判断逻辑,只需覆盖equals()即可。假设现在有这样的需求:删除ArrayList中重复的Person对象并且当Person对象的姓名和年龄相同就视为同一个人。代码如下:
import java.util.ArrayList;
import java.util.Iterator;
class Person {
// 此处省略Person类的属性、构造函数、set/get方法、toString(),与上同
@Override
public boolean equals(Object obj) { // 仅覆盖equals()即可
if(this == obj)
return true;
if(!(obj instanceof Person))
throw new ClassCastException("类型错误");
Person p = (Person)obj;
return this.name.equals(p.name) && this.age == p.age;
}
}
class Demo {
public static void main(String[] args) {
ArrayList al = new ArrayList();
al.add(new Person("lisi1", 21));
al.add(new Person("lisi2", 22)); // 1
al.add(new Person("lisi3", 23));
al.add(new Person("lisi4", 24));
al.add(new Person("lisi2", 22));
al.add(new Person("lisi3", 23));
al = getSingleElement(al);
System.out.println(al);
System.out.println(al.remove(new Person("lisi2",22))); // 与1处的对象是两个对象,但由于我们覆盖了equals()并实现了自己的判断逻辑,所以返回true并且删除成功
System.out.println(al);
}
private static ArrayList getSingleElement(ArrayList al) {
ArrayList temp = new ArrayList();
Iterator it = al.iterator();
while(it.hasNext()){
Object obj = it.next();
if(!temp.contains(obj)){
temp.add(obj);
}
}
return temp;
}
}
所以我们可以总结:如果涉及到判断两个对象是否相等,最好hashCode()和equals()都进行覆盖,这样对List和HashSet都适用。
如果我们希望集合在唯一时还能保证有序性,我们就可以使用LinkedHashSet。它内部是由链表和哈希表共同实现的。
TreeSet?
TreeSet的内部的数据结构是二叉树;它使用元素的自然顺序对元素进行排序;它是不同步的。
TreeSet判断元素是否唯一的根据是:判断比较方法的返回结果,如果是0,就说明是同一个元素。
我们需要注意:TreeSet对元素进行排序有两种方式,第一种就是让需要排序的类实现Comparable接口并且覆盖compareTo()。假设现在有这样的需求:以Person对象的年龄进行从小到大的排序。代码如下:
// 此处省略Person类的属性、构造函数、set/get方法、toString(),与上同
import java.util.Iterator;
import java.util.TreeSet;
class Person implements Comparable { // 实现Comparable接口
@Override
public boolean equals(Object obj) { // 仅覆盖equals()即可
if(this == obj)
return true;
if(!(obj instanceof Person))
throw new ClassCastException("类型错误");
Person p = (Person)obj;
return this.name.equals(p.name) && this.age == p.age;
}
@Override
public int compareTo(Object o) { // 覆盖compareTo()并实现自己的判断逻辑
Person p = (Person)o;
int temp = this.age - p.age;
return temp == 0 ? this.name.compareTo(p.name) : temp;
}
}
class Demo {
public static void main(String[] args) {
TreeSet ts = new TreeSet();
ts.add(new Person("zhangsan",28));
ts.add(new Person("lisi",21));
ts.add(new Person("zhouqi",29));
ts.add(new Person("zhaoliu",25));
ts.add(new Person("wangu",24));
Iterator it = ts.iterator();
while(it.hasNext()){
Person p = (Person)it.next();
System.out.println(p.getName() + ":" + p.getAge());
}
}
}
有很多类都实现了Comparable接口,意味着这些类都自定义了排序方式,这种排序方式被称为类的自然排序,那么当我们不想要按照这种自然排序方式进行排序时,就可以使用第二种方法:定义一个类(比较器)让其实现Comparator接口并且覆盖compare(),并将该类对象作为参数传递给TreeSet集合的构造函数。代码如下:
import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;
class Person {
// 此处省略Person类的属性、构造函数、set/get方法、toString(),与上同
@Override
public int hashCode() {
return name.hashCode() + age * 27;
}
@Override
public boolean equals(Object obj) {
if(this == obj)
return true;
if(!(obj instanceof Person))
throw new ClassCastException("类型错误");
Person p = (Person)obj;
return this.name.equals(p.name) && this.age == p.age;
}
}
/**
* 一个根据Person类的name进行排序的比较器
*/
class ComparatorByName implements Comparator {
@Override
public int compare(Object o1, Object o2) {
Person p1 = (Person)o1;
Person p2 = (Person)o2;
int temp = p1.getName().compareTo(p2.getName());
return temp == 0 ? p1.getAge() - p2.getAge() : temp;
}
public static void main(String[] args) {
TreeSet ts = new TreeSet(new ComparatorByName());
ts.add(new Person("zhangsan",28));
ts.add(new Person("lisi",21));
ts.add(new Person("zhouqi",29));
ts.add(new Person("zhaoliu",25));
ts.add(new Person("wangu",24));
Iterator it = ts.iterator();
while(it.hasNext()){
Person p = (Person)it.next();
System.out.println(p.getName() + ":" + p.getAge());
}
}
}
由于HashSet可以实现任意方式的排序,那我们也可以实现HashSet的有序,代码见下:
import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;
class Person {
// 此处省略Person类的属性、构造函数、set/get方法、toString(),与上同
@Override
public int hashCode() {
return name.hashCode() + age * 27;
}
@Override
public boolean equals(Object obj) {
if(this == obj)
return true;
if(!(obj instanceof Person))
throw new ClassCastException("类型错误");
Person p = (Person)obj;
return this.name.equals(p.name) && this.age == p.age;
}
}
/**
* 一个实现HashSet有序的比较器
*/
class ComparatorByName implements Comparator {
@Override
public int compare(Object o1, Object o2) {
Person p1 = (Person)o1;
Person p2 = (Person)o2;
return 1;
}
public static void main(String[] args) {
TreeSet ts = new TreeSet(new ComparatorByName());
ts.add(new Person("zhangsan",28));
ts.add(new Person("lisi",21));
ts.add(new Person("zhouqi",29));
ts.add(new Person("zhaoliu",25));
ts.add(new Person("wangu",24));
Iterator it = ts.iterator();
while(it.hasNext()){
Person p = (Person)it.next();
System.out.println(p.getName() + ":" + p.getAge());
}
}
}