Set接口
父类是Collect接口。不允许重复元素。
HashSet类
实现Set接口,底层由HashMap实现,当使用构造方法创建一个HashSet对象时,底层会使用HashMap的构造方法创建一个对象给HashSet使用。 只使用HashMap集合对象中的键的位置,值的位置都设置为null。代码复用。
集合特点
无序;(只能通过迭代器遍历)
无下标;
键唯一;
允许null元素;
基本使用
构造方法
无参构造:源代码直接使用返回的是一个 HashMap 类型对象
// 构造一个新的空集合; 背景HashMap实例具有默认初始容量(16)和负载因子(0.75)。
HashSet()
// 构造一个新的空集合; 背景HashMap实例具有指定的初始容量和指定的负载因子
HashSet(int initialCapacity, float loadFactor)
方法:此类中没有单个查询的方法 也没有修改的方法,只能遍历得到元素。
add()
作用:向列表中添加元素。
参数:元素Object;
返回值:布尔值
示例:
// 创建HashSet列表
HashSet<String> hashSet = new HashSet<String>();
// 向列表中添加元素
boolean reine = hashSet.add("Reine");
System.out.println("reine = " + reine); // reine = true
clear()
作用:清空列表元素。
参数:无;
返回值:无
示例:
// 创建HashSet列表 指定键的泛型为String
HashSet<String> hashSet = new HashSet<String>();
// 向列表中添加元素
boolean reine = hashSet.add("Reine");
// 清空列表
hashSet.clear();
contains()
作用:列表内是否包含元素。
参数:Object;
返回值:布尔值
示例:
// 创建HashSet列表 指定键的泛型为String
HashSet<String> hashSet = new HashSet<String>();
// 向列表中添加元素
boolean reine = hashSet.add("Reine");
// 列表中包含与否
boolean res = hashSet.contains("Reine");
System.out.println("res = " + res);
isEmpty()
作用:列表是否为空。
参数:无;
返回值:布尔值
示例:
// 创建HashSet列表 指定键的泛型为String
HashSet<String> hashSet = new HashSet<String>();
// 向列表中添加元素
boolean reine = hashSet.add("Reine");
// 列表是否为空
boolean empty = hashSet.isEmpty();
System.out.println("empty = " + empty); // false
remove()
作用:删除列表中的元素。
参数:object元素;
返回值:布尔值,成功为true,否则为false
示例:
// 创建HashSet列表 指定键的泛型为String
HashSet<String> hashSet = new HashSet<String>();
// 向列表中添加元素
boolean reine = hashSet.add("Reine");
// 列表中删除元素
boolean res = hashSet.remove("Reine");
System.out.println("res = " + res); // res = true
iterator()
作用:获取迭代器对象
参数:无;
返回值:迭代器Iterator对象;
示例:
// 创建HashSet列表 指定键的泛型为String
HashSet<String> hashSet = new HashSet<String>();
// 向列表中添加元素
boolean reine = hashSet.add("Reine");
// 遍历集合
Iterator<String> iterator = hashSet.iterator();
while (iterator.hasNext()){
String item = iterator.next();
System.out.println("item = " + item);
}
size()
作用:获取列表中元素数量
参数:无;
返回值:int类型;
示例:
// 创建HashSet列表 指定键的泛型为String
HashSet<String> hashSet = new HashSet<String>();
// 向列表中添加元素
boolean reine = hashSet.add("Reine");
// 获取列表中数量
int size = hashSet.size();
System.out.println("size = " + size);
遍历集合
方式:通过迭代器方式遍历集合
// 创建HashSet列表 指定键的泛型为String
HashSet<String> hashSet = new HashSet<String>();
// 向列表中添加元素
boolean reine = hashSet.add("Reine");
// 遍历集合
Iterator<String> iterator = hashSet.iterator();
while (iterator.hasNext()){
String item = iterator.next();
System.out.println("item = " + item);
}
方式:通过增强for遍历
// 通过增强for遍历
for (String s : hashSet) {
System.out.println("s = " + s);
}
源码解析
构造函数
// 直接new一个HashMap对象
public HashSet() {
map = new HashMap<>();
}
add方法直接调用put方法
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
LinkedHashSet类
LinkedHashSet继承自HashSet类。
LinkedHashSet底层实现代码复用的是LinkedHashMap类的代码。
HashSet类中的方法都可以使用。
基本使用
构造方法
// 构造一个具有默认初始容量(16)和负载因子(0.75)的新的,空的链接散列集
LinkedHashSet()
// 构造具有指定的初始容量和负载因子的新的,空的链接散列集
LinkedHashSet(int initialCapacity, float loadFactor)
方法
add()
作用:向列表中添加元素。
参数:元素Object;
返回值:布尔值
示例:
// 创建对象
LinkedHashSet<String> linkedHashSet = new LinkedHashSet<>();
// 添加元素
linkedHashSet.add("Reine");
// 遍历集合
Iterator<String> iterator = linkedHashSet.iterator();
while (iterator.hasNext()){
String item = iterator.next();
System.out.println("item = " + item); // item = Reine
}
...
方法的使用如同LinkedHashMap类中的方法一致。(方法名有所不同,使用的仍是HashSet方法名格式)
集合特点
有序;(顺序为元素插入的顺序)
双向链表;
源码解析
构造方法的源码
public LinkedHashSet() {
super(16, .75f, true);
}
// super 指向 HashSet构造,创建一个LinkedHashMap对象
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
添加元素的源码
// 复用HashMapMap中的put方法
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
TreeSet类
有序的Set集合,顺序为元素比较的顺序,父接口为Set接口,底层代码复用的是TreeMap类的代码
基本使用
构造方法
无参构造方法:通过对象类本身实现Comparable接口,重写比较方法 如同 TreeMap的使用方式。
有参构造方法:通过自定义比较器类,实现Comparator接口,重写比较方法 如同 TreeMap的使用方式。
示例:无参构造方法,通过对象类本身实现接口
package com.collectPart.SetPart;
import java.util.TreeSet;
public class TestTreeSet {
public static void main(String[] args) {
// 无参构造,使用String类中自带的方法
TreeSet<String> set = new TreeSet<String>();
// 添加元素
set.add("a");
set.add("ad");
set.add("ae");
set.add("ab");
set.add("ac");
set.add("af");
// 增强for循环遍历
for (String s : set) {
System.out.println("s = " + s);
}
System.out.println("---------------------------------------------------------");
// 创建对象,无参构造,值是Student自定义对象类型
TreeSet<Student> stuSet = new TreeSet<>();
// 创建学生对象 作为添加元素
Student stu1 = new Student("赵四",26);
Student stu2 = new Student("广坤",24);
Student stu3 = new Student("大拿",22);
Student stu4 = new Student("小宝",23);
// 添加元素
stuSet.add(stu1);
stuSet.add(stu2);
stuSet.add(stu3);
stuSet.add(stu4);
// 打印集合长度
System.out.println(stuSet.size());
System.out.println("---------------------------------------------------------");
}
}
package com.collectPart.SetPart;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
public class Student implements Comparable<Student> {
private String name;
private int 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 Student(String name, int age) {
this.name = name;
this.age = age;
}
public Student() {
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
/**
* 重写 compareTo方法 自定义比较规则
* @param stu the object to be compared.
* @return
*/
@Override
public int compareTo(Student stu) {
// if(this.getAge() > stu.getAge()){ // 当前对象的年龄大于传入对象的年龄
// return 1; // 返回正数
// }else if(this.getAge() < stu.getAge()){ // 当前对象的年龄小于传入对象的年龄
// return -1; // 返回负数
// }
// return 0; // 以上条件都不成立 表示两个对象的年龄相等 则 返回0
// return this.getAge() > stu.getAge() ? 1 : (this.getAge() < stu.getAge() ? -1 : 0) ;
return stu.getAge() - this.getAge();
}
}
示例:有参构造方法,通过自定义比较器类,实现接口
package com.collectPart.SetPart;
import java.util.TreeSet;
public class TestTreeSet {
public static void main(String[] args) {
// 创建集合对象 指定比较器对象
TreeSet<Person> personSet = new TreeSet<>(new PersonComparator());
// 创建Person对象 作为添加元素
Person p1 = new Person("赵四", 188);
Person p2 = new Person("小宝", 199);
Person p3 = new Person("刘能", 155);
Person p4 = new Person("大拿", 175);
// 添加元素
personSet.add(p1);
personSet.add(p2);
personSet.add(p3);
personSet.add(p4);
// 打印集合长度
System.out.println(personSet.size());
}
}
package com.collectPart.SetPart;
public class Person {
private String name;
private double height;
public Person(String name, double height) {
this.name = name;
this.height = height;
}
public Person() {
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", height=" + height +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
}
package com.collectPart.SetPart;
import java.util.Comparator;
public class PersonComparator implements Comparator<Person> {
/**
* 重写 compare方法 自定义比较规则 最终返回值为int类型
* @param p1 the first object to be compared.
* @param p2 the second object to be compared.
* @return
*/
@Override
public int compare(Person p1, Person p2) {
// p1大于p2 返回正数 p1小于p2 返回负数 p1==p2 返回0
return p1.getHeight() > p2.getHeight() ? 1 : (p1.getHeight() < p2.getHeight() ? -1 : 0) ;
}
}
方法
add()
作用:向列表中添加元素。
参数:元素Object;
返回值:布尔值
示例:
// 无参构造
TreeSet<String> set = new TreeSet<String>();
// 添加元素
set.add("sakuna");
set.add("yousa");
set.add("xusong");
// 增强for循环遍历
for (String s : set) {
System.out.println("s = " + s);
}
first()
作用:返回集合中的第一个元素。
参数:无;
返回值:Object元素
示例:
// 无参构造
TreeSet<String> set = new TreeSet<String>();
// 添加元素
set.add("sakuna");
set.add("yousa");
set.add("xusong");
// 查找第一个元素
String first = set.first();
System.out.println("first = " + first); // first = sakuna
...
方法如同TreeMap类中的方法使用(虽然代码调用的是TreeMap,但是方法名有所不同,使用的仍是HashSet方法名格式)。
集合特点
有序;
双向链表;
源码解析
构造函数
public TreeSet() {
this(new TreeMap<E,Object>());
}
// TreeSet
TreeSet(NavigableMap<E,Object> m) {
this.m = m;
}
// 返回的是 TreeMap对象
public TreeSet() {
this(new TreeMap<E,Object>());
}
元素添加时
// 底层使用的TreeMap中的put方法,但是在Set集合中使用的方法名是 add方法
public boolean add(E e) {
return m.put(e, PRESENT)==null;
}
Set去重
- set集合去重过程就相当于HashMap类中去除重复的键的过程。
去除的规则
两个对象的equals比较为true,并且hashCode相同,认为是重复的对象
HashSet类中去重过程示例:
package com.collectPart.SetPart;
import java.util.Objects;
public class Student implements Comparable<Student> {
private String name;
private int 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 Student(String name, int age) {
this.name = name;
this.age = age;
}
public Student() {
}
// 重写方法
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
// 重写比较顺序方法
@Override
public int compareTo(Student stu) {
// return this.getAge() > stu.getAge() ? 1 : (this.getAge() < stu.getAge() ? -1 : 0) ;
return stu.getAge() - this.getAge();
}
// 重写equals方法
// 重写equals和HashCode方法,使用 alt + insert 快捷键添加的,详细见方法重写章节
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
package com.collectPart.SetPart;
import java.util.HashSet;
public class TestSetRmDup {
public static void main(String[] args) {
// 创建HashSet接口
HashSet<Student> hashSet = new HashSet<Student>();
// 创建对象
Student stu1 = new Student("九月",16);
Student stu2 = new Student("九月",16);
Student stu3 = new Student("九月",16);
// 添加三个元素,这三个元素是重复的,在未重写 equals 方法之前,这三个都可以添加进去
// 使用equals比较是否为true,如果Student类重写equals则使用Student重写后的;如果没有重写,则用父类(默认比较对象地址)
boolean stu12 = stu1.equals(stu2);
boolean stu13 = stu1.equals(stu3);
System.out.println("stu12 = " + stu12); // 未重写前 false; 重写后 true
System.out.println("stu13 = " + stu13); // 未重写前 false; 重写后 true
// 打印hash值,调用hashCode方法,如果Student类重写hashCode方法则使用本类重写的方法; 如果没有重写则用父类;
int stu1Code = stu1.hashCode();
int stu2Code = stu2.hashCode();
int stu3Code = stu3.hashCode();
System.out.println("stu1Code = " + stu1Code); // 未重写前 1163157884; 重写后 相同 20097254
System.out.println("stu2Code = " + stu2Code); // 未重写前 1956725890; 重写后 相同 20097254
System.out.println("stu3Code = " + stu3Code); // 未重写前 356573597; 重写后 相同 20097254
// 未重写 equals和hashCode方法,会被认为是不同的对象,可以被添加到列表中
hashSet.add(stu1);
hashSet.add(stu2);
hashSet.add(stu3);
// 遍历
for (Student student : hashSet) {
System.out.println(student); // 未重写前列表汇中有三个;重写后只有一个;
}
}
}
面试题:
为什么重写 equals,必须重写HashCode方法
原因:
现在我们重写了equals改变了对象的比较规则,所以应当继续重写hashCode,以维持两个对象equals比较为true,则hashCode必须是相同的,哈希值的计算运用了equals方法的比较规则。
重写hashCode方法之后:
// 只重写equals方法时,相同的元素还是可以添加进去的。
// 相同的元素,在重写了HashCode方法之后,相同的元素只能添加进去一个。
两个对象调用equals方法比较为true;并且调用hashCode方法hashCode值相同才认为是重复的对象;