哈喽,小伙伴们大家好,我是兔哥呀,今天就让我们继续这个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是不是同一个引用,说白了就是问他们是不是同一块内存。
这边呢,就有一个误区,有很多人,哪怕是工作了好几年的老码农都会认为==比较的就是对象的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属性是否相同