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

相关文章
|
17天前
|
存储 安全 Java
java.util的Collections类
Collections 类位于 java.util 包下,提供了许多有用的对象和方法,来简化java中集合的创建、处理和多线程管理。掌握此类将非常有助于提升开发效率和维护代码的简洁性,同时对于程序的稳定性和安全性有大有帮助。
41 17
|
9天前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
|
13天前
|
存储 Java 程序员
Java基础的灵魂——Object类方法详解(社招面试不踩坑)
本文介绍了Java中`Object`类的几个重要方法,包括`toString`、`equals`、`hashCode`、`finalize`、`clone`、`getClass`、`notify`和`wait`。这些方法是面试中的常考点,掌握它们有助于理解Java对象的行为和实现多线程编程。作者通过具体示例和应用场景,详细解析了每个方法的作用和重写技巧,帮助读者更好地应对面试和技术开发。
53 4
|
14天前
|
Java 编译器 开发者
Java异常处理的最佳实践,涵盖理解异常类体系、选择合适的异常类型、提供详细异常信息、合理使用try-catch和finally语句、使用try-with-resources、记录异常信息等方面
本文探讨了Java异常处理的最佳实践,涵盖理解异常类体系、选择合适的异常类型、提供详细异常信息、合理使用try-catch和finally语句、使用try-with-resources、记录异常信息等方面,帮助开发者提高代码质量和程序的健壮性。
32 2
|
18天前
|
存储 安全 Java
如何保证 Java 类文件的安全性?
Java类文件的安全性可以通过多种方式保障,如使用数字签名验证类文件的完整性和来源,利用安全管理器和安全策略限制类文件的权限,以及通过加密技术保护类文件在传输过程中的安全。
|
22天前
|
Java 数据格式 索引
使用 Java 字节码工具检查类文件完整性的原理是什么
Java字节码工具通过解析和分析类文件的字节码,检查其结构和内容是否符合Java虚拟机规范,确保类文件的完整性和合法性,防止恶意代码或损坏的类文件影响程序运行。
|
22天前
|
Java API Maven
如何使用 Java 字节码工具检查类文件的完整性
本文介绍如何利用Java字节码工具来检测类文件的完整性和有效性,确保类文件未被篡改或损坏,适用于开发和维护阶段的代码质量控制。
|
22天前
|
存储 Java 编译器
java wrapper是什么类
【10月更文挑战第16天】
23 3
|
24天前
|
Java 程序员 测试技术
Java|让 JUnit4 测试类自动注入 logger 和被测 Service
本文介绍如何通过自定义 IDEA 的 JUnit4 Test Class 模板,实现生成测试类时自动注入 logger 和被测 Service。
22 5
|
25天前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
19 3