1、Set接口
Set是无序不可重复的集合接口,从JDK中可以看到Set继承了Collection接口中的方法,其自身并没有额外添加实质性方法,只是在原有的Collection方法基础上,重申了方法注释,以区别于父类的使用,它的实现类常用的有HashSet和TreeSet,下面分别介绍这两个类的区别和使用;
2、HashSet
2.1HashSet的特征
(1) 继承了AbstractSet,并实现了Set、Cloneable、Serializable接口;
(2) 采用散列表HashMap实现无序不可重复的特性;
- 无序,输入顺序不等于输出顺序;
public static void main(String[] args) {
Set<String> set=new HashSet<String>();
set.add("A");
set.add("C");
set.add("B");
System.out.println(set);//输出顺序[A,B,C]
}
- 不可重复,基于equals方法判断元素是否相等,由于equals和hashCode都是成对出现且具有一致性(要么都相等,要么都不相等);
定义Person类
package xw.zx.model;
public class Person {
private int id;
private String name;
private int age;
public Person(int id, String name,int age) {
super();
this.id = id;
this.name = name;
this.age=age;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
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;
}
@Override
public String toString() {
return "Person [id=" + id + ", name=" + name + ", age=" + age + "]";
}
}
测试不可重复性
package xw.zx.collection;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import xw.zx.model.Person;
public class SetDemo {
public static void main(String[] args) {
Set<String> set=new HashSet<String>();
/**
* 字符串如果采用字面量赋值,那么将会以常量池的方式存储
* 注: s1="A",s2="A";那么 s1==s2 返回值为true;
*/
String s1=new String("A");
String s2=new String("A");
String s3=new String("B");
System.out.println(s1==s2);//false
//字符串对象重写了equals方法
System.out.println(s1.equals(s2));//true
set.add(s1);
set.add(s2);
set.add(s3);
System.out.println(set);
Set<Person> set1=new HashSet<Person>();
//自定义的Person类,未重写equals方法
Person p1=new Person(1,"张三",16);
Person p2=new Person(1,"张三",16);
Person p3=new Person(2,"李四",25);
set1.add(p1);
set1.add(p2);
set1.add(p3);
System.out.println(p1==p2);//false
System.out.println(p1.equals(p2));//false
System.out.println(set1);
}
}
输出结果:
从输出的结果中可以看出字符串没有重复输出“A” ,但是自定义的类“Person”却存在重复现象,通过查看JDK8发现String类对equals和hashCode方法进行了重写,而我们的自定义的类并未重写equals和hashCode方法,始终调用Object中默认的方法;
String类中的equals和hashCode方法
public boolean equals(Object anObject) {
//判断是否为同一引用
if (this == anObject) {
return true;
}
//判断是否为字符串,才具有可比性;
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
//判断字节数组是否相等
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
/**
*采用s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]形式构建hashCode码
*/
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
《Java编程思想》中提到正确的equals方法必须满足以下条件:
1、 自反性。对任意x,x.equals(x)一定返回true;
2、 对称性。对任意x和y,如果y.equals(x)返回true,则x.equals(y)也返回true;
3、 传递性。对任意的x、y、z,如果x.equals(y)返回true,y.equals(z)返回true,那么x.equals(z)也一定返回true;
4、 一致性。对任意的x和y,如果对象中用于等价比较的信息没有改变,那么无论条用x.equals(y)多少次,返回的结果应该保持一致,要么一直是true,要么一直是false;
5、 对任意不是null的x,x.equals(null)一定返回false;
而设计hashCode()时最重要的因素就是:无论何时,对同一个对象调用hashCode()都应该生成同样的值。所以hashCode码应该由类中不易变的成员变量生成;
Person类中重写equals和hashCode()方法
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
//age是易变的成员变量,不能用来定义hashCode码
//result = prime * result + age;
result = prime * result + id;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
/**
* 模仿String的equals方法,重写Person的equals的方法满足5大条件
*/
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if(obj instanceof Person) {
return id==((Person)obj).getId() && name.equals(((Person) obj).getName());
}
return false;
}
这样,重新编译后输出的结果如下:
(3) 由于采用了哈希无序存储,所以集合的遍历不能基于索引的For循环,而应该使用Interator迭代器遍历或者forEach语法;
package xw.zx.collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import xw.zx.model.Person;
public class SetDemo {
public static void main(String[] args) {
Set<Person> set1=new HashSet<Person>();
Person p1=new Person(1,"张三",16);
Person p2=new Person(1,"张三",16);
Person p3=new Person(2,"李四",25);
set1.add(p1);
set1.add(p2);
set1.add(p3);
//基于迭代Iterator
Iterator<Person> i=set1.iterator();
while(i.hasNext()) {
System.out.println(i.next().getName());
}
//基于forEach
for(Person p :set1) {
System.out.println(p.getName());
}
}
}
3、TreeSet
3.1、TreeSet特征
(1)基于TreeMap采用红黑数结构存储数据;
public TreeSet() {
this(new TreeMap<E,Object>());
}
(2)要求用于存储的对象必须要实现Comparable方法,否则会报错ClassCastException;
package xw.zx.collection;
import java.util.Set;
import java.util.TreeSet;
import xw.zx.model.Person;
public class SetDemo02 {
public static void main(String[] args) {
Set<Person> set=new TreeSet<Person>();
Person p1=new Person(1,"张三",16);
Person p2=new Person(1,"张三",16);
Person p3=new Person(2,"李四",25);
Person p4=new Person(3,"王五",26);
set.add(p1);
set.add(p2);
set.add(p3);
set.add(p4);
System.out.println(set);
}
}
输出后提示Person无法转化为Comparable;
所以应该让Person类实现Comparable接口,并重写compareTo方法
@Override
public int compareTo(Object o) {
return id-((Person)o).getId();
}
重新编译后输出结果: