Java核心卷一之Object类详解

本文详细探讨了Java中对象的equals方法和hashCode方法。equals默认比较对象引用,可重写以比较对象内容;hashCode返回对象的散列码,常用于哈希表。此外,还强调了equals与hashCode的一致性原则,并介绍了toString方法的作用。文章提醒开发者重视基础知识,理解并正确实现这些基础方法的重要性。
摘要由CSDN通过智能技术生成
static int hash ( Object . .. objects )
返回一个散列码 由提供的所有对象的散列码组合而得到
static int hashCode ( Object a )
如果 a null 返回 0 否则返回 a . hashCode ( )
0

Java的基础非常重要,在面试中也会被经常问到,但是本人之前却总是去关注一些框架的技术,反而忽视了基础的重要性,因此读一下Java核心卷一并把自己学到的记录在此,希望可以把Java的基础打牢固。

Object所有类的超类

Object类概述

Object 类是 Java 中所有类的始祖 Java 中每个类都是由它扩展而来的 但是并不需
要这样写 : 不需要显示继承
public class Employee extends Object
如果没有明确地指出超类 Object 就被认为是这个类的超类 由于在 Java 每个类都
是由 Object 类扩展而来的 所以 熟悉这个类提供的所有服务十分重要

可以使用 Object 类型的变量引用任何类型的对象
Object obj = new EmployeeC'Harry Hacker", 35000);
当然 Object 类型的变量只能用于作为各种值的通用持有者 要想对其中的内容进行具体的
操作 还需要清楚对象的原始类型 并进行相应的类型转换
Employee e = (Employee) obj ;
所有的数组类塱 不管是对象数组还是基本类型的数组都扩展了 Object
Employee[] staff = new Employee[10];
obj = staff; // OK
obj = new int[10]; // OK

Object类中的方法

Object 中有几个只在处理线程 时才会被调用的方法)这里不提及,以后写到线程会写到

equals 方法

  public boolean equals(Object obj) {
        return (this == obj);
    }
Object 类中的 equals 方法用于检测一个对象是否等于另外一个对象 Object 类中
个方法将判断两个对象是否具有相同的引用 如果两个对象具有相同的引用 它们一定是相
等的 从这点上看 将其作为默认操作也是合乎情理的 然而 对于多数类来说 这种判断
并没有什么意义 例如 采用这种方式比较两个 PrintStream 对象是否相等就完全没有意义
然而 经常需要检测两个对象状态的相等性 如果两个对象的状态相等 就认为这两个对象
是相等的

 

1.equals()的所属以及内部原理(即Object中equals方法的实现原理)
说起equals方法,我们都知道是超类Object中的一个基本方法,用于检测一个对象是否与另外一个对象相等。而在Object类中这个方法实际上是判断两个对象是否具有相同的引用,如果有,它们就一定相等

实际上我们知道所有的对象都拥有标识(内存地址)和状态(数据),同时“==”比较两个对象的的内存地址,所以说 Object 的 equals() 方法是比较两个对象的内存地址是否相等、

2.equals()与‘==’的区别

或许这是我们面试时更容易碰到的问题”equals方法与‘==’运算符有什么区别?“,并且常常我们都会胸有成竹地回答:“equals比较的是对象的内容,而‘==’比较的是对象的地址。”。但是从前面我们可以知道equals方法在Object中的实现也是间接使用了‘==’运算符进行比较的,所以从严格意义上来说,我们前面的回答并不完全正确。

 public static void main(String[] args) {
        User u = new User("张三");
        User u1 = new User("张三");
        System.out.println(u.equals(u1));
        System.out.println(u == u1);
    }

 

都是false,对于‘==’运算符比较两个User对象,返回了false,这点我们很容易明白,毕竟它们比较的是内存地址,而u与u1是两个不同的对象,所以内存地址自然也不一样。现在的问题是,我们希望name相同的情况下就认为这两个相等,但是运行的结果是尽管name相同,但equals的结果却反回了false。当然对于equals返回了false,我们也是心知肚明的,因为equal来自Object超类,访问修饰符为public,而我们并没有重写equal方法,故调用的必然是Object超类的原始方equals方法,根据前面分析我们也知道该原始equal方法内部实现使用的是’==’运算符,所以返回了false。因此为了达到我们的期望值,我们必须重写Userr的equal方法,让其比较的是对象的批次(即对象的内容),而不是比较内存地址,于是修改如下:

 @Override
    public boolean equals(Object obj) {
        if (obj instanceof User) {
            User u = (User) obj;
            // 判断是否内容
            return name == u.name;
        }
        return false;
    }

 instanceof 严格来说是Java中的一个双目运算符,用来测试一个对象是否为一个类的实例,用法为:boolean result = obj instanceof Class

其中 obj 为一个对象,Class 表示一个类或者一个接口,当 obj 为 Class 的对象,或者是其直接或间接子类,或者是其接口的实现类,结果result 都返回 true,否则返回false。

  注意:编译器会检查 obj 是否能转换成右边的class类型,如果不能转换则直接报错,如果不能确定类型,则通过编译,具体看运行时定。

重新运行

 因为前面的面试题我们应该这样回答更佳


总结:默认情况下也就是从超类Object继承而来的equals方法与‘==’是完全等价的,比较的都是对象的内存地址,但我们可以重写equals方法,使其按照我们的需求的方式进行比较,如String类重写了equals方法,使其比较的是字符的序列,而不再是内存地址。

3.equals()的重写规则

前面我们已经知道如何去重写equals方法来实现我们自己的需求了,但是我们在重写equals方法时,还是需要注意如下几点规则的。Java 语言规范要求 equals 方法具有下面的特性

  • 自反性。对于任何非null的引用值x,x.equals(x)应返回true。

  • 对称性。对于任何非null的引用值x与y,当且仅当:y.equals(x)返回true时,x.equals(y)才返回true。

  • 传递性。对于任何非null的引用值x、y与z,如果y.equals(x)返回true,y.equals(z)返回true,那么x.equals(z)也应返回true。

  • 一致性。对于任何非null的引用值x与y,假设对象上equals比较中的信息没有被修改,则多次调用x.equals(y)始终返回true或者始终返回false。

  • 对于任何非空引用值x,x.equal(null)应返回false。

当然在通常情况下,如果只是进行同一个类两个对象的相等比较,一般都可以满足以上5点要求

但是如果是子类与父类混合比较,那么情况就不太简单了

在前面的例子中 如果发现类不匹配 equals 方法就返冋 false
: 但是 许多程序员
却喜欢使用 instanceof 进行检测:
if ( KotherObject instanceof Employee ) ) return false;
这样做不但没有解决 otherObject 是子类的情况 并且还有可能会招致一些麻烦 。这就是建议
不要使用这种处理方式的原因所在

然而 就对称性来说 当参数不属于同一个类的时候需要仔细地思考一下 请看下面这
个调用
e . equals ( in )

这里的 e 是一个 Employee 对象 m 是一个 Manager 对象 并且两个对象具有相同的姓名、
薪水和雇佣日期。 如果在 Employee . equals 中用 instanceof 进行检测 则返回 true , . 然而这意
味着反过来调用:
m .equals ( e)
也需要返回 true 对称性不允许这个方法调用返回 false 或者抛出异常:

.重写equals()中getClass与instanceof的区别

虽然前面我们都在使用instanceof(当然前面我们是根据需求(批次相同即相等)而使用instanceof的),但是在重写equals() 方法时,一般都是推荐使用 getClass 来进行类型判断(除非所有的子类有统一的语义才使用instanceof),不是使用 instanceof。我们都知道 instanceof 的作用是判断其左边对象是否为其右边类的实例,返回 boolean 类型的数据。可以用来判断继承中的子类的实例是否为父类的实现。

下面给出编写一个完美的 equals 方法的建议

1 ) 显式参数命名为 otherObject , 稍后需要将它转换成另一个叫做 other 的变量
2 ) 检测 this otherObject 是否引用同一个对象
if ( this = otherObject ) return true ;

这条语句只是一个优化 实际上 这是一种经常采用的形式 因为计算这个等式要比一
个一个地比较类中的域所付出的代价小得多
3 ) 检测 otherObject 是否为 null , 如 果 为 null , 返 回 false 这项检测是很必要的
if ( otherObject = null ) return false ;
4 ) 比较 this otherObject 是否属于同一个类 如果 equals 的语义在每个子类中有所改
就使用 getClass 检测
if ( getClass ( ) ! = otherObject . getCIass() ) return false ;

如果所有的子类都拥有统一的语义就使用 instanceof 检测

if (!(otherObject instanceof ClassName)) return false;

5 ) otherObject 转换为相应的类类型变量
ClassName other = ( ClassName ) otherObject

6 ) 现在开始对所有需要比较的域进行比较了 使用 = 比较基本类型域 使用 equals
较对象域 如果所有的域都匹配 就返回 true ; 否 则 返 回 false
return fieldl == other.field && Objects.equa1s ( fie1d 2 , other . field 2 )
如果在子类中重新定义 equals , 就要在其中包含调用 super . equals ( other

hashCode 方法

散列码 hash code ) 是由对象导出的一个整型值 散列码是没有规律的 如果 x y
两个不同的对象 x . hashCode ( ) y . hashCode ( ) 基本上不会相同 在表 5 - 1 中列出 T 几个通
过调用 String 类的 hashCode 方法得到的散列码
由于 hashCode 方法定义在 Object 类中 因此每个对象都有一个默认的散列码 其值为
对象的存储地址 来看下面这个例子

 

5 - 2 列出了结果

 

请注意 字符串 s t 拥有相同的散列码 这是因为字符串的散列码是由内容导出
而字符串缓冲 sb tb 却有着不同的散列码 这是因为在 StringBuffer 类中没有定义
hashCode 方法 它的散列码是由 Object 类的默认 hashCode 方法导出的对象存储地址
Equals hashCode 的定义必须一致 如果 x . equals ( y ) 返回 true , 那么 x . hashCode ( ) 就必
须与 y . hashCode ( ) 具有相同的值 例如 如果用定义的 Employee . equals 比较雇员的 ID
hashCode 方法就需要散列 ID 而不是雇员的姓名或存储地址

int hashCode ( )
返回对象的散列码 散列码可以是任意的整数 包括正数或负数 两个相等的对象要
求返回相等的散列码
static int hash ( Object . .. objects )
返回一个散列码 由提供的所有对象的散列码组合而得到
static int hashCode ( Object a )
如果 a null 返回 0 否则返回 a . hashCode ( )
static int hashCode ( ( int 11 ong | short | byte | double | f 1 oat | char | boolean ) value ) 8
返回给定值的散列码

toString 方法

Object 中还有一个重要的方法 就是 toString 方法 它用于返回表示对象值的字符
下面是一个典型的例子 Point 类的 toString 方法将返回下面这样的字符串
java . awt . Point [ x = 10 ,y= 20 ]
绝大多数 但不是全部 toString 方法都遵循这样的格式 类的名字 ,随后是一对方括
号括起来的域值 下面是 Employee 类中的 toString 方法的实现
实际上 还可以设计得更好一些 最好通过调用 getClaSS ( ) . getName ( ) 获得类名的字符
而不要将类名硬加到 toString 方法中

 

 

toString 方法也可以供子类调用
当然 设计子类的程序员也应该定义自己的 toString 方法
并将子类域的描述添加进去。 如果超类使用了 getClass ( ) . getName ( ) , 那么子类只要调用 super
. toString ( ) 就可以了 例如, 下面是 Manager 类中的 toString 方法

 

现在 Manager 对象将打印输出如下所示的内容
随处可见 toString 方法的主要原因是 只要对象与一个字符串通过操作符 + 连接起
Java 编译就会自动地调用 toString 方法 以便获得这个对象的字符串描述 例如

 

在调用 x . toString ( ) 的地方可以用 " " + x 替代 这条语句将一个空串与 x 的字符串
表示相连接 这里的 x 就是 x . toString ( ) toString 不同的是 如果 x 是基本类型
条语句照样能够执行

Object 类定义了 toString 方法用来打印输出对象所属的类名和散列码例如, 调用

System . out.println ( System.out)  将输出下列内容:java . io . Pri ntStream @ 2 f 6684
之所以得到这样的结果是因为 PrintStream 类的设计者没有覆盖 toString 方法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值