陷阱20 各负其责——重写equals方法但是没有重写hashCode方法
在程序开发的过程中,有时需要重写对象的equals方法,但是在重写对象的equals方法时,一定要记得重写对象的hashCode方法,因为在解决一些特殊问题时,需要应用对象的散列码,例如,当判断某个HashSet集合中是否包含某个对象时,就要用到对象的散列码,如果两个对象的散列码不同,就认为它们是不同的对象。
下面看一个由于重写对象的equals方法而没有重写hashCode方法,导致程序判断集合中是否包含指定对象出错的实例。
例3.20.1 创建类,在该类中重写equals 方法,然后创建该类的两个实例,将其中一个实例放到HashSet 集合中,并判断该集合中是否包含另一个对象。(光盘位置:光盘\MR\Instance\3\20\RewriteMethodApp)
import java. util. HashSet; import java. util. Set; public class Book { private String bookType; // 图书类型 private String bookName; // 图书名称 public Book (String bookType, String bookName) { this. bookType = bookType; // 初始化图书类型 this. bookName = bookName; // 初始化图书名称 } public boolean equals (Object obj) { if (this == obj) { return true; // 当前对象与指定的对象相同返回true } if (obj == null) { return false; // 指定的对象为null,返回false } if (! (obj instanceof Book)) { return false; // 指定对象不是Book的实例,返回false } Book other = (Book) obj; // 将指定对象转换为Book的实例 if (bookName == null) { if (other. bookName ! = null) { // 如果图书名称是null, Book实例的图书名称不为null,返回false return false; } } else if (! bookName. equals (other. bookName)) { // 如果图书名称与Book实例的图书名称不同,返回false return false; } if (bookType == null) { if (other. bookType ! = null) { // 如果图书类型是null, Book实例的图书类型不为null,返回false return false; } } else if (! bookType. equals (other. bookType)) { // 如果图书类型与Book实例的图书类型不同,返回false return false; } return true; // 上述条件都不满足,说明两个对象相同,返回true } public static void main (String[] args) { Book book1 = new Book ("IT", "Java"); // 创建Book的实例book1 Book book2 = new Book ("IT", "Java"); // 创建Book的实例book2 // 输出两个对象的散列码 System. out. println ("对象book1的散列码是:" + book1. hashCode ()); System. out. println ("对象book2的散列码是:" + book2. hashCode ()); // 使用HashSet类创建Set集合对象 Set<Book> set = new HashSet<Book>(); set. add (book1); // 将Book的实例book1添加到集合 // 查看集合中是否包含Book的实例book2 boolean bool = set. contains (book2); System. out. println ("Set集合中是否包含对象book2? " + bool); } }
运行本实例,程序将输出如图3.19所示的信息。
图3.19 没有重写hashCode方法输出的信息
说明
从图3.19的①和②可以看出,本实例输出Book类的两个实例book1和book2的散列码值是不同的,它们的散列码值分别为11077203和14576877,因此虽然book1和book2都是通过图书类型 "IT" 和图书名称 "Java" 创建的,但是由于它们的散列码值不同,所以从③处可以看出,在添加了book1的Set集合中判断是否包含book2时,输出了假值false,表示集合中不包含对象book2。
下面看一个在重写对象的equals方法的同时,也重写了hashCode方法,使程序能够正确判断集合中是否包含指定对象的实例。
例3.20.2 创建类,在该类中重写对象的equals方法和hashCode方法,然后创建该类的两个实例,将其中一个实例放到HashSet集合中,并判断该集合中是否包含另一个对象。(光盘位置:光盘\MR\ Instance\3\20\RewriteTwoMethodApp)
import java. util. HashSet; import java. util. Set; public class Book { private String bookType; // 图书类型 private String bookName; // 图书名称 public Book (String bookType, String bookName) { this. bookType = bookType; // 初始化图书类型 this. bookName = bookName; // 初始化图书名称 } // 重写equals方法 public boolean equals (Object obj) { if (this == obj) { return true; // 当前对象与指定的对象相同,返回true } if (obj == null) { return false; // 指定的对象为null,返回false } if (! (obj instanceof Book)) { return false; // 指定对象不是Book的实例,返回false } Book other = (Book) obj; // 将指定对象转换为Book的实例 if (bookName == null) { if (other. bookName ! = null) { // 如果图书名称是null, Book实例的图书名称不为null,返回false return false; } } else if (! bookName. equals (other. bookName)) { // 如果图书名称与Book实例的图书名称不同,返回false return false; } if (bookType == null) { if (other. bookType ! = null) { // 如果图书类型是null, Book实例的图书类型不为null,返回false return false; } } else if (! bookType. equals (other. bookType)) { // 如果图书类型与Book实例的图书类型不同,返回false return false; } return true; // 上述条件都不满足,说明两个对象相同,返回true }
运行本实例,由于重写equals方法的同时重写了hashCode方法,所以程序将输出如图3.20所示的信息。
图3.20 重写hashCode方法之后输出的信息
说明
从图3.20的①和②可以看出,Book 类的两个实例book1和book2的散列码值都是71349994,即它们的散列码值是相同的,因此对于通过图书类型 "IT" 和图书名称 "Java" 创建的两个对象book1和book2代表相同的对象,所以从图中的③处可以看出,在添加了book1的Set集合中判断是否包含book2时,输出了真值true,表示集合中包含了对象book2。