哈喽,小伙伴们大家好,我是兔哥呀,今天就让我们继续这个JavaSE成神之路!

这一节啊,咱们要学习的内容是Java所有类的父类-Object类。

Object类是Java语言中最基本的类,所有类都继承自Object类,Object类位于java.lang 包下。

Object类提供了大量的方法,这些方法可以支持Java程序操作任何对象。下面介绍Object类的一些常用方法。

本文重点介绍Object类的精华三板斧,即toString方法,hashCode方法和equals方法。这三个方法是子类重写率最高的,我们需要重点掌握。

1.toString()

toString() 方法以字符串的形式返回对象的字符串表示,一般情况下,toString()方法会返回对象的“类名@哈希码”的字符串表示,但是用户可以重写Object的toString()方法,自定义一个返回值,比如:

public class User{
    private String name;
    private int age;
 
    @Override
    public String toString(){
        return "User[name=" + name + ", age=" + age + "]";
    }
} 

当我们执行System.out.println(new User())时,user对象会自动调用toString方法。

结果是:User[name=null, age=0]

因为toString方法被重写了,所以得到这样的字符。如果不重写,结果就是:

com.company.dto.User@4554617c

为什么是这样呢,看下源码就知道啦:

public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

所谓哈希码就是当前对象hashCode值的16进制写法。

2.hashCode()

hashCode() 方法可以返回对象的哈希值,比如:

User user1 = new User("张三", 18);
User user2 = new User("张三", 18);
System.out.println(user1.hashCode() == user2.hashCode()); // 输出:false

user1和user2是不同的对象,哪怕属性相同,hash值也不同。

Java中的hashCode是Object类中定义的一种方法,用于返回一个对象的哈希码值。这个哈希码值是一个32位的整数,可以用来唯一标识对象。一般来说,不同的对象会有不同的hashCode值。

举例来说,假设有一个Person类,它有3个属性:name,age和gender。

假设有两个Person对象:Tom和Jerry,它们的属性分别是:

name:Tom,age:20,gender:Male;

name:Jerry,age:18,gender:Female。

那么,这两个对象的哈希码值就是不同的,比如Tom的哈希码值可能是abcd123,而Jerry的哈希码值可能是efgh456。

hashCode是一个native声明的本地方法,返回一个int型的整数。由于在Object中,因此每个对象都有一个默认的哈希值。你可以理解为Java程序中每个对象的身份证号。

但是,因为是算法生成的,难免是会有两个对象拥有相同的hashCode的情况,这就叫做哈希冲突。

hashCode的主要作用就是为了查找,我们之前有一次作业,回顾一下:

编写一个方法,接收一个String类型的参数,里面设置一个String类型的局部数组变量,要求每次调用该方法时,参数要均匀地分配到数组。(即实现一个简易的hash表,不考虑hash冲突问题)

我们的思路是,用一个数组,假如长度为10,每次有参数进来,我们就获取它的hashCode,然后跟10取余数。核心代码如下:

// 计算 str 的哈希值
int hash = str.hashCode();
// 计算 str 在数组中的下标
int index = hash % arr.length;
 // 将 str 分配到数组的对应位置
arr[index] = str;

如果不用hashCode,那么我们就只能通过暴力循环或者用二分法之类的去查找,效率太低了。如果用HashCode,我们只需要一个简单的%运算,就可以立马知道这个数据放在了数组的哪一个位置。

但是,之前的程序不能解决hash冲突问题。即可能有两个参数算出来的hashCode是一样的,那么就会有后面的数据覆盖之前数据的问题。HashMap的做法是在数组的每一个格子上放一个链表或红黑树,这个会在后面讲到。但就算是有链表,发生哈希冲突时,我们还是需要判断两个数据是否相等,这样才能精确地查找。

为了唯一标识一个java对象,光有hashCode方法是不够的,我们一般还需要通过equals方法。

3.equals()

equals() 方法可以比较两个对象是否相等,比如:

User user1 = new User("张三", 18);
User user2 = new User("张三", 18);
System.out.println(user1.equals(user2)); // 输出:false

user1和user2是不同的对象,哪怕属性相同,equals的结果也不同。这是因为,User对象默认继承自Object,继承了Object的equals方法,而Object的equals方法源码如下:

public boolean equals(Object obj) {
    return (this == obj);
}

原来就是直接用==比较的,这个比较的其实是user1和user2是不是同一个引用,说白了就是问他们是不是同一块内存。

这边呢,就有一个误区,有很多人,哪怕是工作了好几年的老码农都会认为==比较的就是对象的hashCode,其实不一定。

== 是java中关系运算符,比较的是变量(栈)内存中存放的对象的(堆)内存地址,用来判断两个对象的地址是否相同,即是否是指相同一个对象。比较的是真正意义上的指针操作。

hashcode:对象的初始地址的整数表示。可以理解为现实世界人的身份证号,hashcode( )作为基类Object中一个方法。

Java中的对象是JVM在管理,JVM会在她认为合适的时候对对象进行移动,比如,在某些需要整理内存碎片的GC算法下发生的GC。此时,对象的地址会变动,但hashcode不会改变。就好比现实中一个人出生在A城市,成年工作后落户到了B城市,但是身份证号是不会变的。

OpenJDK8 默认hashCode的计算方法是通过和当前线程有关的一个随机数+三个确定值,运用Marsaglia's xorshift scheme随机数算法得到的一个随机数。和对象内存地址无关

为什么很多人都觉得hashCode就是内存地址呢?这恐怕和很多培训机构教的有关,因为很多线下和线上的机构就是这么教的,具体我就不点名了。

总结一下,Object的equals方法就是用来比较两个对象的引用是否相等,对于引用类型来说,这种比较没有太大的意义。为什么我说这边比较意义不大呢?因为如果两个对象用==都能返回true,说明就是同一个对象啊,hashCode肯定也是一样的。而实际情况是,比如这个User对象,我们实际上要比对的是user1和user2对象的属性是否相同,而不是引用,所以我们一般需要重写equals方法。

重写equals方法:

public boolean equals(Object o) {
        // 判断对象引用是否相等,如果相等则表示两个对象相等
        if (this == o) return true;
        // 判断传入的对象是否为空,或者类型是否不匹配
        if (o == null || getClass() != o.getClass()) return false;
        // 将传入的对象强制转换为User类型
        User user = (User) o;
        // 判断两个对象的age和name属性是否相等
        return age == user.age && Objects.equals(name, user.name);
    }

强调一下,我们重写equals方法的时候,必须同时重写hashCode方法,这是因为当我们把这个对象作为key放到类似HashMap这样的容器中时,需要hashCode去定位数组下标。如果不同,那么不同对象hashCode大概率是不同的,就取不到存储的数据啦。

篇幅有限,关于这个后面我们专门开一篇博客来聊。这边呢,如果你已经有了后面HasMap的知识储备,就让我们思考一个问题,为什么我们hashMap的key一般都用String类型呢?

好了,卖个关子,我们继续。

重写hashCode方法:

public int hashCode() {
    return Objects.hash(name, age);
}

重新测试:

public static void main(String[] args) {
    User user1 = new User("张三", 18);
    User user2 = new User("张三", 18);
    System.out.println(user1.hashCode() == user2.hashCode());
    System.out.println(user1.equals(user2)); // 输出:true
}

得到的就是两个true啦!

最后,我们碎碎念一下,其实在实际的开发中,我们很少去重写hashCode和equals方法。

要说原因嘛,可能就是框架用的比较多,一般接触不到这个层面。

然后就是Map类的数据,我们都会用String作为key,而String类已经重写了hashCode方法和equals方法,我们一般也不会用一个非String类型去作为key值的。

4.课后练习

定义一个Cat类,包含属性:name,age,color

实现Cat类的toString()方法,返回一个字符串,格式为”Cat[name=?, age=?, color=?]”,?表示具体的name,age和color值

重写Cat类的hashCode()方法,利用name,age和color的值计算得出hash值

重写Cat类的equals()方法,判断两个Cat对象的name,age和color属性是否相同

 

posted on 2023-03-10 13:16  剽悍一小兔  阅读(21)  评论(0编辑  收藏  举报  来源