查看原文
其他

Object类及其方法

树莓蛋黄派 Java规途 2023-07-04
  • Object:所有类的超类

    • Object类型的变量

    • equals方法

    • 相等测试与继承

    • hashcode方法

    • toString方法

  • 往期文章回顾

Object类及其方法

Object:所有类的超类

object

Object类是Java中所有类的超类,虽然我们不必指明某类继承Object类(即public class Employee extends Object),可以说Object类一切类的祖先。

Object类型的变量

众所周知,面向对象具有三大特性:「封装、继承、多态。」

封装

「封装」即隐藏数据的具体实现,使用时并不需要知道底层。

继承

「继承」即通过扩展父类,使该类既具有父类的基本特性,也有子类的特性(可以增加自己的新特性)。

多态

「多态」即同一个对象有不同的状态(面对不同的情景),简单来说就是「替换原则」

例如可以使用Object的对象变量引用任何类型的对象:

Object object=new Employee(“Harry Hacker”,35000);

容器

当然这个object变量只能作为一个存储任何值的「泛型容器」(即任何类都可以向上转型成为object)

如果需要使用该对象的「具体行为」,需要将其转换成确切的类型。

Java中,只有「基本类型」不是对象,例如数值、字符和布尔类型的值都不是对象

对象

「所有的数组类型,不论是对象数组还是基本类型的数组都是对象,都扩展了Object类。」

Employee[] staff=new Employee[10];
 Object obj=staff; //OK
Object obj=new int[10];//OK

equals方法

equals方法是Object类的一个方法,作用是检测一个对象是否等于另一个对象。

并且Object的默认equals方法是比较两个对象的引用是否相等。(这种默认行为本质上是合理的)那么什么是对象的引用呢?

简单来说就是定义一个变量,使其指向一个对象,那个变量就是对象引用。

如果两个对象的引用都相等,那么两个对象肯定相等。

但是现实是我们总是需要基于「特定的情况」来比较对象的相等性。

比如在一个公司中,比较员工对象是否相等,只需要比较数值是否相等即可,

此时就需要重写Object类的equals方法,来提供我们需要的实现。

public class Employee{
    .........
     @Override
   public boolean equals(Object otherObject){
        //比较当前传入对象的引用是否相等
        if(this==otherObject) return true;
        //若传入的对象为空,则立即返回false
        if(otherObject==nullreturn false;
        //比较传入的对象与本例中的对象是否属于同一个类
        if(getClass()!=otherObject.getclass()) return false;
        //将other强制类型转换成Employee类
        Employee other=(Employee)otherObject;
        //最后返回比较的结果(进行数值上的比较)
        return name.equals(other.name) && salary==other.salary && hireDay.equals(other.hireDay);
        //第二个版本
        return Objects.equals(name,other.name)&& salary==other.salary && Objects.equals(hireeDay,other.hireDay)
    }
    
}

需要注意的是getClass()返回一个对象所属的类,只有两个对象属于同一个类,才有可能相等。

Obejcts的equals方法用来处理name或者hireDay都为null的情况。

  • 若两者均为null,则返回true
  • 若其中一个为null,则返回false
  • 若两个参数都不为null,则调用a.equals(b)

子类中重写的父类equals方法时,会首先调用超类equals方法,若检测失败,对象则不相等。若超类的字段都相等,那么比较子类的实例字段。

public class Manager extends Employee{
    .......
        @Override
        //重写Employee类的equals方法
        public boolean equals(Object otherObject){
        //if比较的是当前类对象与传入的对象是否属于同一个类
        if(!super.equals(otherObject))return false;
        Manager other=(Manager)otherObject;
        return bonus==other.bonus;
    }
}

相等测试与继承

根据Java语言规范要求equals方法必须要有以下特性;

  1. 自反性:对于任何非空的引用X,X.equals(X)应该返回true。
  2. 对称性:对于任何的引用X、Y,需要满足当且仅当Y.equals(X)返回true时,X.equals(Y)也应该返回true。
  3. 传递性:对于任何引用X、Y、Z,如果X.equals(Y)返回true,Y.equals(Z)返回true,X.equals(Z)也应该返回true。
  4. 一致性:如果X、Y的引用没有变化,那么反复调用X.equals(Y)应该返回相同的结果。
  5. 对于任意非空引用X,X.equals(null)应该返回false。

需要注意的是instanceof这个关键词,比如在比较两个对象是否相等时用instanceof关键词会得到奇怪的结果

Employee  e =new Employee();
Manager m=new Manager();
    e.equals(m)//返回true
    m.equals(e)//返回false

以上来看,用instanceof关键词是不合理的,它违背了对称性的原则。

现在来看,有两种完全不同的概念:

  • 若子类有自己的相等性概念,那么对称性需求将强制使用getClass()检测
  • 若完全由超类来决定相等性概念,那么可以使用instanceof关键词进行检测,可以在不同子类的对象之间进行相等性比较。
建议

下面是编写一个完美的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的语义可以在子类中被改变(子类重写equals)就使用getClass()检测,if(!getClass()==otherObject.getClass()) return false
  5. 如果所有的子类都有相同的相等性语义,可以使用instanceof检测if(!(otherObject instanceof ClassName))return false
  6. 将otherObject强制转换成相应类类型的变量:ClassName other=(ClassName)otherObject
  7. 再根据相等性概念来比较字段,基本类型字段用**==**,使用Obejcts.equals()比较对象字段,若所有字段都匹配,返回ttrue,否则,返回false。若子类重写了equals方法,就在其中包含一个super.equals(other)的调用。

hashcode方法

散列

hashcode即「散列码」,本身其实并没有任何规律,只是对象导出的一个「整型值」

如果X,Y是两个不同的对象,那么他们的散列值一般不会相同。

由于hashcode方法是定义在Object类中的,因此每个对象都有一个默认的散列值,其值由对象的存储地址得出

来看个例子:

这里可以发现一个很有趣的事,s和t具有相同的散列码,tb和sb虽然载内容上是相同的,但散列码却不同。

重定义

原因分析:

  1. 字符串s和t的散列码是由「内容」导出的,这是由于String类重写了hashcode方法,此时不再依据对象地址来获得散列码,而是通过对象内容,如果对象内容相同,则散列码相同,反之,则不同。(尽管这两个对象地址是不同的)
  2. 字符串构建器StringBuilder类的对象则是依据「对象地址」来确定散列码的。出现内容相同但散列码不同的情况是因为:StringBuilder类没有重写Object类的hashcode方法,是依据对象地址来确定散列值,但由于通过对象构造器的方式会在堆内存中划出不同的空间,于是内容相同的两者会有不同的内存地址,也就会有不同的散列码。

如果重新定义了equals方法,就必须为用户可能插入散列表的对象重新定义hashcode方法

hashcode方法应该返回一个整数(可以为负),**应该要合理地组织实例字段的散列码,以便能够让不同的对象产生的散列码分布更加均匀。**以下是Employee的hashcode方法。

public class Employee{
    ......
        public int hashcode(){
        return 7*Objects.hashcode(name)+11*Double.hashcode(salary)+13*Obejcts.hashcode(hireDay);
            //使用Objects.hashcode()方法来保证安全性:
            //1.若参数为null,则返回0
            //2.若参数不为null,则返回参数调用hashcode的结果。
            //3.Double.hashcode()避免创建Double对象。
    }
}

其实还有一种更简单的重写方法:使用Objects.hash(),

public int hashcode(){
    return Objects.hash(name,salary,hireDay);
        //此方法会对各个参数调用Objects.hashcode(),并组合这些散列值。
}      

此外还有非常重要的一点:equals()方法必须与hashcode()方法兼容」,意思是若X.equals(Y)返回true,那么X.hashcode()的值必须与Y.hashcode()的值相同。

toString方法

转换

toString方法顾名思义就是将某个对象转成String类型的对象。它会返回「对象值的一个字符串。」

绝大多数的toString方法都遵循这样的格式:类的名字,随后是一对方括号括起来的字段值

以下是Employee类的一个例子:

public String toString(){
    return getClass().getName()="[name="+name+",salary="+salary+",hireDay="+hireDay+"]";
    //通过调用getClass().getName()方法来获得类名的字符串,这样的方法也可以由子类调用。
}

当设计子类时,也必须要重写toString方法,并加入子类自己的字段。如果超类使用了getClass().getName()方法,那么子类只需要调用super.toString()即可。「调用父类的toString()方法,方法中的getClass()返回子类类型,并以字符串的形式返回子类类名。」

例如子类的重写方法

public  class Manager extends Employee{
    .........
        public String toString(){
        return super.toString()+"[bonus="+bonus+"]";
        
        //将其打印输出会产生如下内容
        /*
        Manager[name=...,salary=....,hireDay=....][bonus=......]
        */

    }

toString方法比较常见是因为:只要将对象与一个字符串通过操作符“+”连接起来,Java编译器会自动调用toString方法来获得这个对象的字符串描述。

注意:可以不写X.toString()而写“ ”+X,虽然功能实现的功能相同,但有一些细节需要知道:「X.toString()中的X必须是一个对象,其他类型则不行,但“ ”+X中的X可以是对象,也可以是基本数据类型。」

如果X是一个对象,那么System.out.println(X)会简单地调用X.toString()方法,并打印输出得到的字符串,具体内容是:对象的类名加上散列码。(此时并未覆盖Object类的toString()方法)

toString方法的优点:

  1. 便利用户可以获得有关对象状态的有用信息。
  2. 是一个重要的调式工具。
  3. 使得对象的内容更加简洁明了。

综上所述:本质上这三个Object方法都需要重写,使得内容更加清晰。

往期文章回顾

Java基本程序设计结构——数据类型

变量+运算=?

字符串详解

输入输出与流程

数组与大数

类初识

自定义类与时间类

方法参数与对象构造

包、注释及JAR文件

继承及其子类



您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存