Java——Object 类

简介: Java——Object 类

1 Object 类


Object 类是 Java 中所有类的父类。


在 Java 中任何一个类,如果追根溯源的话,归根结底都是 Object 类的直接或者间接子类。


首先,Object 类既然是所有类型的父类,那么在 Java 中所有的对象,都能够赋值给 Object类型的引用。这是因为子类对象可以直接赋值给父类引用,而所有 Java 中的类都是 Object类的子类。


其次,由于子类中能够继承父类中的公开方法。因此,Object 类中所有的公开方法都能被子类继承。也就是说,Object 类中的公开方法,是 Java 中所有对象都拥有的方法。接下来,我们就仔细来研究一下 Object 类的公开方法。


1.1 finalize


finalize 是一个 protected 的方法。虽然不是 public 的,但是这个方法也同样能够被所有子类继承(考虑一下,protected 能够被同包以及非同包的子类访问,也就是能够被所有子类访问)。


这个方法会在对象被垃圾回收时由垃圾回收器调用。

class Student{
    String name;
    int age;
}
public class TestStudent{
    public static void main(String args[]){
        Student stu1 = new Student();
                stu1 = new Student();
    }
}

在这段代码中,创建了两个不同的 Student 对象。内存中的结构如下图:


image.png


           可以看出,一开始分配的那块内存(Student 对象 1),由于之后把 stu1 指向了 Student对象 2(创建了一个新对象并把首地址赋值给 stu1 引用),因此再也没有引用指向 Student对象 1。因此结果就是,这个对象占据着内存空间,但是没有引用指向这个对象,因此这个对象无法被使用。于是,这种没法使用但又占据内存空间的对象,就被称为垃圾对象。这些垃圾对象占用内存空间,如果一直不处理的话,会造成内存空间的浪费,严重的话会造成程序的崩溃。


           而在 Java 中,程序员只需要负责分配空间(也就是 new 对象),而不需要操心处理垃圾对象的问题。JVM 有一个自动垃圾回收的机制,这个机制能够自动回收垃圾对象所占用的内存。这样,程序员就只需要负责分配空间、创建对象而不用担心内存的回收。


           当 JVM 进行一个对象的垃圾回收工作时,会自动调用这个对象的 finalize 方法。我们应该如何来看待这个 finalize 方法呢?这要从垃圾回收的时机说起。在 JVM 的规范中,只规定了 JVM 必须要有垃圾回收机制,但是什么时候回收却没有明确说明。也就是说,对象成为了垃圾对象之后,并不一定会马上就被垃圾回收。


          在 Java 中还有一个 System.gc()方法。调用这个方法,就相当于通知 JVM,程序员希望能够进行垃圾回收。但是调用 gc 也不能保证马上就能进行垃圾回收,这一切都要看当时 JVM的运行状态。


总结一下:finalize 方法在对象被垃圾回收的时候调用。但是,由于 Sun 公司的 JVM 采用的是“最少”回收的机制,因此不应当把释放资源的代码写在 finalize 方法中。


1.2 getClass


getClass 方法是 Object 类中的一个公开方法,这个方法的作用是:返回对象的实际类型。

class Animal{}
class Dog extends Animal{}
class Courser extends Dog{}
public static boolean isDog(Animal ani){
    if (ani instanceof Dog){
            return true;
    }else return false;
}

这样的代码却有一个问题,当传入的 ani 参数所指向的对象是一个 Courser 对象时,这个函数同样返回 true。因为根据多态,Courser 是 Dog 类的子类,因此当 ani 指向一个 Courser 对象时,返回值也是 true。这与我们的要求并不一致,我们希望的是,仅仅当 ani 参数指向一个实际类型为 Dog 类的对象时才返回 true。



针对这个问题,我们可以使用 getClass 方法来解决。getClass 方法能够返回一个对象的实际类型,通过比较两个对象 getClass 的返回值,就能够判断这两个对象是否是同一个类型。例如有如下代码:

Dog d1 = new Dog();
Dog d2 = new Dog();
Dog d3 = new Courser();

//d1 和 d2 实际类型相同,因此 getClass 方法返回值相同,输出 true


System.out.println(d1.getClass() == d2.getClass());


//d1 和 d3 实际类型不同,因此 getClass 方法返回值不同,输出 false


System.out.println(d1.getClass() == d3.getClass());


因此,利用 getClass 方法,就能够避免 instanceof 操作符的麻烦。改写 isDog 方法如下:

public static boolean isDog(Animal ani){
    Dog d = new Dog();
    if (ani.getClass() == d.getClass() ){
        return true;
        }else return false;
}

1.3 equals



equals 方法是 Object 类中定义的方法,其方法签名为:


public boolean equals(Object obj)


由于 equals 是 Object 类中的公开方法,这意味着在 java 中所有对象都包含了 equals 方法。这个方法用来判断两个对象内容是否相等。要注意的是 equals 方法和双等号“==”之间的区别。


“==”比较两个对象类型,比较的是两个引用中保存的地址,而不是对象的值。


image.png


用 equals 方法来比较两个对象的内容是否相等。


注意 equals 方法的签名:这个方法接受一个 Object 类型的对象 obj 做为参数。equals方法比较的就是当前对象(this)和 obj 这两个对象的内容。例如,对于 str1.equals(str2)这个比较而言,当前对象就是 str1 对象,而 obj 对象就是 str2 对象。


要注意的是,在某些情况下需要程序员覆盖 equals 方法。例如下面的代码:

class Student {
    String name;
    int age;
    public Student(){}
    public Student(String name, int age){
        this.name = name;
        this.age = age;
    }
}
public class TestStudent{
    public static void main(String args[]){
        Student stu1 = new Student(“Tom”, 18);
        Student stu2 = new Student(“Tom”, 18);
        System.out.println(stu1 == stu2);
        System.out.println(stu1.equals(stu2));
    }
}

上面这个程序,输出结果为两个 false。



第二个输出语句中,我们调用了 equals 方法来比较两个对象。在这两个对象中,两个学生的姓名相同,年龄也相同,但是比较的结果依然是 false。


在我们的 Student 对象中并没有定义 equals 方法,因此在调用 stu1.equals 方法时,调用的实际上是 Student 类从 Object 类中继承的 equals 方法。那 Object 类中的 equals 方法进行判断时,判断的就是引用是否相等。以下代码来源于 Sun 公司 JDK6 的源码

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

可以看到,在 Object 类中的 equals 方法,使用的是双等号进行的引用的比较,因此,Object 类中的 equals 方法不能够帮助我们比较两个学生对象的内容是否相等。为了替换掉 Object 类中对 equals 的实现,我们应当覆盖 equals 方法。也就是说,程序员应当自己来指定两个对象的比较准则。



要注意的是,实现的 equals 方法,应当满足以下几个条件:


自反性。自反性指的是,如果一个引用 x 不等于 null,则 x.equals(x)应当为 true。也就是说,无论什么情况,一个对象自己跟自己比较,必须为 true。

对称性。对称性指的是,如果有两个不为 null 的引用 x 和 y,如果 x.equals(y)为 true,则 y.equals(x)也为 true;而如果 x.equals(y)为 false,则 y.equals(x)也为 false。

传递性。这条准则指的是,如果有三个不为 null 的引用 a、b、c,如果 a.equals(b)为 true,b.equals(c)为 true,则 a.equals(c)也必然为 true。

一致性。这条准则比较简单,指的是如果有两个不为 null 的引用 x 和 y,在不改变x 和 y 的属性的前提下,每次调用 x.equals(y)返回的值都相同。也就是说,如果你不改变 x 和 y 的属性,则每次比较这两个对象,结果应该一致,不能因为时间的变化等因素而影响比较的结果。

如果 x 不为 null,则 x.equals(null)应当返回 false。
public boolean equals(Object obj){
//判断 obj 是否和 this 相等,保证自反性
if (obj == this) return true;
//判断 obj 是否为 null,保证最后一条准则
if (obj == null) return false;
//判断两个对象的实际类型是否相等,
//如果不相等,则说明比较的是两个不同种类的对象,应当返回 false
if (obj.getClass() != this.getClass()) return false;
//强制类型转换
//由于之前已经使用 getClass 判断过实际类型,因此这里强转是安全的
Student stu = (Student) obj;
//判断每个属性是否相等
// 对于基本类型的属性用“==”比较,对象类型的属性用 equals 比较
if (this.age == stu.age && this.name.equals(stu.name) )
        return true;
    else return false;
}

总结一下,覆盖 equals 方法的五个步骤:


1、 判断 this == obj


2、 判断 obj == null


3、 判断两个对象的实际类型(使用 getClass()方法)


4、 强制类型转换


5、 依次判断两个对象的属性是否相等


1.4 toString


toString 方法也是 Object 类中定义的方法,这意味着这个方法是 Java 中所有对象都有的方法。这个方法的签名如下:


public String toString()


这个方法没有参数,返回值类型是一个 String 类型。这个方法的返回值是某个对象的字符串表现形式。

class Student{
    String name;
    int age;
    public Student(){}
    public Student(String name, int age){
        this.name = name;
        this.age = age;
    }
}
public class TestToString{
    public static void main(String args[]){
        Student stu = new Student();
        System.out.println(stu.toString());//手动调用 toString 方法
        System.out.println(stu); //直接打印 stu 对象
    }
}

上面的两行代码运行结果如下:


Student@c17164


Student@c17164


可以看出,无论是手动调用 toString 方法,还是直接打印 stu 对象,所得的结果都是一样的。这意味着,如果打印 stu 对象的话,就相当于打印 stu 的 toString 方法返回值。


我们调用学生对象的 toString 方法,目的是获知学生对象的相关信息,例如学生的姓名和年龄等。而由于在上面的代码中,我们没有为 Student 类写 toString 方法,这意味着学生对象中的 toString 方法是从 Object 类中继承来的,因此打印出的是类名以及相关地址。为了让 toString 方法能打印出我们想要的内容,我们可以覆盖这个方法如下:

class Student{
    String name;
    int age;
    public Student(){}
    public Student(String name, int age){
        this.name = name;
        this.age = age;
    }
    public String toString(){
        return name + " " + age;
    }
}
public class TestToString{
    public static void main(String args[]){
        Student stu = new Student("Tom", 18);
        System.out.println(stu.toString());
        System.out.println(stu);
    }
}

运行结果如下:


Tom 18


Tom 18


可以看出,无论用哪种方式,都打印的是 Student 类中 toString()方法的返回值。此外,我们在介绍 String 类型时曾经提过,String 类型与任何其他类型相加,结果都是String 类型。这其中,“其他类型”既包括简单类型,又包括对象类型。

Student stu = new Student(“Tom”, 18);
String str = “my string ” + stu;
System.out.println(str);

我们使用一个“my string”字符串与一个学生对象相加,结果还是一个字符串。在生成这个字符串的时候,会把一个对象转换成一个字符串,转换的方式,就是调用这个对象的 toString 方法并获取其返回值。


这句代码表示把“my string”字符串和 stu 的 toString 方法返回值相加,并把相加的结果赋值给 str 变量。


上述代码的运行结果如下:


my string Tom 18

相关文章
|
4月前
|
Java 编译器 API
Java 密封类:精细化控制继承关系
Java 密封类:精细化控制继承关系
340 83
|
2月前
|
安全 Java 数据建模
Java记录类:简化数据载体的新选择
Java记录类:简化数据载体的新选择
232 101
|
2月前
|
安全 Java 开发者
Java记录类:简化数据载体的新方式
Java记录类:简化数据载体的新方式
285 100
|
5月前
|
IDE Java 数据挖掘
Java 基础类从入门到精通实操指南
这份指南专注于**Java 17+**的新特性和基础类库的现代化用法,涵盖开发环境配置、数据类型增强(如文本块)、字符串与集合处理进阶、异常改进(如密封类)、IO操作及实战案例。通过具体代码示例,如CSV数据分析工具,帮助开发者掌握高效编程技巧。同时提供性能优化建议和常用第三方库推荐,适合从入门到精通的Java学习者。资源链接:[点此下载](https://pan.quark.cn/s/14fcf913bae6)。
226 36
|
3月前
|
安全 IDE Java
Java记录类型(Record):简化数据载体类
Java记录类型(Record):简化数据载体类
418 143
|
1月前
|
存储 Java 索引
用Java语言实现一个自定义的ArrayList类
自定义MyArrayList类模拟Java ArrayList核心功能,支持泛型、动态扩容(1.5倍)、增删改查及越界检查,底层用Object数组实现,适合学习动态数组原理。
86 4
|
1月前
|
IDE JavaScript Java
在Java 11中,如何处理被弃用的类或接口?
在Java 11中,如何处理被弃用的类或接口?
159 5
|
1月前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
143 1
|
1月前
|
Java Go 开发工具
【Java】(8)正则表达式的使用与常用类分享
正则表达式定义了字符串的模式。正则表达式并不仅限于某一种语言,但是在每种语言中有细微的差别。
203 1
|
1月前
|
存储 Java 程序员
【Java】(6)全方面带你了解Java里的日期与时间内容,介绍 Calendar、GregorianCalendar、Date类
java.util 包提供了 Date 类来封装当前的日期和时间。Date 类提供两个构造函数来实例化 Date 对象。第一个构造函数使用当前日期和时间来初始化对象。Date( )第二个构造函数接收一个参数,该参数是从1970年1月1日起的毫秒数。
147 1
下一篇
oss云网关配置