HashSet 应用 - 卡拉 OK(五)
文章来源:《Head First Java》修炼感悟。
通过前几篇文章,老白也了解了基本排序方法,接下来要讨论的是数据重复问题。 ArrayList 不会阻止添加重复数据,那是 Set 集合类的职责。 本篇文章我们就来了解一下 Set 集合相关特性。
一、两个对象怎样才算相等
假如有两个引用变量,那么怎样才算相等呢? 是引用到了堆上的同一个对象,还是两个对象都有相同成员? 下面分别说说两种情况:
1、引用相等
这种表示引用了堆上同一个对象,如果对两个引用调用 hashCode()
方法,必定会返回相同的 hashcode(堆上的每个对象都是唯一的),而此时使用 ==
对两个引用变量进行比较肯定也是相等的,因为是同一个对象的字节组合。 例如:
java
if (obj1.hashCode() == obj2.hashCode() && obj1.equals(obje2)) {
// 认为 obj1 与 obj2 相等
}
2、对象相等
这表示堆上的两个对象具有相同成员,从感官上认为相等。 由于 hashcode 的唯一性,这两个对象的 hashcode 并不相等,所以如果想让它们相等就必须覆盖掉从原始对象 Object 继承到的 hashCode()
和 equals()
方法,重新实现让它们返回相同的 hashcode
和 true
。 例如覆盖 Song 类方法:
java
// 覆盖父类方法,传入的歌曲对象名与当前歌名进行比较
// 比较对象是 String 类型,String 已经覆盖过此方法,所以可以和直接调用
public boolean equals(Object song) {
Song s = (Song) song;
return this.getTitle().equals(s.getTitle());
}
// 覆盖父类方法,注意使用了同一个实例变量 title
// 同样的,String 也覆盖过 hashCode() 方法,可以直接调用
public int hashCode() {
return title.hashCode();
}
经过以上处理后,即歌名相同就可以认为两个 Song 对象重复。
二、HashSet 如何判断对象是否重复
元素加入 HashSet 前,需要检查集合中是否存在相同的 hashcode 元素,如果没有则认为没有重复,可以直接加入;如果存在相同的 hashcode,还要进一步使用 equals()
检查是否真的相等,以此来确定是否重复加入对象。
所以,如果你希望曲目文件中不会出现相同歌名,就要覆盖掉 hashCode()
和 equals()
方法,让 Java 认为不相同的两个 Song 对象变为相同的,从而只会添加一个到 HashSet 中。
1、更新后的曲目清单
text
Pink Moon/Nick Drake/5/80
Somersault/Zero 7/4/84
Shiva Moon/Prem Joshua/6/120
Circles/BT/5/110
Deep Channel/Afro Celts/4/120
Passenger/Headmix/4/100
Listen/Tahiti 80/5/90
Listen/Tahiti 80/5/90
Listen/Tahiti 80/5/90
Circles/BT/5/110
可以看到,这份清单中有些重复元素,接下来准备去掉这些重复曲目。
2、修改 Song 类代码
java
/**
* 文件:Song.java
*
* 描述:歌曲信息类,用于保存歌曲名称、歌手等信息。
* 版本:v6.0
*/
public class Song implements Comparable<Song> {
// 定义歌曲信息
String title;
String artist;
String rating;
String bpm;
// 默认构造方法
public Song() {}
// 构造器,新建对象时传入歌曲信息
public Song(String t, String a, String r, String b) {
title = t;
artist = a;
rating = r;
bpm = b;
}
// 覆盖父类方法,传入的歌曲对象名与当前歌名进行比较
// 比较对象是 String 类型,String 已经覆盖过此方法,所以可以和直接调用
public boolean equals(Object song) {
Song s = (Song) song;
return this.getTitle().equals(s.getTitle());
}
// 覆盖父类方法,注意使用了同一个实例变量 title
// 同样的,String 也覆盖过 hashCode() 方法,可以直接调用
public int hashCode() {
return title.hashCode();
}
// 实现接口方法
public int compareTo(Song s) {
return title.compareTo(s.getTitle());
}
// 返回歌曲名称
public String getTitle() {
return title;
}
// 返回歌手名称
public String getArtist() {
return artist;
}
// 返回歌曲等级
public String getRating() {
return rating;
}
// 返回歌曲节拍信息
public String getBpm() {
return bpm;
}
// 重写方法,返回歌曲标题
public String toString() {
return title;
}
}
Song 类文件覆盖了 hashCode()
和 equals()
方法,强制把歌名相同的 Song 当做重复元素,不会被加入 HashSet 中。
3、修改 Karaoke 类代码:
java
/**
* 文件:Karaoke6.java
*
* 描述:模拟 KTV 曲目清单,学习使用集合排序。
* 使用 HashSet 替换 ArrayList,确保曲目清单中不会出现重复歌曲。
* 版本:v6.0
*/
import java.io.*;
import java.util.*;
public class Karaoke6 {
/**
* 用于对歌手名字进行比较的内部类,实现了Comparator接口
*/
class ArtistCompare implements Comparator<Song> {
// 对传入的Song对象的歌手名字的字符串进行比较
// 并返回一个整数值给 Collections 的比较方法
public int compare(Song one, Song two) {
return one.getArtist().compareTo(two.getArtist());
}
}
// 用来保存所有曲目的列表
ArrayList<Song> tracks = new ArrayList<Song>();
// 执行入口
public void go() {
loadSongs();
// 原始顺序
System.out.println("original: " + tracks);
// 按曲目排序
// Collections.sort(tracks);
// System.out.println("by title: " + tracks);
// 使用了 HashSet,把曲目清单全部添加到 Set 集合中
HashSet<Song> songSet = new HashSet<Song>();
songSet.addAll(tracks);
System.out.println("using Set: " + songSet);
// 按歌手名字排序
// ArtistCompare ac = new ArtistCompare();
// Collections.sort(tracks, ac);
// System.out.println("by artist: " + tracks);
}
// 载入曲目文件
private void loadSongs() {
try {
// 先不理会下面语句的含义,
// 只需知道能读取 songs.txt 文件内容就可以了
File file = new File("songs.txt");
BufferedReader reader = new BufferedReader(new FileReader(file));
String line = null;
while ((line = reader.readLine()) != null) {
addSong(line);
}
} catch (Exception e) {
e.printStackTrace();
}
}
// 解析曲目
private void addSong(String token) {
String[] tokens = token.split("/");
Song s = new Song(tokens[0], tokens[1], tokens[2], tokens[3]);
tracks.add(s);
}
// 程序入口
public static void main(String[] args) {
new Karaoke6().go();
}
}
代码使用 HashSet 替代 ArrayList。
4、编译执行
可以看到,Circles
和 Listen
的重复元素已经被去除。 目前代码还没有经过排序,这是按原始顺序输出的结果。
三、关于 hashCode 和 equals 的绕口令
- 相同
hashcode
值的两个对象并不一定相等; - 如果两个对象相等,则
hashcode
必定也相等; - 如果两个对象相等,其一使用
equals()
必定返回true
,反之亦然; - 如果
equals()
被覆盖,则hashCode()
也必须覆盖; hashcode
是唯一的,如果没有覆盖hashCode()
方法,两个对象无论如何也不会认为是相等的;equals()
用于测试两个引用变量是否引用同一个对象,如果没有覆盖这个方法,那么两个对象永远不会认为是相等的;- 如果
equals()
为true
,那么hashCode()
必定相等;而如果hashCode()
相等则不一定equals()
为true
。
结束语
从下一篇开始,老白会使用另一种集合类进行无重复排序,请保持关注。
《 上一篇 泛型应用 - 卡拉 OK(四) |
---|