聊聊所有类的祖宗-Object类(精华三板斧)

简介: 聊聊所有类的祖宗-Object类(精华三板斧)

哈喽,小伙伴们大家好,我是兔哥呀,今天就让我们继续这个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属性是否相同


相关文章
|
23天前
|
存储 Java 程序员
Java基础的灵魂——Object类方法详解(社招面试不踩坑)
本文介绍了Java中`Object`类的几个重要方法,包括`toString`、`equals`、`hashCode`、`finalize`、`clone`、`getClass`、`notify`和`wait`。这些方法是面试中的常考点,掌握它们有助于理解Java对象的行为和实现多线程编程。作者通过具体示例和应用场景,详细解析了每个方法的作用和重写技巧,帮助读者更好地应对面试和技术开发。
76 4
|
2月前
|
Java
Java Object 类详解
在 Java 中,`Object` 类是所有类的根类,每个 Java 类都直接或间接继承自 `Object`。作为所有类的超类,`Object` 定义了若干基本方法,如 `equals`、`hashCode`、`toString` 等,这些方法在所有对象中均可使用。通过重写这些方法,可以实现基于内容的比较、生成有意义的字符串表示以及确保哈希码的一致性。此外,`Object` 还提供了 `clone`、`getClass`、`notify`、`notifyAll` 和 `wait` 等方法,支持对象克隆、反射机制及线程同步。理解和重写这些方法有助于提升 Java 代码的可读性和可维护性。
|
4月前
|
Java
【Java基础面试二十】、介绍一下Object类中的方法
这篇文章介绍了Java中Object类的常用方法,包括`getClass()`、`equals()`、`hashCode()`、`toString()`、`wait()`、`notify()`、`notifyAll()`和`clone()`,并提到了不推荐使用的`finalize()`方法。
【Java基础面试二十】、介绍一下Object类中的方法
|
3月前
|
Python
类与面向对象编程(Object-Oriented Programming, OOP)
类与面向对象编程(Object-Oriented Programming, OOP)
|
4月前
|
前端开发 Java 编译器
【前端学java】java中的Object类和前端中的Object有什么区别(9)
【8月更文挑战第10天】java中的Object类和前端中的Object有什么区别
45 0
【前端学java】java中的Object类和前端中的Object有什么区别(9)
|
4月前
|
算法 Java
12 Java常用类(一)(内部类+object类+包装类)
12 Java常用类(一)(内部类+object类+包装类)
38 5
|
5月前
|
Java
Java中的Object类 ( 详解toString方法 | equals方法 )
Java中的Object类 ( 详解toString方法 | equals方法 )
|
6月前
|
Java
【Java】Object类简单解析
【Java】Object类简单解析
65 1
|
6月前
|
前端开发 JavaScript Java
Java基础10-深入理解Class类和Object类(二)
Java基础10-深入理解Class类和Object类(二)
49 5
|
6月前
|
Java C++
Java基础10-深入理解Class类和Object类(一)
Java基础10-深入理解Class类和Object类(一)
77 4