Java:不得不知的Object类

2024-01-08 03:32
文章标签 java object 不知 不得

本文主要是介绍Java:不得不知的Object类,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 一、equals
    • 1、equals与==有啥区别?
    • 2、equals方法的规范
    • 3、instanceof 和getClass()
    • 4、其他总结
  • 二、hashCode
    • 1、hashCode的规范
    • 2、String类的hashCode实现
  • 三、toString
    • 1、打印对象信息
    • 2、论优雅打印数组
    • 3、自定义toString方法
  • 五、其他重要方法

Class Object is the root of the class hierarchy.

Object类是所有类的顶级父类,任何一个对象(除了基本类型)都实现了Object类的方法,包括数组。

一、equals

public boolean equals(Object obj)

1、equals与==有啥区别?

我们通常会见到一类问题:==equals有啥区别?每次见到这种问题,都有一种模棱两可的感觉,这次完完整整地总结一波:

从Object类中的equals方法作为切入点,我们看看它的源码:

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

事实上,在Object类中,a.equals(b)等价于a==b,即判断两个对象是否具有相同的引用。

==对于基本数据类型,判断数值是否相等,对于引用数据类型,判断地址值是否相等,也就是是否具有相同的引用。

我们发现,如果两个对象具有相同引用,则equals结果相等。但大多数情况下,这样的判断形式没啥意义,在实际情况下,我们往往需要用equals检测对象状态、属性的相等性,往往会在类中重写equals方法。

我们以String来举例,看看String类中的源码:

    //重写注意形参类型必须是Objectpublic boolean equals(Object anObject) {//引用相同,必然返回trueif (this == anObject) {return true;}//判断anObject类型是否和String相同if (anObject instanceof String) {//anObject向下转型String anotherString = (String)anObject;int n = value.length;//每个位置上字符逐一比较if (n == anotherString.value.length) {char v1[] = value;char v2[] = anotherString.value;int i = 0;while (n-- != 0) {if (v1[i] != v2[i])return false;i++;}return true;}}return false;}

我们可以发现,如果是两个字符串对象,除了判断是否同一引用,还比较字符串值是否相等(至于字符串的地址相关问题,我们之后再做总结)。同样的例子还有Integer等包装类,如果感兴趣,可以查看源码。

2、equals方法的规范

equals方法实现了非空对象引用的等价关系

  1. 自反性(reflexive):对于任何非空引用x,x.equals(x)为true。
  2. 对称性(symmetric):对于任何非空的引用x和y,当且仅当y.equals(x)返回true时,x.equals(y)返回true。
  3. 传递性(transitive):对于任何非空引用x、y和z,如果x.equals(y)返回true,而y.equals(z)返回true,那么x.equals(z)也应该返回true。
  4. 一致性(consistent):如果非空引用x和y的对象没有变化,反复调用x.equals(y)返回相同的结果。
  5. 对于任何非空引用x,x.equals(null)应该返回false。

3、instanceof 和getClass()

如果equals方法判断双方属于同一类,按照上面的规则编写代码其实比较轻松。但是,如果双方不同类,则关于对称性,就需要考虑用哪种方式判定。

我们通过小测试看看instanceofgetClass()的区别:

public class EqualsDemo {public static void main(String[] args) {Super aSuper = new Super();Sub sub = new Sub();System.out.println(sub.getClass() == aSuper.getClass());//falseSystem.out.println(sub instanceof Super);//true}
}
class Super{}
class Sub extends Super{}

可以发现,getClass判断时,子类与父类类型严格不同;而instanceof意味着父类的概念适用于所有子类,相同类。

对于一个要编写equals的类而言:

  • 如果equals的语义在子类中有所改变,则应使用getClass检测。

  • 如果所有的子类都拥有同一的语义,就使用instanceof检测。

4、其他总结

  • 重写equals方法时,注意形参类型一定是Object
  • 数组类型的域可以使用Arrays工具类的静态方法static boolean equals(type[] a,type[] b)判断是否相等。
    int[] a = {1,2,3};int[] b = {1,2,3};System.out.println(Arrays.equals(a, b));//true

二、hashCode

public native int hashCode();

Object底层的hashCode方法是native修饰的,不是Java语言编写的,我们要知道:

这个方法将会返回对象的哈希码值,哈希码是整型且没有规律的,也叫做散列码。

我们之前进行过基于JDK1.8的HashMap源码分析,了解到通过哈希函数将键值转化为整型的哈希值,然后通过巧妙的操作,将其映射到数组的各个索引上,利用数组查询快的优势,大大提升了性能。

1、hashCode的规范

  1. 当equals方法被重写时,应该重写hashCode方法,从而保证两个相等的对象拥有相同的哈希码
  2. 程序执行过程中,如果对象的数据没有被修改,则多次调用hashCode方法将返回相同的整数。
  3. 两个不相等的对象可能具有相同的哈希码,但在实现hashCode方法时应避免太多这样的情况出现。

2、String类的hashCode实现

hash函数非常多样,我们以常见的String类的hashCode实现举例:

    public int hashCode() {int h = hash;if (h == 0 && value.length > 0) {char val[] = value;for (int i = 0; i < value.length; i++) {h = 31 * h + val[i];}hash = h;}return h;}
  • 字符串底层是由字符数组组成,当我们传入"abc"时,用val[]这个字符数组接收[‘a’,‘b’,‘c’]。
  • 然后将字符转化为int,也就是[97,98,99]。
  • ((97x31)+98)x31+99 = 96354

三、toString

public String toString()

该方法返回对象的字符串表现形式,结果应该是简洁但信息丰富的表示,便于阅读。建议所有子类都重写此方法。

如果不重写的话,Object类中定义的形式会让人很不爽,以下是toString()源码:

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

返回字符串 = 对象对应运行时类的名称+“@”+对象哈希码的无符号十六进制表示形式

1、打印对象信息

我们可以试一试:

    public static void main(String[] args) {Super s = new Super();System.out.println(s);}//输出结果com.my.objectClass.equals.Super@677327b6

System.out.println(s);意思是标准输出s到打印台上,我们看看具体执行的步骤:

    public void println(Object x) {//首先调用String类的valueOf方法,获得s的字符串表现形式String s = String.valueOf(x);synchronized (this) {//打印print(s);//换行newLine();}}

我们继续看看这个valueOf是怎么一回事:

    public static String valueOf(Object obj) {//如果为null,则输出"null",非空则调用toString()return (obj == null) ? "null" : obj.toString();}

至此,我们可以知道,我们在试图打印对象的时候,都会调用对象的toString方法,我们可以试着重写该方法,则测试的时候能够快速清晰地定位。

2、论优雅打印数组

数组也是一种特殊的类型,我们通过打印可以发现,他的结果比较离谱,看上去比较奇怪,具体规则可以查看官方文档Class类的getName()方法。

    int[] arr = {1,2,3};System.out.println(arr);//[I@14ae5a5

总之,我们总是希望我们打印出来的是,数组中的数整整齐齐排列着的,对吧。

我们可以利用Arrays工具类的静态方法toString方法,优雅地打印数组:

    public static String toString(int[] a) {if (a == null)return "null";int iMax = a.length - 1;if (iMax == -1)return "[]";StringBuilder b = new StringBuilder();b.append('[');for (int i = 0; ; i++) {b.append(a[i]);if (i == iMax)return b.append(']').toString();b.append(", ");}}

3、自定义toString方法

既然是自定义,那么就没啥严格规定,为了测试数据的时候更加清晰,可以想想适合自己的打印信息的方法。

现在的IDE一般都是支持根据字段,自动生成toString方法的,比如我是用的IDEA,按住AltIns键就可以快速生成。

    
public class EqualsDemo {public static void main(String[] args) {System.out.println(new Person());}
}
class Person{String name = "天乔巴夏丶";int age = 18;@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +'}';}
}//测试结果
Person{name='天乔巴夏丶', age=18}

五、其他重要方法

至此,已经总结了Object中三个比较重要的方法,其他的诸如

  • 并发编程相关的wait()、notify()、notifyAll()等方法
  • 用于垃圾回收终结的finalize()方法
  • 用于创建对象副本的clone()方法
  • 用于获取对象运行时类信息getClass()的方法

这些方法,我们之后再做总结。


参考资料:《Java核心技术卷Ⅰ》

这篇关于Java:不得不知的Object类的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/582194

相关文章

java如何解压zip压缩包

《java如何解压zip压缩包》:本文主要介绍java如何解压zip压缩包问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Java解压zip压缩包实例代码结果如下总结java解压zip压缩包坐在旁边的小伙伴问我怎么用 java 将服务器上的压缩文件解压出来,

SpringBoot中SM2公钥加密、私钥解密的实现示例详解

《SpringBoot中SM2公钥加密、私钥解密的实现示例详解》本文介绍了如何在SpringBoot项目中实现SM2公钥加密和私钥解密的功能,通过使用Hutool库和BouncyCastle依赖,简化... 目录一、前言1、加密信息(示例)2、加密结果(示例)二、实现代码1、yml文件配置2、创建SM2工具

Spring WebFlux 与 WebClient 使用指南及最佳实践

《SpringWebFlux与WebClient使用指南及最佳实践》WebClient是SpringWebFlux模块提供的非阻塞、响应式HTTP客户端,基于ProjectReactor实现,... 目录Spring WebFlux 与 WebClient 使用指南1. WebClient 概述2. 核心依

Spring Boot @RestControllerAdvice全局异常处理最佳实践

《SpringBoot@RestControllerAdvice全局异常处理最佳实践》本文详解SpringBoot中通过@RestControllerAdvice实现全局异常处理,强调代码复用、统... 目录前言一、为什么要使用全局异常处理?二、核心注解解析1. @RestControllerAdvice2

Spring IoC 容器的使用详解(最新整理)

《SpringIoC容器的使用详解(最新整理)》文章介绍了Spring框架中的应用分层思想与IoC容器原理,通过分层解耦业务逻辑、数据访问等模块,IoC容器利用@Component注解管理Bean... 目录1. 应用分层2. IoC 的介绍3. IoC 容器的使用3.1. bean 的存储3.2. 方法注

Spring事务传播机制最佳实践

《Spring事务传播机制最佳实践》Spring的事务传播机制为我们提供了优雅的解决方案,本文将带您深入理解这一机制,掌握不同场景下的最佳实践,感兴趣的朋友一起看看吧... 目录1. 什么是事务传播行为2. Spring支持的七种事务传播行为2.1 REQUIRED(默认)2.2 SUPPORTS2

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.

Java进程异常故障定位及排查过程

《Java进程异常故障定位及排查过程》:本文主要介绍Java进程异常故障定位及排查过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、故障发现与初步判断1. 监控系统告警2. 日志初步分析二、核心排查工具与步骤1. 进程状态检查2. CPU 飙升问题3. 内存

java中新生代和老生代的关系说明

《java中新生代和老生代的关系说明》:本文主要介绍java中新生代和老生代的关系说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、内存区域划分新生代老年代二、对象生命周期与晋升流程三、新生代与老年代的协作机制1. 跨代引用处理2. 动态年龄判定3. 空间分

Java设计模式---迭代器模式(Iterator)解读

《Java设计模式---迭代器模式(Iterator)解读》:本文主要介绍Java设计模式---迭代器模式(Iterator),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,... 目录1、迭代器(Iterator)1.1、结构1.2、常用方法1.3、本质1、解耦集合与遍历逻辑2、统一