6.2 HashSet和TreeSet

本节摘要
这一节我们将学习Java中三种重要的Set集合:HashSet、LinkedHashSet和TreeSet。通过动手实验,你将掌握它们的基本使用方法、去重特性以及排序功能。特别是TreeSet的自动排序功能,能让数据按照指定规则有序存储。

HashSet是Java中最常用的集合之一,它最大的特点是不允许重复元素。让我们从简单的例子开始学习。

下面的代码演示了HashSet如何自动去除重复元素:

java

import java.util.HashSet;  

public class Example08 {  
    public static void main(String[] args) {  
        HashSet<String> names = new HashSet<>();  
        
        names.add("张三");  
        names.add("李四");  
        names.add("王五");  
        names.add("李四");  
  
        System.out.println(names);  
    }  
}
观察结果
运行上面的代码,注意观察输出结果。虽然我们添加了两次"李四",但HashSet会自动去除重复,最终只保留一个。

当我们要在HashSet中存储自定义的对象时,情况会有所不同。让我们看一个例子:

java

import java.util.HashSet;  
  
class Student {  
    String id;  
    String name;  
  
    public Student(String id, String name) {  
        this.id = id;  
        this.name = name;  
    }  
  
    public String toString() {  
        return id + ":" + name;  
    }   
}  
  
public class Example08 {  
    public static void main(String[] args) {  
        HashSet<Student> students = new HashSet<>();  
        Student s1 = new Student("1", "张三");  
        Student s2 = new Student("2", "李四");  
        Student s3 = new Student("2", "李四");  
        Student s4 = new Student("3", "王五"); 
  
        students.add(s1);  
        students.add(s2);  
        students.add(s3);  
        students.add(s4); 
  
        System.out.println(students);  
    }  
}
问题出现了
运行上面的代码,你会发现输出了4个学生,包括两个相同的"2:李四"。这是因为HashSet判断两个对象是否相同时,需要用到hashCode()equals()方法。

要解决这个问题,我们需要在Student类中重写这两个方法:

修改后的代码:

java

import java.util.HashSet;  
  
class Student {  
    String id;  
    String name;  
  
    public Student(String id, String name) {  
        this.id = id;  
        this.name = name;  
    }  
  
    public String toString() {  
        return id + ":" + name;  
    }  
  
    public int hashCode() {  
        return id.hashCode();  
    }  

	// 判断自己和obj是不是相等
    public boolean equals(Object obj) {  
	    // 如果obj就是自己
        if (this == obj) {  
            return true;  
        }  
		// 如果 obj 不是 Student 类型
        if (!(obj instanceof Student)) {  
            return false;  
        }  
		// 把 obj 转换成 Student(强制类型转换)
        Student other = (Student) obj;  
        // 我们认为学号相等就是同一个学生
        return this.id.equals(other.id);  
    }  
}  
  
public class Example08 {  
    public static void main(String[] args) {  
        HashSet<Student> students = new HashSet<>();  
        Student s1 = new Student("1", "张三");  
        Student s2 = new Student("2", "李四");  
        Student s3 = new Student("2", "李四");  // 和s2学号相同
        Student s4 = new Student("3", "王五"); 
  
        students.add(s1);  
        students.add(s2);  
        students.add(s3);  // 这个不会被添加,因为学号重复了
        students.add(s4); 
  
        System.out.println(students);  
    }  
}

运行结果:

现在正常了
重写了hashCode()equals()方法后,HashSet就能正确识别重复的学生了。现在输出结果只会显示3个不同的学生。

HashSet的一个特点是不保证元素的顺序,但有时候我们希望保持插入时的顺序,这时可以使用LinkedHashSet。

动手试试

把刚才代码中的 HashSet 改成 LinkedHashSet,观察输出顺序的变化:

java

LinkedHashSet<Student> students = new LinkedHashSet<>();

TreeSet最强大的功能是自动排序。它会把存入的元素按照一定规则自动排列。

让我们先看看TreeSet如何对数字进行排序:

java

import java.util.TreeSet;

public class Example11 {
   public static void main(String[] args) {
       TreeSet ts = new TreeSet();
       ts.add(3);
       ts.add(1);
       ts.add(1);
       ts.add(2);
       ts.add(3);
       System.out.println(ts);
   }
}

运行结果:

自动排序
可以看到,尽管我们是按3、1、1、2、3的顺序添加的,但TreeSet自动按照1、2、3的顺序排列,并且去除了重复元素。

当我们想让TreeSet存储自定义对象时,需要告诉它按照什么规则排序:

java

import java.util.TreeSet;  
  
public class Example12 {  
    public static void main(String[] args) {  
        Student s1 = new Student("1", "张三");  
        s1.setHeight(175);  
        Student s2 = new Student("2", "李四");  
        s2.setHeight(170);  
        Student s4 = new Student("3", "王五");  
        s4.setHeight(180);  
  
        TreeSet<Student> students = new TreeSet<>();  
        students.add(s1);  
        students.add(s2);  
        students.add(s4);  
  
        System.out.println(students);  
    }  
}
会出错
上面的代码会报错,因为TreeSet不知道如何比较Student对象的大小。我们需要让Student类实现Comparable接口。

为了让TreeSet能够排序Student对象,我们需要在Student类中实现Comparable接口:

java

class Student implements Comparable<Student> {  
    String id;  
    String name;  
    int height;  // 身高
  
    public Student(String id, String name) {  
        this.id = id;  
        this.name = name;  
    }  
      
    public void setHeight(int height) {  
        this.height = height;  
    }  
  
    public String toString() {  
        return id + ":" + name;  
    }  
  
    public int hashCode() {  
        return id.hashCode();  
    }  
  
    public boolean equals(Object obj) {  
        if (this == obj) {  
            return true;  
        }  
  
        if (!(obj instanceof Student)) {  
            return false;  
        }  
  
        Student other = (Student) obj;  
        return this.id.equals(other.id);  
    }  
  
    // 这个方法决定了排序规则
    public int compareTo(Student other) {  
        if (this.height > other.height) {  
            return 1; // 返回正数:表示"我"比"你"大(我排在后面)
        } else if (this.height < other.height) {  
            return -1;  // 返回负数:表示"我"比"你"小(我排在前面)
        } else {  
            return 0; // 返回0:表示"我"和"你"一样大  
        }  
    }  
}

java

import java.util.TreeSet;  
  
public class Example12 {  
    public static void main(String[] args) {  
        Student s1 = new Student("1", "张三");  
        s1.setHeight(175);  
        Student s2 = new Student("2", "李四");  
        s2.setHeight(170);  
        Student s4 = new Student("3", "王五");  
        s4.setHeight(180);  
  
        TreeSet<Student> students = new TreeSet<>();  
        students.add(s1);  
        students.add(s2);  
        students.add(s4);  
  
        System.out.println(students);  
    }  
}

运行结果:

相关内容