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、更新后的曲目清单
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(四) | 下一篇 TreeSet 应用 - 卡拉 OK(六) 》 |
---|