Java基础之(二十五)hashCode() 与 toString()

hashCode()方法

我们在程序中输入任意长度的二进制数据,通过hash算法就能产生对应的唯一的散列值。
hash算法的另一个作用是存储数据,假设我们要存储四个数据,通过hash算法,我们为每个数据产生一个不同的散列值,然后我们把散列值和数据存储的位置建立一种关联。因此,当你下次想要找到某个数据时,只需要知道hash值就知道了它存储的位置。

散列值我们可以理解为房子上的门牌号,我们想要找某个人,知道了门牌号就可以找到。

说明
说hashCode之前,先来看看Object类。

我们知道,Object类是java程序中所有类的直接或间接父类,处于类层次的最高点。在Object类里定义了很多我们常见的方法,包括我们要讲的hashCode方法,如下:

    public final native Class getClass();  
    public native int hashCode();  
    public boolean equals(Object obj) {  
      return (this == obj);  
    }   
    public String toString() {  
     return getClass().getName() + "@" +  Integer.toHexString(hashCode());  
    }  

根据这个方法的声明可知,该方法返回一个int类型的数值,也就是说你调用某个对象的hashCode()之后,会产生一个这个对象的hash码。

在java的很多类中都会重写equals和hashCode方法,如果两个对象用equals()方法进行比较,且结果相等,那么这两个对象调用hashCode()方法产生的hash码也是相等的。但是反过来说,equals()方法不相等的两个对象,hashCode()却是有可能相等的。

看下面一个例子:

class User{
    String name;
    int age;

    public User(){}
    public User(String name,int age){
        this.name = name;
        this.age = age;
    }

    public boolean equals(Object obj){
        if(this == obj){
            return true;
        }
        boolean b = obj instanceof User;
        if(b){
            User u = (User)obj;
            if(this.age == u.age
            && this.name.equals(u.name)){
            return true;
            }
            else{
                return false;
            }
        }
        else{
        return false;
        }
    }
}

下面再写一个测试类:

import java.util.*;
import java.util.HashMap;

class Test{
public static void main(String[] args){
        User u = new User("zhangsan",12);
        HashMap map = new HashMap();
        map.put(u,"abc");
        String s = map.get(new User("zhangsan",12));
        System.out.println(s);
    }
}

按理说结果应该输出为“abc”,但是实际结果却是null。

map根据传入的键(u)来寻找对应的值,它通过计算这个对象的hashCode来判断在hashMap当中有没有存在这个值。而我们并没有复写hashCode()方法,因此User对象用的是默认的hashCode方法。默认的hashCode方法对于内存中不同的对象生成的hash码是不一样的。

hashCode()的复写

那么,接下来我们就来重写hashCode()方法:

class User{
    String name;
    int age;

    public User(){}
    public User(String name,int age){
        this.name = name;
        this.age = age;
    }

    public boolean equals(Object obj){
        if(this == obj){
            return true;
        }
        boolean b = obj instanceof User;
        if(b){
            User u = (User)obj;
            if(this.age == u.age
            && this.name.equals(u.name)){
            return true;
            }
            else{
                return false;
            }
        }
        else{
        return false;
        }
    }

    public int hashCode(){
        //返回一个固定值,所有对象调用hashCode方法生成的hash码都是一样的
        return 12;
    }
}
输出结果:
abc

虽然得到了想要的结果,但是上面程序中复写的方法是不合理的。
下面这段话摘自Effective Java一书:

  • 在程序执行期间,只要equals方法的比较操作用到的信息没有被修改,那么对这同一个对象调用多次,hashCode方法必须始终如一地返回同一个整数。

  • 如果两个对象根据equals方法比较是相等的,那么调用两个对象的hashCode方法必须返回相同的整数结果。

  • 如果两个对象根据equals方法比较是不等的,则hashCode方法不一定得返回不同的整数。

    对于第二条和第三条很好理解,但是第一条,很多时候就会忽略。在《Java编程思想》一书中的P495页也有同第一条类似的一段话:
    “设计hashCode()时最重要的因素就是:无论何时,对同一个对象调用hashCode()都应该产生同样的值。如果在讲一个对象用put()添加进HashMap时产生一个hashCdoe值,而用get()取出时却产生了另一个hashCode值,那么就无法获取该对象了。所以如果你的hashCode方法依赖于对象中易变的数据,用户就要当心了,因为此数据发生变化时,hashCode()方法就会生成一个不同的散列码”。

hashCode()方法的重写和equals()方法的重写往往是一起的。因此我们可以这样重写hashCode()方法。

public int hashCode(){
        //声明一个整型数据,值随便,一般是个质数
        int result  = 17;
        //这里习惯用31这个奇素数
        result = 31*result + age;
        //name是String类型,String已经重写过hashCode(),这里生成它自己的hash码
        result = 31*result + name.hashCode();

        return result;
    }

打印对象和toString()方法

先看下面程序:

class Person
{
    private String name;
    public Person(String name)
    {
        this.name = name;
    }
    public void info()
    {
        System.out.println("此人名为:" + name);
    }
}
public class PrintObject
{
    public static void main(String[] args) 
    {
        //创建一个Person对象,将之赋给给p变量
        Person p = new Person("孙悟空");
        //打印p所引用的Person对象
        System.out.println(p);
    }
}

上面程序创建了一个Person对象,然后使用System.out.println(p)方法输出Person对象。

输出结果:
Person@24ee5d13

这个奇怪的结果是怎么来的呢?System.out.println方法只能在控制台输出字符串,,当使用该方法输出Person对象的时候,实际上是输出的Person对象的toString()方法的返回值,也就是说下面两行效果一样:

System.out.println(p);
System.out.println(p.toString());

toString()方法是Object类里的一个实例方法,因此所有对象都具有toString()方法。
不仅如此,java对象都可以和字符串进行连接运算,当java对象都可以和字符串进行连接运算时,系统自动调用java对象toString方法的返回值和字符串进行连接运算,即下面两行效果一样:

String pStr = p + " ";
String pStr = p.toString() + " ";

toString()方法是一个非常特殊的方法,它是一个“自我描述”方法,该方法通常用于实现这样的功能:当程序员直接打印该对象时,系统将会输出该对象的“自我描述”信息,用以告诉外界该对象具有的状态信息。

Object类提供的toString()方法只是返回该对象实现类的类名+@+hashCode值,这个返回值并不能真正实现“自我描述“的功能,必须重写toString方法。

class Apple
{
    private String color;
    private double weight;
    public Apple(){ }
    //提供有参数的构造器
    public Apple(String color , double weight)
    {
        this.color = color;
        this.weight = weight;
    }

    public void setColor(String color)
    {
        this.color = color;
    }
    public String getColor()
    {
         return this.color;
    }

    public void setWeight(double weight)
    {
        this.weight = weight;
    }
    public double getWeight()
    {
         return this.weight;
    }
    public String toString()
    {       
        return "一个苹果,颜色是:" + color + ",重量是:" + weight;

    }
}
public class TestToString
{
    public static void main(String[] args) 
    {
        Apple a = new Apple("红色" , 5.68);
        //打印apple对象
        System.out.println(a);
    }
}
输出结果:
一个苹果,颜色是:红色,重量是:5.68

从上面的结果可以看出,通过重写Apple类的toString方法,就可以让系统在打印Apple对象时打印出该对象的“自我描述”信息。

大部分时候,重写toString方法总是返回该对象所有令人感兴趣的信息所组成的字符串,通常可返回如下格式字符串:
类名[属性1 = 值1,属性2 = 值2,…]
因此可将上面Apple类的toString方法改写如下:

public String toString()
    {       
        return "Apple[color=" + color + ",weight=" + weight + "]";
    }

关于hashCode更多的介绍呢,可以查看以下链接进行学习:

http://www.cnblogs.com/dolphin0520/p/3681042.html

http://blog.csdn.net/jiangwei0910410003/article/details/22739953

http://www.tuicool.com/articles/fuE3Y3

你可能感兴趣的:(Java基础)