java Object 万字详解 (通俗易懂)

目录

Δ前言

一、基本介绍

二、构造方法

三、成员方法

        0.总览 :

        1.hashCode() :

        2.getClass() :

        3.toString() :

        4.equals() :

        5.finalize() :

四、JavaBean重写Object类的方法

        0.需求 :

        1.重写toString() 方法

        2.重写equals() 方法(重点)

            2.1 解读重写后的equals方法

            2.2 == 和 equals 的区别 ?

五、总结


Δ前言

  • 大家好,up今天给大家带来一篇详解Object类的博文,是我们《API-常用类》专题的第一篇博文(第一小节)。up希望通过文字解说和代码演示,帮助大家查缺补漏,并快速上手Java Object类。
  • 注意事项 : ①代码中的注释也很重要。不要眼高手低,自己跟着过一遍才真正有收获。点击侧边栏目录或者文章开头的目录可以跳转。
  • 良工不示人以朴,up所有文章都会适时补充完善。感谢阅读!

一、基本介绍

        Object类是Java类层次最顶层的基类(父类),所有类都是直接或间接继承自Object类,因此,所有类都可以使用Object类中的成员方法。

        Object类属于java.base模块,java.lang包下java.lang包下的类可以直接使用,不需要导包),如下图所示 :


二、构造方法

        Object() : 构造一个对象。

                如下图所示 :

                Object类仅一个构造器,根据继承机制,所有子类在构造器初始化前都会默认优先调用该构造器[super();]。


三、成员方法

        0.总览 :

                Object类常用的方法,如下图所示 :

                像notify,wait这些与线程相关的方法,因为我们还没有讲到Java高级篇——《多线程与高并发》的内容,因此这里不做说明,大家如果有兴趣可以先去看看up写得多线程基础。我们来一起看一看up用粗线条标注出的这五个方法

①int hashCode()

返回当前对象的哈希码值,该方法通过对象的地址值进行计算,不同对象的返回值一般不同

②Class <?> getClass()

返回调用此方法的对象的运行时类对象(即Class对象,也叫字节码文件对象)

③String toString()

返回该对象的字符串表示(默认打印的是"全类名 + @ + 哈希码值的十六进制")

④boolean equals()

返回其他某个对象是否与此对象“相等”(默认情况下比较的是两个对象的引用)

⑤void finalize()

当“垃圾回收器”确定不存在该对象的更多引用时,由对象的垃圾回收器调用此方法

        1.hashCode() :

                其实我们在面向对象专题的封装篇中,就已经提到过hashCode方法,并且还给出了hashCode的应用和常规协定。不知道大家是否还有印象,如果忘了也不要紧,这里再给大家演示一下 :

                演示 :

                up以HashCode_类为测试类,以Student类为演示类。

                代码如下 :


package knowledge.api.objectmethods;

/**
 * @author : Cyan_RA9
    1. hashCode() : 返回当前对象的哈希码值,该方法通过对象的地址值进行计算,
                 不同对象的返回值一般不同。
 */
public class HashCode_ {
    public static void main(String[] args) {
        //创建学生类对象
        Student student_0 = new Student();
        Student student_1 = new Student();
        //比较不同学生对象的哈希码值
        System.out.println("student_0对象的哈希码值为 : " + student_0.hashCode());
        System.out.println("student_1对象的哈希码值为 : " + student_1.hashCode());
        System.out.println("student_0对象的哈希码值为 : " + student_0.hashCode());
    }
}

class Student {}

                运行结果 :

                从运行结果可以看出,当我们打印不同对象的哈希码值时,得到的结果不同。而同一对象的哈希码值相同。

        2.getClass() :

                getClass方法需要用一个Class类来作接收,即获取字节码文件对象(也叫Class对象)。并且getClass方法要通过对象来调用,因此使用前一定要创建一个对象。这个方法呢,其实up也讲过了😂。只不过是在未来的java反射专题(虽然我已经写好了)。同样的,不要紧,再来演示一下不就完了。

                演示 :

                up以GetClass_类为测试类,以Grape类和Apple类作为演示类。

                代码如下 :


package knowledge.api.objectmethods;

/**
 * @author : Cyan_RA9
   2.Class <?> getClass() : 返回调用此方法的对象的运行时类对象(字节码文件对象)
 */
public class GetClass_ {
    public static void main(String[] args) {
        //创建葡萄类对象
        Grape grape_0 = new Grape();
        Grape grape_1 = new Grape();
        //获取葡萄类的Class对象
        Class class_grape_0 = grape_0.getClass();
        Class class_grape_1 = grape_1.getClass();
        Class grape_0_class = grape_0.getClass();
        //比较通过葡萄类不同对象获得的葡萄类的Class对象有什么不同。
        System.out.println("grape_0对象的字节码文件对象是:" + class_grape_0);
        System.out.println("grape_1对象的字节码文件对象是:" + class_grape_1);
        System.out.println("grape_0对象的字节码文件对象是:" + grape_0_class);
        System.out.println("--------------------------------------------------");
        
        //创建苹果类对象
        Apple apple_0 = new Apple();
        Apple apple_1 = new Apple();
        //获取苹果类的Class对象
        Class apple_0_class = apple_0.getClass();
        Class apple_1_class = apple_1.getClass();
        //比较通过苹果类不同对象获得的苹果类的Class文件有什么不同
        System.out.println("apple_0对象的字节码文件对象是:" + apple_0_class);
        System.out.println("apple_1对象的字节码文件对象是:" + apple_1_class);
    }
}

class Grape {}
class Apple {}

                运行结果 :

                从输出结果我们可以看出,一个类只有一个字节码文件对象。我们通过创建同一个类的不同对象来分别调用getClass方法,得到的Class对象是相同的。而不同类的字节码文件对象是不同的。同时,我们也能发现字节码文件对象的格式为 “class + 类的正名(类的正名 = 包类+ 类名)

        3.toString() :

                关于toString方法要注意一点 : 当我们直接输出一个对象时,默认调用toString方法

                演示 :

                up以ToString_类为测试类,以Animal类为演示类。

                代码如下 :


package knowledge.api.objectmethods;

/**
 * @author : Cyan_RA9
    3. toString() : 返回该对象的字符串表示(默认打印的是"全类名 + @ + 哈希码值的十六进制")
 */
public class toString_ {
    public static void main(String[] args) {
        //创建Animal类对象
        Animal animal_0 = new Animal();
        Animal animal_1 = new Animal();
        Animal animal_2 = new Animal();
        //toString() 方法的返回值类型为String类型(也可不做接收)
        String animal_0_string = animal_0.toString();
        System.out.println("animal_0对象的字符串表示是:" + animal_0_string);
        System.out.println(animal_0);
        System.out.println("------------------------------------------------");
        System.out.println("animal_1对象的字符串表示是:" + animal_1.toString());
        System.out.println("animal_2对象的字符串表示是:" + animal_2.toString());
    }
}

class Animal {}

                运行结果 :

                运行结果可以验证我们之前的结论——当我们直接输出一个对象时,默认调用toString方法。而且toString() 方法默认打印的的确是“类的正名 + @ + 哈希码值的十六进制”。当然,可能有p小将(Personable小将,指风度翩翩的人)要怀疑这里“@”后面跟着的一大堆玩意儿是不是当前对象哈希码值的十六进制?

                哎😂,其实验证方法也很简单,你先把这里的某个十六进制数转换成十进制数,然后利用hashCode() 方法打印出该对象的哈希码值,比对一下就知道了。当然up这里就不演示了,绝不是因为我懒!是因为我已经明确知道结论了,才给大家演示的,对不对?

                这不是重点。大家有没有想过,toString() 方法既然要打印出对象的字符串表示形式,那肯定是要有用的啊,最起码给人直观想象的机会。但是现在就这一堆乱七八糟的玩意儿,给谁第一眼看了也觉得糟心。那怎么办?诶~,别急,等等我们讲到子类重写Object类成员方法时再说。

        4.equals() :

                Object类中的equals()方法默认比较的是两个对象的引用。

                演示 :

                up以Equals_类为测试类,以Teacher类为演示类。

                代码如下 :


package knowledge.api.objectmethods;

/**
 * @author : Cyan_RA9
    4. equals() : 返回其他某个对象是否与此对象“相等”(默认情况下比较的是两个对象的引用)
 */
public class Equals_ {
    public static void main(String[] args) {
        //创建教师类对象
        Teacher teacher_0 = new Teacher();
        Teacher teacher_1 = new Teacher();
        //通过对象调用equals() 方法
        System.out.println("teacher_0和teacher_1是一个对象吗?" + teacher_0.equals(teacher_1));
        System.out.println("teacher_0和teacher_0是一个对象吗?" + teacher_0.equals(teacher_0));
    }
}

class Teacher {}

                相信大家看代码也能感觉出来了——这方法的原版看起来没什么意义!你只比较个地址值有个啥用?雷声大雨点小!我们创建对象的目的是什么?创建对象是为了描述某一类事物的一个具体存在,每个对象的属性和行为都不尽相同。所以,我想你也猜到了,这个方法往往也要在子类重写的。

        5.finalize() :

                垃圾回收机制的调用,是由系统来决定的,即有自己的GC算法;也可以通过System.gc()来尝试主动触发,虽然也有可能无效。你也可以重写此方法,做一些释放资源的操作。
                finalize()方法在实际开发中用的并不多,对于初学者几乎用不到,因此这里不做演示


四、JavaBean重写Object类的方法

        0.需求 :

                实际开发中,通常需要将对象转换成字符串形式进行运输,也需要对即将使用的对象进行相等判断。而toString() 方法和equals() 方法本身的特点无法实现以上需求。因此,这时候就需要我们定义标准JavaBean类,并重写Object类的toString() 方法 和 equals() 方法

                IDEA中提供了自动重写toString方法 和 equals方法的机制,可以使用快捷键Alt + insert (笔记本电脑可以Alt + Shift + 小键盘0),选择要定义或重写的方法,即可自动完成。

        1.重写toString() 方法

                up以ToString_EX类为测试类,以Teacher类为演示类。

                代码如下 :


package knowledge.api.objectmethods.override_;

/**
 * @author : Cyan_RA9
 * @purpose : 重写Object类的toString方法
 */
public class ToString_EX {
    public static void main(String[] args) {
        Teacher teacher_0 = new Teacher();
        teacher_0.setName("王斌");
        teacher_0.setAge(44);
        teacher_0.setSalary(20000);

        Teacher teacher_1 = new Teacher("许银川", 48, 100000);

        //直接输出对象,默认调用toString() 方法
        System.out.println(teacher_0);
        System.out.println(teacher_1);
    }
}

class Teacher {     /** 以JavaBean标准来敲 */
    //成员变量
    private String name;
    private int age;
    private double salary;

    //构造器
    public Teacher() {}
    public Teacher(String name, int age, double salary) {
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    //getter,setter方法
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }

    public double getSalary() {
        return salary;
    }
    public void setSalary(double salary) {
        this.salary = salary;
    }

    //重写的toString() 方法
    @Override
    public String toString() {
        return "Teacher{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", salary=" + salary +
                '}';
    }
}

                运行结果 :

                可以看到,重写后的toString方法本身并不复杂,就是返回了一个拼接后的字符串。而当我们在Teacher类中重写toString方法后,输出Teacher类对象调用的就是我们重写后的toString方法,成功打印出了Teacher类对象的相关信息。因此,重写toString() 方法的目的,往往是将对象的信息能够显式地打印出来,让我们看到。

        2.重写equals() 方法(重点)

                up以Equals_EX类为测试类,以Animal类为演示类。

                代码如下 :


package knowledge.api.objectmethods.override_;

import java.util.Objects;

/**
 * @author : Cyan_RA9
 * @purpose : 重写Object类的equals方法
 */
public class Equals_EX {
    public static void main(String[] args) {
        //利用带参构造创建不同Animal类对象
        Animal animal_0 = new Animal("猫", 15);
        Animal animal_1 = new Animal("狗", 12);
        Animal animal_2 = new Animal("猫", 15);
        //查看不同Animal对象的信息
        System.out.println("animal_0对象的信息为:" + animal_0);
        System.out.println("animal_1对象的信息为:" + animal_1);
        System.out.println("animal_2对象的信息为:" + animal_2);
        System.out.println("----------------------------------------------");
        //通过重写后的equals方法比较不同对象的信息是否相同
        System.out.println("animal_0对象和animal_1对象信息相同吗?" + animal_0.equals(animal_1));
        System.out.println("animal_0对象和animal_2对象信息相同吗?" + animal_0.equals(animal_2));
        System.out.println("animal_1对象和animal_2对象信息相同吗?" + animal_1.equals(animal_2));
    }
}

class Animal {      /** JavaBean标准 */
    //成员变量
    private String species_name;
    private int average_age;
    
    //构造器
    public Animal() {}
    public Animal(String species_name, int average_age) {
        this.species_name = species_name;
        this.average_age = average_age;
    }

    //getter,setter方法
    public String getSpecies_name() {
        return species_name;
    }
    public void setSpecies_name(String species_name) {
        this.species_name = species_name;
    }

    public int getAverage_age() {
        return average_age;
    }
    public void setAverage_age(int average_age) {
        this.average_age = average_age;
    }

    //重写equals方法
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Animal animal = (Animal) o;
        return average_age == animal.average_age && 
                    species_name.equals(animal.species_name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(species_name, average_age);
    }

    //重写toString方法以打印出对象的信息
    @Override
    public String toString() {
        return "Animal{" +
                "species_name='" + species_name + '\'' +
                ", average_age=" + average_age +
                '}';
    }
}

                运行结果 :

            2.1 解读重写后的equals方法

                我们先来解读一下关于equals方法的重写 :

//重写equals方法
@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Animal animal = (Animal) o;
    return average_age == animal.average_age &&
                species_name.equals(animal.species_name);
}

                解读①——该方法的形参列表是一个Object类型的对象,也就是多态了。

                解读②——方法第一行是一个if条件语句的判断,判断条件“this == o”中,用到了比较运算符“==”,我们之前讲过,==比较运算符如果判断引用类型,判断的是二者的地址是否相同,即判定是否为同一对象;因此这里就是像看看你要判断的对象是不是它本身,如果判断为true,意思就是你拿一个对象和它自己比去了。那还用比吗😅,这不就和你照镜子一样,比个🐔儿?因此直接return true,不再进行后续的比较。

                解读③——接着,又来了第二个if条件语句的判断,判断条件“o == null || getClass() != o.getClass())”中,由逻辑运算符短路或 “||”分为了两部分,短路或的判断条件中,语句从左向右执行,且只要满足第一部分的判断,之后的部分便不再执行第一部分“o==null”,是判断传入的对象是否为空。你想,一个在堆空间中真正存在的对象和一个空对象null能比出个啥来吗?就像你比身高,让你和空气比,这不虚空对线呢😅?因此,如果传入的对象为空,立刻return false,没啥比头。
                来看第二部分“getClass() != o.getClass()),这不我们前面刚说过getClass方法么,巧了😋。其实,第一个getClass() 前面隐含了“this.”,当然你也可以手动加上。这部分判断是看你传入的对象和调用该方法的对象是否是同一类对象。因为一个类只对应一个字节码文件,因此同一个类的字节码文件对象一定相同。你想,如果一条狗和你手里的泡泡糖都叫“大大”,那**能比么?
                因此,如果第一部分判断条件不通过,说明传入的对象不为空;如果第二部分判断条件不通过,说明传入的对象和调用方法的对象是同一类型的对象,可做后续的比较。反之,return false,不再进行后续的判断。

                解读④——当前两个if条件语句都没有把o对象拦下来后,我们已经确定传入的对象既不是调用方法的对象本身,也不为空,并且和调用方法的对象是同一类对象。这时候它来了一个强制转型 “Animal animal = (Animal) o;”,没毛病吧,妥妥地强转,原因估计你也猜到了,现在的o对象是一个Object类型的对象,根据多态的弊端,父类引用不能直接使用子类对象的特有成员。因此,这里要把o对象进行强制向下转型,以改变o对象的编译类型,使得做接收后的新的引用变量可以调用要判断类型(此处为Animal类)的特有成员,以便进行后续的判断。

                解读⑤——最后就可以开始判断了。IDEA也很干脆,直接return了一个boolean类型。注意,具体的比较语句中up还要说一下,判断非引用类型直接用比较运算符“==”即可,如果判断的是引用类型,比如说字符串,要使用“字符串.equals()”的形式来判断,很明显字符串属于String类,而String类中重写了Object类的equals方法,可以比较两个字符串的内容是否相同。

                其次,看到代码后,可能会有小伙伴儿们疑惑——为什么我用快捷键重写equals方法时,IDEA会默认同时重写equals方法和hashCode方法呢?

                这要说起来可就大费篇章了😂。这与hashCode的常规协定有关,也是为了避免某些程序执行出现异常的情况。现在不需要深究,大家了解一下即可。等你到找工作面试的时候,你不想懂也得懂它了🤗。

            2.2 == 和 equals 的区别 ?

                (1) ==是一个比较运算符既可以判断基本类型,又可以判断引用类型
                (2) 如果判断基本类型,判断的是二者的值是否相等(eg : 判断1 == 3,结果为false);
                (3) 如果判断引用类型,判断的是二者的地址是否相同,即判定是否为同一对象(eg : Student student1 = new Student();,Student student2 = student1;判断student2 == student1,结果为true)。
                而equals是顶层父类Object类中的方法,equals方法本身在Object类中的源码如下 : 

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

                可以看到,Object类中的 equals 方法用来检测两个对象是否相等,即默认情况下比较的是两个对象的引用(地址)。这一点和 == 用于判断引用类型时一致。equals的特点在于,它是一个方法,而且是Object类中的方法,因此,equals方法往往在子类中被重写,例如在String类中,equals方法被重写去判断两个字符串的内容是否相等。并且,在我们自己创建的类中,equals方法也常常被重写,去判断两个对象的指定的具体内容是否一致。
                还有一点要注意,==的运行速度比equals方法更快,因为==比较引用类型时,仅比较地址。


五、总结

  • 🆗,以上就是关于“API——常用类”专题——Object类的全部分享了。
  • 总结一下,对于Object类中的方法,大家只要理解这几个原初的方法,并且掌握toString方法和equals方法的重写问题就没问题了;还需要理解 == 和 equals方法的区别。
  • 下一节内容——常用类之String类,我们不见不散,感谢阅读!

        System.out.println("END-------------------------------------------"); 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Cyan_RA9

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值