HashSet以及TreeSet实现了哪些接口?有什么作用?

发布时间 2023-04-23 12:46:59作者: news_one

HashSet简介

HashSet是Java集合框架中非常常用的一种无序、不可重复的集合。它是通过哈希表来实现的,可以快速检索元素并消除重复。

泛型的作用

泛型可以帮助我们在编译时就发现类型错误,从而减少了运行时错误的发生。在使用HashSet时,我们通常会指定它的泛型类型为某个具体的类或接口。

假设我们定义一个Student类,并将其作为HashSet的泛型类型:

public class Student {
    private String name;
    private int age;

    // 省略构造函数和getter/setter方法
}

我们可以创建一个HashSet对象,并向其中添加多个Student对象:

Set<Student> set = new HashSet<>();
set.add(new Student("Tom", 18));
set.add(new Student("Lucy", 20));
set.add(new Student("John", 22));

为什么需要重写hashCode和equals方法

我们知道,HashSet是通过哈希表来实现的。当要往HashSet中添加某个元素时,HashSet会首先调用该元素的hashCode()方法,得到一个哈希值,然后根据这个哈希值寻找到对应的桶(哈希码)。如果该桶为空,说明该元素在HashSet中不存在,可以直接添加到该桶中。

但是,如果该桶已经存在其他元素,则需要调用这些元素的equals()方法来判断这些元素是否和要添加的元素重复。如果重复了,则无需再次添加,否则就将该元素添加到该桶中。这个过程就是HashSet中去重的过程。

那么问题来了:如何比较两个对象是否相等呢?在Java中,我们通常会重写hashCode()和equals()方法来进行比较。

在上面的代码中,我们并没有重写hashCode()和equals()方法。这样,当我们尝试将多个Student对象添加到HashSet中时,可能会出现重复的情况。

所以,我们需要针对具体的泛型类型,重写它们的hashCode()和equals()方法。这样才能确保HashSet中的元素不会重复,而是按照自己定义的相等规则进行比较。

以下是一个自定义Person类的例子:

public class Person {
    private String name;
    private int age;

    // 省略构造函数和getter/setter方法

    @Override
    public boolean equals(Object obj) {
        if (obj == null || !(obj instanceof Person)) {
            return false;
        }
        Person p = (Person)obj;
        return this.name.equals(p.getName()) && this.age == p.getAge();
    }

    @Override
    public int hashCode() {
        return 31 * name.hashCode() + age;
    }
}

在这个例子中,Person类的equals方法判断两个Person对象是否相等,它判断了name和age属性。而hashCode方法则使用了name和age属性的哈希码来计算元素的位置。

在实际开发中,我们可以根据具体的业务需求来定义equals()和hashCode()方法的实现。但一定要确保hashCode()方法的值能够尽量均匀地分布在整个Hash表中,不要让其出现哈希冲突,否则会影响HashSet的性能。

TreeSet的Comparable接口

Comparable接口的介绍

在Java中,如果我们想要对某个类的对象进行排序,可以让该类实现Comparable接口,并重写compareTo方法。Comparable接口的作用是指定一个类的自然顺序,即通过compareTo方法来定义该类的对象之间的大小关系。

Comparable接口只包含一个抽象方法:compareTo(Object obj),该方法接收一个Object对象作为参数,表示要比较的另一个对象。该方法返回一个int值,用于表示当前对象和另一个对象之间的大小关系,具体规则如下:

  • 如果当前对象小于目标对象,则返回负数;
  • 如果当前对象等于目标对象,则返回0;
  • 如果当前对象大于目标对象,则返回正数。

实现Comparable接口的两种方法

我们已经了解了Comparable接口的基本概念,接下来我们来看一下如何在TreeSet中使用该接口进行对象排序。实现Comparable接口的方式有两种:一种是在自定义对象中继承Comparable接口并重写compareTo方法,另一种是使用匿名函数重写compareTo方法。

第一种方式:在自定义对象中继承Comparable接口并重写compareTo方法

对于自定义的Student类,我们可以让它实现Comparable接口并重写compareTo方法。以下是一个例子:

public class Student implements Comparable<Student> {
    private String name;
    private int age;

    // 省略构造函数和getter/setter方法

    @Override
    public int compareTo(Student s) {
        if (this.age < s.getAge()) {
            return -1;
        } else if (this.age > s.getAge()) {
            return 1;
        }
        return 0;
    }
}

在这个例子中,我们让Student类实现了Comparable接口,并在compareTo()方法中,按照age属性进行排序。当需要对Student类的实例进行排序时,只需要将Student类的实例添加到TreeSet集合中即可:

Set<Student> set = new TreeSet<>();
set.add(new Student("Tom", 20));
set.add(new Student("Lucy", 18));
set.add(new Student("John", 22));

通过set的迭代器可以获取到有序的元素。

第二种方式:使用匿名函数重写compareTo方法

使用匿名函数重写compareTo方法可以让我们在不修改原代码的情况下,按照指定的条件进行排序,非常方便。以下是一个例子:

Set<Student> set = new TreeSet<>(new Comparator<Student>() {
    @Override
    public int compare(Student o1, Student o2) {
       return o1.getName().compareTo(o2.getName());
    }
});
set.add(new Student("Tom", 20));
set.add(new Student("Lucy", 18));
set.add(new Student("John", 22));

在这个例子中,我们使用了匿名函数来实现了Comparator接口,并在compare()方法中,按照name属性进行排序。

总结

HashSet和TreeSet是Java集合框架中最常用的两种集合类型。HashSet可以帮助我们去重和快速查找元素,而TreeSet则可以帮助我们将元素排序。当我们使用这两种集合时,需要注意它们内部实现的机制,以及泛型和Comparable接口的使用方法,确保业务逻辑得以正确实现。