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

相关文章
|
2天前
|
Java
课时14:Java数据类型划分(初见String类)
课时14介绍Java数据类型,重点初见String类。通过三个范例讲解:观察String型变量、"+"操作符的使用问题及转义字符的应用。String不是基本数据类型而是引用类型,但使用方式类似基本类型。课程涵盖字符串连接、数学运算与字符串混合使用时的注意事项以及常用转义字符的用法。
|
1天前
|
存储 Java 编译器
课时11:综合实战:简单Java类
本次分享的主题是综合实战:简单 Java 类。主要分为两个部分: 1.简单 Java 类的含义 2.简单 Java 类的开发
|
3天前
|
传感器 监控 Java
Java代码结构解析:类、方法、主函数(1分钟解剖室)
### Java代码结构简介 掌握Java代码结构如同拥有程序世界的建筑蓝图,类、方法和主函数构成“黄金三角”。类是独立的容器,承载成员变量和方法;方法实现特定功能,参数控制输入环境;主函数是程序入口。常见错误包括类名与文件名不匹配、忘记static修饰符和花括号未闭合。通过实战案例学习电商系统、游戏角色控制和物联网设备监控,理解类的作用、方法类型和主函数任务,避免典型错误,逐步提升编程能力。 **脑图速记法**:类如太空站,方法即舱段;main是发射台,static不能换;文件名对仗,括号要成双;参数是坐标,void不返航。
17 5
|
1天前
|
Oracle Java 关系型数据库
课时37:综合实战:数据表与简单Java类映射转换
今天我分享的是数据表与简单 Java 类映射转换,主要分为以下四部分。 1. 映射关系基础 2. 映射步骤方法 3. 项目对象配置 4. 数据获取与调试
|
24天前
|
安全 Java 编译器
JAVA泛型类的使用(二)
接上一篇继续介绍Java泛型的高级特性。3. **编译时类型检查**:尽管运行时发生类型擦除,编译器会在编译阶段进行严格类型检查,并允许通过`extends`关键字对类型参数进行约束,确保类型安全。4. **桥方法**:为保证多态性,编译器会生成桥方法以处理类型擦除带来的问题。5. **运行时获取泛型信息**:虽然泛型信息在运行时被擦除,但可通过反射机制部分恢复这些信息,例如使用`ParameterizedType`来获取泛型参数的实际类型。
|
24天前
|
安全 Java 编译器
JAVA泛型类的使用(一)
Java 泛型类是 JDK 5.0 引入的重要特性,提供编译时类型安全检测,增强代码可读性和可维护性。通过定义泛型类如 `Box<T>`,允许使用类型参数。其核心原理是类型擦除,即编译时将泛型类型替换为边界类型(通常是 Object),确保与旧版本兼容并优化性能。例如,`Box<T>` 编译后变为 `Box<Object>`,从而实现无缝交互和减少内存开销。
|
2月前
|
安全 Java
Object取值转java对象
通过本文的介绍,我们了解了几种将 `Object`类型转换为Java对象的方法,包括强制类型转换、使用 `instanceof`检查类型和泛型方法等。此外,还探讨了在集合、反射和序列化等常见场景中的应用。掌握这些方法和技巧,有助于编写更健壮和类型安全的Java代码。
53 17
|
4月前
|
Java 开发者
在 Java 中,一个类可以实现多个接口吗?
这是 Java 面向对象编程的一个重要特性,它提供了极大的灵活性和扩展性。
221 58
|
3月前
|
JSON Java Apache
Java基础-常用API-Object类
继承是面向对象编程的重要特性,允许从已有类派生新类。Java采用单继承机制,默认所有类继承自Object类。Object类提供了多个常用方法,如`clone()`用于复制对象,`equals()`判断对象是否相等,`hashCode()`计算哈希码,`toString()`返回对象的字符串表示,`wait()`、`notify()`和`notifyAll()`用于线程同步,`finalize()`在对象被垃圾回收时调用。掌握这些方法有助于更好地理解和使用Java中的对象行为。
|
4月前
|
存储 缓存 安全
java 中操作字符串都有哪些类,它们之间有什么区别
Java中操作字符串的类主要有String、StringBuilder和StringBuffer。String是不可变的,每次操作都会生成新对象;StringBuilder和StringBuffer都是可变的,但StringBuilder是非线程安全的,而StringBuffer是线程安全的,因此性能略低。
122 8