Set 接口
Set 接口的定义
Set 是 Collection 的子接口 ,Set 接口以散列的形式存储数据,所以元素没有顺序。可以存储一组无序且唯一的对象。在实际开发中也不能直接实例化 Set,需要对其实现类进行实例化再完成业务操作。Set 的常用实现类主要有:
- HashSet
- LinkedHashSet
- TreeSet
Set 接口的实现类
HashSet
HashSet 是开发中经常使用到的实现类,存储一组无序且唯一的对象。这里的无序是指元素的存储顺序和遍历顺序不一致。
使用方法非常简单,让我们一起实践一下:
public class TestHashSet {
public static void main(String[] args) {
HashSet hashSet = new HashSet();
hashSet.add("Hello");
hashSet.add("World");
hashSet.add("Java");
hashSet.add("Hello");
System.out.println("hashSet的长度:"+hashSet.size());
System.out.println("遍历 hashSet");
Iterator iterator = hashSet.iterator();
while(iterator.hasNext()) {
System.out.print(iterator.next()+", ");
}
System.out.println();
hashSet.remove("Hello");
System.out.println("删除之后再次遍历 hashSet");
//这里 hashSet 对象改变了,所以我们需要重新给 iterator 赋值
iterator = hashSet.iterator();
while(iterator.hasNext()) {
System.out.print(iterator.next()+", ");
}
}
}
运行结果:
对集合进行了添加两个 “Hello” 的操作,但是只保存了一个,这是因为 HashSet 集合的元素是唯一的
LinkedHashSet
LinkedHashSet 是 Set 的另一个接口,可以存储一组有序且唯一的元素。这里的有序是指元素的存储顺序和遍历顺序是一致的。
示例:
public class TestLinkedHashSet {
public static void main(String[] args) {
LinkedHashSet linkedHashSet = new LinkedHashSet();
linkedHashSet.add("Hello");
linkedHashSet.add("World");
linkedHashSet.add("Java");
linkedHashSet.add("Hello");
System.out.println("linkedHashSet 的长度:"+linkedHashSet.size());
System.out.println("编译 linkedHashSet");
Iterator iterator = linkedHashSet.iterator();
while(iterator.hasNext()) {
System.out.print(iterator.next()+", ");
}
System.out.println();
linkedHashSet.remove("World");
System.out.println("删除之后遍历 linkedHashSet");
iterator = linkedHashSet.iterator();
while(iterator.hasNext()) {
System.out.print(iterator.next()+", ");
}
}
}
运行结果:
我们对集合执行了添加两个 “Hello” 的操作,但是只保存了一个,这是因为LinkedHashSet 集合的元素是唯一的,既不能出现两个相等的元素。字符串如此,其他对象也是一样。我们定义一个 A 类,将类的实例化对象存入集合
public class Test{
public static void main(String[] args){
LinkedHashSe set = new LinkedHashSet();
set.add(new A(1));
set.add(new A(1));
Iterator iterator = set.iterator();
while(iterator.hasNext()){
System.out.print(iterator.next()+",");
}
}
}
class A{
private int num;
public A(int num){
this.num = num;
}
@Override
public String toString(){
return "A [num=" + num +"]";
}
}
运行结果:
A [num=1],A [num=1],
我们可以看到两个 A 对象都保存到了集合中,也就是说当前集合不认为这两个对象相等,那么程序是如何来鉴别两个对象是否相等呢?通过继承自 Object 类的 equals() 方法来判断,Object 类的 equals() 方法定义如下
public boolean equals(Object obj){
return (this == obj);
}
"==" 表示比较两个对象的内存地址,所以虽然两个 A 对象的 num 值相等,也就是从内容的角度来看是相等的,但是内存地址不同,所有程序 会认为是不想等的两个对象
- 首先让我们了解一下 LinkedHashSet 判断两个对象是否相等的原理
首先会判断两个对象的 hashCode 是否相等,什么是hashCode?简单来说就是将对象的内部信息(如内存地址,属性值等),通过某种特定规则转换成一个散列值,也就是该对象的 hashCode。两个不同对象的 hashCode 可能相等,但是 hashCode 不相等的两个对象一定不是同一个对象
所以集合在判断两个对象是否相等时,会先比较它们的 hashCode,如果不相等,则认为不是同一个对象,可以添加。如果 hashCode 相等,还不能认为两个对象就是相等的,需要通过 equlas() 方法进一步判断。如果 equals() 方法为 true,则不会重复添加;如果 equals() 方法为 false,则正常添加。
先判断 hashCode 是否相等可以减少 equals() 方法的调用,提高效率。所以两个 A 相等的前提是 hashCode 相等,且 equlas() 方法返回 true
对类 A 进行修改:
class A{
.....
@Override
public boolean equals(Object obj){
return true;
}
@Override
pubilc int hashCode(){
return 1;
}
}
运行结果:
A [num=1],
程序显示只存储了一个 A 对象。
TreeSet
在 Set 的接口中,除了 LinkedHashSet 可以存放有序元素之外,TreeSet 中保存的元素也是有序的,并且 TreeSet 的有序和 LinkedHashSet 的有序有所不同。LinkedHashSet 的有序是指元素的存储顺序和遍历顺序是一致的,即元素按什么顺序进去,遍历时就按什么顺序输出。TreeSet 的有序是指集合内部会自动给所有的元素按照升序进行排序,即无论存入元素的顺序是什么,遍历时会按照升序进行输出。
public class TestTreeSet {
public static void main(String[] args) {
TreeSet treeSet = new TreeSet();
treeSet.add(1);
treeSet.add(2);
treeSet.add(5);
treeSet.add(3);
treeSet.add(1);
treeSet.add(4);
System.out.println("treeSet 的长度:"+treeSet.size());
System.out.println("遍历 treeSet");
Iterator iterator = treeSet.iterator();
while(iterator.hasNext()) {
System.out.print(iterator.next()+", ");
}
System.out.println();
treeSet.remove(3);
System.out.println("删除之后遍历 treeSet");
iterator = treeSet.iterator();
while(iterator.hasNext()) {
System.out.print(iterator.next()+", ");
}
}
}
运行结果:
因为 TreeSet 内部会自动按照升序对元素进行排序,所以添加到 TreeSet 集合中的元素必须具备排序的功能,现在我们创建一个 A 类,同时将 A 的实例化对象保存到 TreeSet 中
class A{
private int num;
public A(int num) {
this.num = num;
}
@Override
public String toString() {
return "A [num="+num+"]";
}
}
public class TestTreeSetA {
public static void main(String[] args) {
TreeSet treeSet = new TreeSet();
treeSet.add(new A(2));
treeSet.add(new A(1));
treeSet.add(new A(5));
treeSet.add(new A(1));
treeSet.add(new A(4));
treeSet.add(new A(3));
System.out.println("treeSet的长度"+treeSet.size());
System.out.println("遍历 treeSet");
Iterator iterator = treeSet.iterator();
while(iterator.hasNext()) {
System.out.print(iterator.next()+", ");
}
System.out.println();
treeSet.remove(new A(3));
System.out.println("删除之后遍历 treeSet");
iterator = treeSet.iterator();
while(iterator.hasNext()) {
System.out.print(iterator.next()+", ");
}
}
}
尝试运行会报错,报错原因是 A 不具备排序功能,如何解决呢?让 A 实现 Comparable 接口即可。
class A implements Comparable{
private int num;
public A(int num) {
this.num = num;
}
@Override
public int compareTo(Object o) {
/**
* A.compareTo(B)
* 返回值
* 1 表示 A 大于 B
* 0 表示 A 等于 B
* -1 表示 A 小于 B
*
* */
A a = (A) o;
if(this.num > a.num) {
return 1;
}else if(this.num == a.num) {
return 0;
}else {
return -1;
}
}
@Override
public String toString() {
return "A [num="+num+"]";
}
}
public class TestTreeSetA {
public static void main(String[] args) {
TreeSet treeSet = new TreeSet();
treeSet.add(new A(2));
treeSet.add(new A(1));
treeSet.add(new A(5));
treeSet.add(new A(1));
treeSet.add(new A(4));
treeSet.add(new A(3));
System.out.println("treeSet的长度"+treeSet.size());
System.out.println("遍历 treeSet");
Iterator iterator = treeSet.iterator();
while(iterator.hasNext()) {
System.out.print(iterator.next()+", ");
}
System.out.println();
treeSet.remove(new A(3));
System.out.println("删除之后遍历 treeSet");
iterator = treeSet.iterator();
while(iterator.hasNext()) {
System.out.print(iterator.next()+", ");
}
}
}
运行结果: