Set接口简介
Set接口也是接口Collection的子接口,它继承了Collection接口中所有的抽象方法,并且它和List接口不同,它并没有新增加属于自己的抽象方法,因此学习Set接口时会较为简单。
那么为什么它没有新增加属于自己的方法呢,这和Set接口定义的容器类的特点有关系。Set接口定义的容器类和List接口定义的容器类不同,它里面的元素是无序且不重复的,也就是说无法通过索引来操纵容器里面的元素,因此不需要新增加有关索引的方法。
Set接口常用的实现类有两个,分别是HashSet和TreeSet,一般情况下使用HashSet比较多。
HashSet容器类
HashSet容器的存储原理
HashSet容器类是Set接口的一个具体实现类,它的底层是通过HashMap来实现的,可以说HashSet就是一个简化版的HashMap。HashSet容器类中没有重复的元素,并且允许出现元素为null的情况,由于它的底层是通过HashMap来实现的,因此它的查询效率和增删效率都比较高。
在说HashSet的存储方式之前,我们需要先了解一下Hash算法。Hash算法可以用以下的一个例子来简单理解,比如我们要将一组数据存储到一个长度为16的数组当中,这时如果存储有序,那么一般会按照由小到大或者由大到小的顺序来储存,但是要如何实现无序排列呢,Hash算法是这样实现的,将需要储存的数据的值和16取余得到的值就是这个元素在数组中的位置,这样就能将元素在数组中分开储存,但是这里会不会出现一种极端情况呢?就是两个值和16进行取余运算时得到的结果是相同的情况,这时要怎样处理呢。一般情况下如果进行取余运算后得到了元素要存储到数组中的位置,如果该位置已经有元素了,那么存储位置会往下移动一个,如果移动后的位置仍然有元素,那么继续移动,以此类推。这就是Hash算法原理一个比较简单描述。
在了解Hash算法之后,我们在来谈Hash容器类的存储特征就比较好理解了。HashSet的底层是通过HashMap来实现的,而HashMap的底层是通过数组与链表来实现的。当我们往容器中添加一个元素时,java会调用Object中的HashCode方法,通过这个方法返回一个整数的值,让这个值进行Hash运算,得到的结果就是这个元素在容器中存储的位置。这样就实现了容器中存储的无序性。
实现了容器中元素的无序性,还要实现元素的不可重复性。在HashSet中,如果两个元素进行Hash算法时得到的结果是不同的,那么这两个元素肯定是不相同的,我们要考虑的就只是通过Hash算法得到的结果相同的情况。因此我们只要在通过Hash算法得到的结果相同时调用equals方法,判断两个元素是否相同即可,如果相同,将原来的元素覆盖,如果不相同,则将其存储到下一个位置,这样就实现了容器中的元素不会有重复的情况。
HashSet容器的使用
在实例化HashSet容器类时,由于其是Set接口的实现类,因此可以用Set接口来进行实例化,这里要注意,Set接口是泛型接口,在实例化时要给定泛型类型,并且用泛型的方法实例化。在HashSet容器类中,不可以出现重复的元素,但是在添加元素时,如果添加了相同的元素程序仍旧可以照常运行,但是在运行的结果中却不会出先两个相同的元素,这是因为后添加的元素把先添加的元素覆盖了的原因。此外,由于HashSet类中的元素是无序储存的,也就是说元素不存在索引,所以我们不能通过下标对元素进行操作。这也就意味着我们无法通过循环的方式来对元素进行遍历,但可以通过增强for循环来对元素进行遍历。这里可能会有一个疑问,不是说不能通过循环结构来遍历HashSet容器中的元素吗?为什么又能用增强for循环来遍历?这不是自相矛盾吗?其实,增强for循环的本质是一个迭代器,因此使用它并不需要对元素的索引进行操作就能遍历元素。
在说完上面的情况,我们这里再补充说明在HashSet容器中储存自定义对象时的问题。这里我们先创建一个User类,在这个类中定义了两个私有属性UserName和UserAge,并且提供了相应的set和get方法。构建了User类的全参构造方法和空参构造方法。然后我们新创建一个HashSet类的容器set1来储存这个User类。
可以发现,当我们没有在User类中重写hashCode方法时,此时我们创建的两个对象u和u1虽然内容完全一致,但是两个对象都添加进了容器set1中。打印它们的hashCode的值能发现它们的值并不相同,而我们在User类中重写了hashCode方法和equals方法后,它们的hashCode的值变成一样的了,所以这时只能储存一个对象。因此当我们在HashSet容器中储存自创建的对象时,要在该对象对应的类中重写hashCode方法和equals方法,不然无法满足HashSet容器的存储要求。
关于上述的HashSet类的使用,详细可参考一下演示代码:
package com.container.demo;
import java.util.Set;
import java.util.HashSet;
public class HashSetTest {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
//添加元素
set.add("a");
set.add("b1");
set.add("c2");
set.add("d");
set.add("a");//定义重复的元素时,程序能通过编译,且能运行,但一个set容器中不会出现两个相同的元素
//获取元素,没有索引,所以没有get方法,且无法通过普通for循环获取元素
for (String str:set
) {
System.out.println(str);
}
System.out.println("___________________________");
boolean flag = set.remove("a");
System.out.println(flag);
for (String str:set
) {
System.out.println(str);
}
System.out.println(set.size());
System.out.println("_________________________");
//实例化HashSet
Set<User> set1 = new HashSet<User>() ;
User user = new User("linyi",18);
User user1 = new User("linyi",18);
set1.add(user);
set1.add(user1);
System.out.println(user.hashCode());
System.out.println(user1.hashCode());
for (User users:set1
) {
System.out.println(users);
}
}
}
package com.container.demo;
import java.util.Objects;
public class User implements Comparable<User>{
private String usersName;
private int usersAge;
public String getUsersName() {
return usersName;
}
public void setUsersName(String usersName) {
this.usersName = usersName;
}
public int getUsersAge() {
return usersAge;
}
public void setUsersAge(int usersAge) {
this.usersAge = usersAge;
}
@Override
public String toString() {
return "User{" +
"usersName='" + usersName + '\'' +
", usersAge=" + usersAge +
'}';
}
public User(String usersName, int usersAge) {
this.usersName = usersName;
this.usersAge = usersAge;
}
public User() {
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return usersAge == user.usersAge && Objects.equals(usersName, user.usersName);
}
@Override
public int hashCode() {
return Objects.hash(usersName, usersAge);
}
}