概述
Set接口作为Collection的子接口,按理来说应该在原来接口的基础增加更多的方法,但是Set这个子接口呢,并没有提供额外的方法,只不过是在原来的基础上,对数据的要求更加严格了。
Set接口的特点:
- 无序性:Set集合中的元素是不要求有顺序的
- 不可重复性:Set集合中的元素是不能出现重复的
- 无索引:Set集合中的元素是没有索引的(特定的顺序编号)
既然Set没有在Collection的基础上添加新的方法,那么其常用方法也是可以参考Collection的常用方法的
Collection的常用方法可参考本文
与Collection集合的遍历方式一样,Set集合也是支持foreach和Iterator两种方式遍历元素的。
代码实例package com.atguigu.demo.Set;
import java.util.Iterator;
import java.util.linkedHashSet;
public class linkedSet {
public static void main(String[] args) {
//创建linkedHashSet集合
linkedHashSet<String> set = new linkedHashSet<>();
//添加元素
set.add("张三");
set.add("李四");
set.add("王五");
set.add("张三");
//元素个数
System.out.println("元素个数:" + set.size());
//增强for遍历元素
for (String name : set) {
System.out.println(name);
}
//使用iterator
Iterator<String> iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
Set的实现类
Set接口有很多的实现类,下面我们介绍下其常用的实现类: HashSet、TreeSet、linkedHashSet
HashSet 基本介绍HashSet是Set接口最常使用的实现类,其底层是通过HashMap实现的。(HashMap可以看这个文章)
源码如下:
public HashSet() {
map = new HashMap<>();
}
HashMap的底层物理实现是一个Hash表,那这样的话,存入元素的时候,就会根据Hash算法来存储了。即当向HashSet集合中存入一个元素时,HashSet会先判断该对象的hashCode值,然后根据hashCode值决定该对象在HashSet中的存储位置:
- 如果hashCode值不同,直接把该元素存储到hashCode()指定的位置
- 如果hashCode 值相同,那么会继续判断该元素和集合对象的equals()作比较。
所以存储到HashSet的元素要重写hashCode和equals方法。
代码示例package com.atguigu.demo.Set;
public class Employee {
//自定义属性
private String name;
private MyDate birthday;
//构造器
public Employee(String name, MyDate birthday) {
super();
this.name = name;
this.birthday = birthday;
}
public Employee() {
super();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public MyDate getBirthday() {
return birthday;
}
public void setBirthday(MyDate birthday) {
this.birthday = birthday;
}
//重写hashCode
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((birthday == null) ? 0 : birthday.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
//重写equals
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Employee other = (Employee) obj;
if (birthday == null) {
if (other.birthday != null)
return false;
} else if (!birthday.equals(other.birthday))
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
@Override
public String toString() {
return "姓名:" + name + ", 生日:" + birthday;
}
}
//hashSet测试
import java.util.HashSet;
public class TestHashSet {
@SuppressWarnings("all")
public static void main(String[] args) {
HashSet<Employee> set = new HashSet<>(); //创建HashSet集合
set.add(new Employee("张三", new MyDate(1990,1,1)));
//重复元素无法添加,因为MyDate和Employee重写了hashCode和equals方法
set.add(new Employee("张三", new MyDate(1990,1,1)));
set.add(new Employee("李四", new MyDate(1992,2,2)));
for (Employee object : set) {
System.out.println(object);
}
}
}
linkedHashSet 基本介绍
linkedHashSet是HashSet的子类,所以它的很多东西都是继承自HashSet的,这里不再说明,但是相对于HashSet,它在结点中增加两个属性before和after维护了结点的前后添加顺序,这是因为其底层采用了链表+哈希表的结构。
源码如下:
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new linkedHashMap<>(initialCapacity, loadFactor);
}
所以linkedHashSet可以保证元素的插入顺序。
代码示例因为维护了一个链表,存储、查找的性能略低于HashSet,但遍历时性能高于HashSet(根据链表进行遍历)。
import java.util.Iterator;
import java.util.linkedHashSet;
public class linkedHashSet {
public static void main(String[] args) {
//创建linkedHashSet集合
linkedHashSet<String> set = new linkedHashSet<>();
//添加元素
set.add("张三");
set.add("李四");
set.add("王五");
set.add("张三");
//元素个数
System.out.println("元素个数:" + set.size());
//增强for遍历元素
for (String name : set) {
System.out.println(name);
}
}
}
TreeSet 基本介绍
与前面两种实现类不同,TreeSet底层是基于红黑树结构实现的,另外TreeSet里的元素是有序的,而实现这个有序的特征,是需要TreeSet里的元素实现排序,其实现排序的形式有两种:自然排序和定制排序。
因为放入其中的元素的类必须实现Comparable接口,所以在元素不能为null。
自然排序
这个默认的排序,让待添加的元素类型实现Comparable接口,并重写compareTo方法(数值型按数值大小排列,字符按码值排列,Date、Time按时间戳的大小排列…默认升序)
对于 TreeSet 集合而言,它判断两个对象是否相等的唯一标准是:两个对象通过 compareTo(Object obj) 方法比较返回值为0。
定制排序
如果自然排序(Comparable)规则不符合我们的排序需求,或者元素的类型没有实现Comparable接口。那么在创建TreeSet时,可以单独指定一个Comparator的对象,根据我们自己的需求进行自定义排序。
使用定制排序判断两个元素相等的标准是:通过Comparator比较两个元素返回了0。
public class Student {
//自定义属性
private int id;
private String name;
//构造器
public Student(int id, String name) {
super();
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
//省略了name属性的get/set
@Override
public String toString() {
return "Student [id=" + id + ", name=" + name + "]";
}
}
import org.junit.Test;
import java.util.Comparator;
import java.util.TreeSet;
public class TestTreeSet {
@Test
public void test1(){
//MyDate myDate = new MyDate(1990,1,1);
//创建TreeSet集合
TreeSet<String> set = new TreeSet<>();
//TreeSet set2 = new TreeSet<>(); //ClassCastException:com.atguigu.demo.Set.MyDate cannot be cast to java.lang.Comparable
//添加元素
set.add("zhangsan"); //String它实现了java.lang.Comparable接口
set.add("zisi");
set.add("wangwu");
set.add("hangsan");
//set2.add(myDate);
//System.out.println(set2);
System.out.println("元素个数:" + set.size());
for (String str : set) {
System.out.println(str);
}
}
@Test
public void test2(){
//创建集合,并指定Comparator的对象
TreeSet<Student> set = new TreeSet(new Comparator<Student>(){ //那么在创建TreeSet时,可以单独指定一个Comparator的对象
@Override
public int compare(Student o1, Student o2) {
return o1.getId() - o2.getId();
}
});
//添加学生
set.add(new Student(3,"张三"));
set.add(new Student(1,"李四"));
set.add(new Student(2,"王五"));
set.add(new Student(3,"张三风"));
//元素个数
System.out.println("元素个数:" + set.size());
//遍历
for (Student stu : set) {
System.out.println(stu);
}
}
}
文章转自:吃透Java集合中的Set集合必备文章,快快收藏_Java-考高分网 (kaotop.com)