全栈开发那些事

全栈开发那些事

Object类

8
2024-06-25

1、Object类

java.lang.Object类是类层次结构的根类,每个类(除了Object类本身)都使用Object类作为超类。一个类如果没有显示声明继承另一个类,则相当于默认继承了Object类。换句话说,Object类的变量可以接收任意类型的对象。Java规定Object[]可以接收任意类型对象的数组,但是不能接收基本数据类型的数组。

Object类只有一个空参构造器,所有类的对象创建最终都会通过super()语句调用到Object类的无参构造器中。如果一个类没有显示继承另一个类,那么在它的构造器中出现的super()语句表示调用的就是Object类的无参构造器。

Object类是其他类的根父类,因此Object类的所有方法都会继承到子类中,包括数组对象,Object类的主要方法如下所示。

image-20220920172721468

1.1 toString方法

源码如下:

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

toString方法的作用是返回对象的字符串形式,也就是任意类型对象想转换成String类型,都可以调用toString方法。toString方法的原型返回的是一个类似地址值的字符串,不够简明并且对开发人员来讲没有意义,所以建议子类在重写该方法时,返回一个简明易懂的信息表达式,一般为对象的属性信息。

没有重写toString()之前的Person类代码

public class ToStringTest {
    public static void main(String[] args) {
        Person per = new Person("codeleader", 32);
        System.out.println(per);
        System.out.println(per.toString());
    }
}
class Person{
    private String name;
    private int age;

    public Person(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }
}

image-20220920173315544

从上述结果来看,当我们打印一个对象时,调用toString方法和不调用toString方法的最终打印结果都是一样的。这说明当我们使用System.out.println或System.out.print方法打印对象时,会默认调用对象的toString方法。其实在Java中当一个对象与字符串进行拼接时,也会自动调用该对象的toString方法

另外,toString方法默认返回的是“全类名+@+对象的哈希值”。

重写toString方法之后的Person类的实例代码

package chap11.test;

public class ToStringTest {
    public static void main(String[] args) {
        Person per = new Person("codeleader", 32);
        System.out.println(per);
        System.out.println(per.toString());
    }
}
class Person{
    private String name;
    private int age;

    public Person(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

image-20220920173647703

1.2 equals方法

比较两个基本数据类型的值是否相等应使用"==",而比较两个字符串的内容是否相等应使用equals方法。

这是因为==对于基本数据类型来说,变量中存储的是数据值,所以直接使用==比较即可。对于应用数据类型来说,变量中存储的是对象的首地址,所以直接用==比较时,只是比较两个对象的首地址是否相等,而不是比较两个对象的内容是否相等。==

如果要比较两个对象的内容是否相等,则需要调用equals方法,该方法在Object类中的源码如下所示:

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

Object类中该方法的作用是比较两个对象的内容是否相等。从上面的代码可以看出,在Object类中,默认实现的equals方法与==的效果是一样的。

没有重写Person类的示例代码:

public class TestPersonEquals {
    public static void main(String[] args) {
        Person per1 = new Person("codeleader", 32);
        Person per2 = new Person("codeleader", 32);
        System.out.println(per1==per2);
        System.out.println(per1.equals(per2));
    }
}
class Person{
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

image-20220920174414822

正如前面所看到的,Object类中默认实现的equals方法也是通过==判断对象是否相等。而对于引用类型变量,==判断的不是属性信息,而实而这引用的是否是同一个对象,也就是地址是否相等。一般来讲,开发人员希望判断的是两个对象的内容是否相等,所以往往需要重写equals方法。

重写了equals方法和hashCode方法之后的代码:

package chap11.equals;

import java.util.Objects;

public class TestPersonEquals {
    public static void main(String[] args) {
        Person per1 = new Person("codeleader", 32);
        Person per2 = new Person("codeleader", 32);
        System.out.println(per1==per2);
        System.out.println(per1.equals(per2));
    }
}
class Person{
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age &&
                Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

image-20220920174723671

从上面模板生成的equals方法代码主要分为三个方面。

  • 两个对象的地址不一样,肯定返回true
  • 两个对象的类型不一样,肯定返回false
  • 两个对象被选择比较的属性信息完全一样,肯定返回true,有不一样的返回false

equals方法的重写需要满足自反性、传递性、一致性、对称性、非空对象。

==和equals方法的区别?

  • ==可用于判断两个基本数据类型变量,也可以用于判断两个引用类型变量。但都需要保证判断双方的类型一致或兼容,否则编译出错。
  • equals方法只能用于判断应用类型的变量,因为只有对象才有方法,默认判断的是对象的内容,如果重写Object类的equals方法,则一般判断的是对象的内容是否相等。

1.3 hashCode方法

源码如下:

image-20220920175157751

hashCode方法的说明有以下几点:

  • hashCode方法用于返回对象的哈希码值。支持此方法是为了提高哈希表(如java.util.Hashtable提供的哈希表)的性能。
  • hashCode在Object类中有native修饰,是本地方法,该方法的方法体不是Java实现的,是由C/C++实现的,最后编译为.dll文件,然后由Java调用。

示例代码:

public class HashCodeTest {
    public static void main(String[] args) {
        Person per1 = new Person("康师傅", 32);
        Person per2 = new Person("康师傅", 32);
        System.out.println(per1.hashCode());
        System.out.println(per2.hashCode());
        System.out.println(per1.equals(per2));
    }
}
class Person{
    private String name;
    private int age;

    public Person(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }
}

image-20220920185411512

我们也可以重写hashCode方法,手动重写或使用IDE开发工具提供的模板重写。

hashCode方法重写时要满足如下几个要求:

  • 如果两个对象调用equals方法返回true,那么要求这两个对象的hashCode值一定是相等的。
  • 如果两个对象的hashCode值不相等,那么要求这两个对象调用equals方法一定是false。
  • 如果两个对象的hashCode值相等,那么这两个对象调用equals方法可能是true,也可能是false。

重写Person类的hashCode和equals方法:

import java.util.Objects;

public class HashCodeTest {
    public static void main(String[] args) {
        Person per1 = new Person("康师傅", 32);
        Person per2 = new Person("康师傅", 32);
        System.out.println(per1.hashCode());
        System.out.println(per2.hashCode());
        System.out.println(per1.equals(per2));
    }
}
class Person{
    private String name;
    private int age;

    public Person(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age &&
                Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

image-20220920185742122

Java中的hashCode值遵循如下规定:

  • 如果两个对象的hashCode值不相等,那么这两个对象一定不相等。
  • 如果两个对象的hashCode值是相等的,那么这两个对象不一定相等。

1.4 getClass方法

对象有编译时类型和运行时类型两种类型,而且编译时类型可能与运行时类型不一致。编译时类型就是变量声明时的类型,那么如何在运行时获取某个变量中对象的运行时类型呢,Object类为我们提供了一个getClass方法,可以获取对象的运行时类型。该方法源码如下:

image-20220920190030904

示例代码:

public class GetClassTest {

    public static void listen(Animal animal){
        System.out.println("The animal is a "+animal.getClass());
        animal.shut();
    }

    public static void main(String[] args) {
        listen(new Dog());
        listen(new Cat());
    }
}
abstract class Animal{
    public abstract void shut();
}
class Dog extends Animal{
    @Override
    public void shut() {
        System.out.println("汪汪汪~");
    }
}
class Cat extends Animal{
    @Override
    public void shut() {
        System.out.println("喵喵喵~");
    }
}

image-20220920190110463

在上述代码的listen方法中,animal变量的编译时类型是Animal,而运行时类型变量由传入的实参对象决定,可能是Dog,也可能是Cat。

1.5 clone方法

开发中如果要复制一个对象,则可以使用Object类提供的clone方法。源码如下:

image-20220920190250103

调用该方法时可以创建并返回当前对象的一个副本。从源码中可以发现该方法的权限修饰符是protected,说明默认Object类的clone方法只能在java.lang包或其他包的子类中调用。因此,如果在测试类中要通过自定义类的对象来调用clone方法,则必须重写该方法。如果重写该方法,则子类必须实现java.lang.Cloneable接口,否则会抛出CloneNotSupportedException

示例代码:

import java.util.Objects;

public class CloneTest {
    public static void main(String[] args) {
        try {
            Person1 per = new Person1("codeleader", 18);
            Object clone = per.clone();
            System.out.println(per==clone);
            System.out.println(per.equals(clone));
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}
class Person1 implements Cloneable{
    private String name;
    private int age;

    public Person1(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    @Override
    public int hashCode() {
        return Objects.hash(name,age);
    }

    @Override
    public boolean equals(Object obj) {
        if(this==obj) return true;
        if(obj ==null||getClass()!=obj.getClass()) return false;
        Person1 person1=(Person1) obj;
        return age==person1.age&&Objects.equals(name,person1.name);
    }
}

image-20220920190606682

1.6 finalize方法

对于初学者来说,finalize方法是最没有机会接触到的方法,简单了解以下即可。源码如下:

image-20220920190728199

finalize方法是Object类中的protected方法,子类可以重写该方法以实现资源清理工作,GC在回收对象之前会调用该方法,即该方法不是由开发人员手动调用的。

当对象变为不可达时,即对象成为需要被回收的垃圾对象时,GC会判断该对象是否覆盖了finalize方法,若未覆盖,则直接将其回收。若对象未执行过finalize方法,则将其放入F-Queue队列,有一个低优先级线程执行该队列中对象的finalize方法。执行完finalize方法后,GC会再次判断该对象是否可达,若不可达,则进行回收,否则对象复活,复活后的对象下次回收时,将不再放入F-Queue队列,即不再执行finalize方法。

Java语言规范并不能保证finalize方法会被及时执行,而且根本不能保证它们会被执行。所以不建议finalize方法完成非内存资源清理工作以外的任务。