覆写hashCode()

在明白了HashMap具有哪些功能,以及实现原理后,了解如何写一个hashCode()方法就更有意义了。当然,在HashMap中存取一个键值对涉及到的另外一个方法为equals (),因为该方法的覆写在高级特性已经讲解了。这里就不做过多的描述。

设计hashCode()时最重要的因素就是:无论何时,对同一个对象调用hashCode()都应该生成同样的值。如果在将一个对象用put()方法添加进HashMap时产生一个hashCode()值,而用get()取出时却产生了另外一个hashCode()值,那么就无法重新取得该对象了。所以,如果你的hashCode()方法依赖于对象中易变的数据,那用户就要小心了,因为此数据发生变化时,hashCode()就会产生一个不同的hash码,相当于产生了一个不同的“键”。

此外,也不应该使hashCode()依赖于具有唯一性的对象信息,尤其是使用this的值,这只能产生很糟糕的hashCode()。因为这样做无法生成一个新的“键”,使之与put()种原始的“键值对”中的“键”相同。例如,如果我们不覆写Object的hashCode()方法,那么调用该方法的时候,就会调用Object的hashCode()方法的默认实现。Object的hashCode()方法,返回的是当前对象的内存地址。下次如果我们需要取一个一样的“键”对应的键值对的时候,我们就无法得到一样的hashCode值了。因为我们后来创建的“键”对象已经不是存入HashMap中的那个内存地址的对象了。

我们看一个简单的例子,就能更加清楚的理解上面的意思。假定我们写了一个类:Person (人),我们判断一个对象“人”是否指向同一个人,只要知道这个人的身份证号一直就可以了。

先看我们没有实现hashCode的情况:

package c08.hashEx;

import java.util.*;

//身份证类

class Code{

   final int id;//身份证号码已经确认,不能改变

   Code(int i){

      id=i;

   }

   //身份号号码相同,则身份证相同

   public boolean equals(Object anObject) {

      if (anObject instanceof Code){

         Code other=(Code) anObject;

         return this.id==other.id;

      }

      return false;

   }

   public String toString() {

      return "身份证:"+id;

   } 

}

//人员信息类

class Person {

   Code id;// 身份证

   String name;// 姓名

   public Person(String name, Code id) {

      this.id=id;

      this.name=name;

   }

   //如果身份证号相同,就表示两个人是同一个人

   public boolean equals(Object anObject) {

      if (anObject instanceof Person){

         Person other=(Person) anObject;

         return this.id.equals(other.id);

      }

      return false;

   }

   public String toString() {

      return "姓名:"+name+" 身份证:"+id.id+"\n";

   }

}

public class HashCodeEx {

   public static void main(String[] args) {

      HashMap map=new HashMap();

      Person p1=new Person("张三",new Code(123));

      map.put(p1.id,p1);//我们根据身份证来作为key值存放到Map中

      Person p2=new Person("李四",new Code(456));

      map.put(p2.id,p2);

      Person p3=new Person("王二",new Code(789));

      map.put(p3.id,p3);

      System.out.println("HashMap 中存放的人员信息:\n"+map);

      // 张三 改名为:张山 但是还是同一个人。

      Person p4=new Person("张山",new Code(123));

      map.put(p4.id,p4);

      System.out.println("张三改名后 HashMap 中存放的人员信息:\n"+map);

      //查找身份证为:123 的人员信息

      System.out.println("查找身份证为:123 的人员信息:"+map.get(new Code(123)));

   }

}

运行结果为:

HashMap 中存放的人员信息:

{身份证:456=姓名:李四 身份证:456

, 身份证:123=姓名:张三 身份证:123

, 身份证:789=姓名:王二 身份证:789

}

张三改名后 HashMap 中存放的人员信息:

{身份证:456=姓名:李四 身份证:456

, 身份证:123=姓名:张三 身份证:123

, 身份证:123=姓名:张山 身份证:123

, 身份证:789=姓名:王二 身份证:789

}

查找身份证为:123 的人员信息:null

上面的例子的演示的是,我们在一个HashMap中存放了一些人员的信息。并以这些人员的身份证最为人员的“键”。当有的人员的姓名修改了的情况下,我们需要更新这个HashMap。同时假如我们知道某个身份证号,想了解这个身份证号对应的人员信息如何,我们也可以根据这个身份证号在HashMap中得到对应的信息。

而例子的输出结果表示,我们所做的更新和查找操作都失败了。失败的原因就是我们的身份证类:Code 没有覆写hashCode()方法。这个时候,当查找一样的身份证号码的键值对的时候,使用的是默认的对象的内存地址来进行定位。这样,后面的所有的身份证号对象new Code(123) 产生的hashCode()值都是不一样的。所以导致操作失败。

下面,我们给Code类加上hashCode()方法,然后再运行一下程序看看:

//身份证类

class Code{

   final int id;//身份证号码已经确认,不能改变

   Code(int i){

      id=i;

   }

   //身份号号码相同,则身份证相同

   public boolean equals(Object anObject) {

      if (anObject instanceof Code){

         Code other=(Code) anObject;

         return this.id==other.id;

      }

      return false;

   }

   public String toString() {

      return "身份证:"+id;

   } 

   //覆写hashCode方法,并使用身份证号作为hash值

   public int hashCode(){

      return id;

   }

}

再次执行上面的HashCodeEx 的结果就为:

HashMap 中存放的人员信息:

{身份证:456=姓名:李四 身份证:456

, 身份证:789=姓名:王二 身份证:789

, 身份证:123=姓名:张三 身份证:123

}

张三改名后 HashMap 中存放的人员信息:

{身份证:456=姓名:李四 身份证:456

, 身份证:789=姓名:王二 身份证:789

, 身份证:123=姓名:张山 身份证:123

}

查找身份证为:123 的人员信息:姓名:张山 身份证:123

这个时候,我们发现。我们想要做的更新和查找操作都成功了。

对于Map部分的使用和实现,主要就是需要注意存放“键值对”中的对象的equals()方法和hashCode()方法的覆写。如果需要使用到排序的话,那么还需要实现Comparable 接口中的compareTo() 方法。我们需要注意Map中的“键”是不能重复的,而是否重复的判断,是通过调用“键”对象的equals()方法来决定的。而在HashMap中查找和存取“键值对”是同时使用hashCode()方法和equals()方法来决定的。

J2SE知识点摘记(二十四)的更多相关文章

  1. J2SE知识点摘记(二十六)

    为了用“集合框架”的额外部分把排序支持添加到 Java 2 SDK,版本 1.2,核心 Java 库作了许多更改.像 String 和 Integer 类如今实现 Comparable 接口以提供自然 ...

  2. J2SE知识点摘记(二十五)

    Set 1.5.1        概述 Java 中的Set和正好和数学上直观的集(set)的概念是相同的.Set最大的特性就是不允许在其中存放的元素是重复的.根据这个特点,我们就可以使用Set 这个 ...

  3. J2SE知识点摘记(二十二)

    Map 1.4.1        概述 数学中的映射关系在Java中就是通过Map来实现的.它表示,里面存储的元素是一个对(pair),我们通过一个对象,可以在这个映射关系中找到另外一个和这个对象相关 ...

  4. J2SE知识点摘记(二十)

    List 1.3.1        概述 前面我们讲述的Collection接口实际上并没有直接的实现类.而List是容器的一种,表示列表的意思.当我们不知道存储的数据有多少的情况,我们就可以使用Li ...

  5. J2SE知识点摘记(二十三)

    我们简单介绍一下这个接口: 1.4.3        Comparable 接口 在 java.lang 包中,Comparable 接口适用于一个类有自然顺序的时候.假定对象集合是同一类型,该接口允 ...

  6. J2SE知识点摘记(二)

    1.    对象的声明 "类名 对象名 = new 类名();"例子:Person P;//先声明一个Person类的对象p p=new Person();//用new关键字实例化 ...

  7. J2SE知识点摘记(二十一)

    实现原理 前面已经提了一下Collection的实现基础都是基于数组的.下面我们就已ArrayList 为例,简单分析一下ArrayList 列表的实现方式.首先,先看下它的构造函数. 下列表格是在S ...

  8. 小小知识点(二十四)什么是5G

    转自 https://www.ifanr.com/1149419 一个简单且神奇的公式 今天的故事,从一个公式开始讲起.这是一个既简单又神奇的公式.说它简单,是因为它一共只有 3 个字母.而说它神奇, ...

  9. JAVA之旅(二十四)——I/O流,字符流,FileWriter,IOException,文件续写,FileReader,小练习

    JAVA之旅(二十四)--I/O流,字符流,FileWriter,IOException,文件续写,FileReader,小练习 JAVA之旅林林总总也是写了二十多篇了,我们今天终于是接触到了I/O了 ...

随机推荐

  1. TCP/IP详解之:IP选路 动态选路协议

    第九章 IP选路 netstat -rn 显示路由表 初始化路由表的两种方法: 方法1:在配置文件中指定静态路由(不常用) 方法2:运行路由守护程序 或者 使用ICMP路由器发现报文 没有到达目的地的 ...

  2. hdu 5126 stars cdq分治套cdq分治+树状数组

    题目链接 给n个操作, 第一种是在x, y, z这个点+1. 第二种询问(x1, y1, z1). (x2, y2, z2)之间的总值. 用一次cdq分治可以将三维变两维, 两次的话就变成一维了, 然 ...

  3. Amazon MWS 上传数据 (一) 设置服务

    Amazon 上传数据的流程为: 通过 SubmitFeed 操作.加密标头和所有必需的元数据(包括 FeedType 的值在内),来提交 XML 或文本型数据文件.正如亚马逊 MWS的所有提交内容一 ...

  4. arrowTip 提示

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  5. Array 的五种迭代方法 -----every() /filter() /forEach() /map() /some()

    ES5定义了五个迭代方法,每个方法都接收两个参数:要在每一项上运行的函数和运行该函数的作用域对象(可选的),作用域对象将影响this的值.传入这些方法中的函数会接收三个参数:数组的项的值.该项在数组中 ...

  6. SQL Server 分离与附加数据库

    前期准备: 创建数据库 create database Studio     on primary     (name ='Studio',filename ='E:\Studio.mdf'),    ...

  7. QueryString和BASE64

    加号(+)是BASE64编码的一部分,而加号在QueryString中被当成是空格.因此,当一个含有BASE64编码的字符串直接作为URL的一部分时,如果其中含有加号,则使用QueryString读取 ...

  8. MFC使用Windows media player播放声音文件

    一.在需要播放声音的资源上添加控件 资源视图  . 选择添加控件的资源(如对话框).右键单击.插入ActiveX控件.调整你需要的控件属性并记录ID 二.在项目中添加播放声音的类 点击菜单中的项目.添 ...

  9. Delphi下重载窗体CreateParams翻转关闭按钮

    type  TForm1 = class(TForm)  private    { Private declarations }  public    { Public declarations }  ...

  10. ubuntu_安装aptana3

    下面记录下偶怎么安装aptana3(aptana2应该也适用). 安装java运行时,偷看这里 说明:实际上偶并没有执行这步,因为发现在安装aptana3之前 java的运行时已经安装过了. 貌似是安 ...