HashSet以及TreeSet实现了哪些接口?有什么作用?
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接口的使用方法,确保业务逻辑得以正确实现。
本文来自博客园,作者:news_one,转载请注明原文链接:https://www.cnblogs.com/new-one/p/17346231.html