Java类的共同父类Object

本文深入解析Java.lang.Object类中的核心方法,包括hashCode(), toString(), equals(), finalize(), clone()等,并通过示例代码演示如何重写这些方法以实现特定功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

java.lang.Object类是所有Java类的最高层次父类,该类中没有定义任何属性,方法也只有几个,但正是这些方法提供了面向对象编程技术的基本机制,下面将分别介绍:

[size=medium]1、hashCode()方法[/size]

hashCode()方法的格式如下:
public int hashCode()

其功能是返回当前对象的哈希码(HashCode)数值,哈希码可以理解为系统为每个Java对象自动创建的整型编号,任何两个不同的Java对象的哈希码一定不同,而在Java应用程序的一次执行期间,在同一个对象上多次调用hashCode()方法时,必须一致的返回相同的整数。这样哈希码就可以起到标识对象的功能,引用类型变量所记录的对象句柄实际上就包含了该对象的哈希码信息。例如:测试hashCode()方法
源文件:Person.java
public class Person{
private int age;
public Person(int age){
this.age = age;
}
public void setAge(int age){
this.age = age;
}
public int getAge(){
return age;
}
}

源文件:TestHashCode.java
public class TestHashCode{
public static void main(String args[]){
Person p1 = new Person(18);
Person p2 = new Person(18);
int handle1 = p1.hashCode();
System.out.println(handle1);
System.out.println(Integer.toHexString(handle1));
System.out.println(p1);
System.out.println("------------");
System.out.println(p2.hashCode());
}
}

程序运行结果如下:
[color=blue]374283533
164f1d0d
Person@164f1d0d
------------
603737068[/color]
程序中,变量p1和p2所引用的两个Person类型对象虽然内容相同(属性age的值相同),但其哈希码不同,且保存在不同的空间中。还可看出,使用System.out.println()方法直接打印引用类型数据时输出的信息中包含了对象的哈希码信息,这一点在下面的toString()方法中会做解释。

[size=medium]2、toString()方法[/size]

Object类中toString()方法的原始定义如下:
public String toString (){
return getClass().getName()+”@”+Integer.toHexString(hashCode());
}

该方法以字符串形式返回当前对象的有关信息,从其原始定义可以看出,所返回的是对象所属的类型名称及其哈希码。当使用System.out.println()方法直接打印输出引用类型变量时,println()方法中会自动调用其toString()方法,再将所返回的字符串信息输出到屏幕上。
如:
System.out.println(p1);

等价于:
System.out.println(p1.toString());

由于Java语言中允许子类对父类中继承来的方法进行重写,以改变其实现细节,因此我们也可以根据需要在自己定义的Java类中重写其toString()方法,以提供更适合的说明信息。
例如:重写toString()方法
源文件:Person.java
public class Person{
private int age;
public Person(int age){
this.age = age;
}
public void setAge(int age){
this.age = age;
}
public int getAge(){
return age;
}
public String toString(){
return "This is a instance of Person,age=" + age;
}
}

源文件:TestOverride.java
public class TestOverride{
public static void main(String args[]){
Person p1 = new Person(18);
System.out.println(p1.hashCode());
System.out.println(p1); //等价于
System.out.println(p1.toString());
}
}

程序运行结果如下:
[color=blue]374283533
This is a instance of Person,age=18
This is a instance of Person,age=18[/color]
回想一下,我们以前使用System.out.println()方法直接打印输出[b]java.lang.String[/b]、[b]java.util.Data[/b]等类型数据时输出的也不是对象的哈希码,而是更有意义的字符串信息,原理也是相同的——这些类中也根据需要重写了各自的toString()方法。

[size=medium]3、equals()方法[/size]

Object类中equals()方法的原始定义如下:
public boolean equals(Object obj){
return (this==obj);
}

其功能是比较当前对象和方法参数obj所引用的对象两者的等价性,如果等价则返回值为true,否则返回false。在进一步讲解之前,让我们先明确Java语言中的等价性标准:基本类型数据比较的是数据的值,而引用数据类型比较的则是对象的句柄,即对象的hashCode编码或者说引用类型变量的值,而非对象本身。简单地说,比较的永远是变量的值是否相等。
例如:“==”和equals()方法使用举例1
源文件:Person.java
public class Person{
private int age;
public Person(int age){
this.age = age;
}
public void setAge(int age){
this.age = age;
}
public int getAge(){
return age;
}
}

源文件:TestEquals1.java
public class TestEquals1{
public static void main(String args[]){
int i = 5;
int j = 5;
System.out.println(i == j);
Person p1 = new Person(18);
Person p2 = new Person(18);
System.out.println(p1 == p2);
System.out.println(p1.equals(p2));
p2 = p1;
System.out.println(p2 == p1);
System.out.println(p1.equals(p2));
}
}

程序运行结果如下:
[color=blue]true
fals
fals
true
true[/color]
[color=red]可以看出比较引用类型数据的等价性时,其标准比较苛刻,只有当两个引用变量的值相等,实际上是指向同一个对象时才算做等价。看起来使用“==”运算符与equals()方法效果似乎相同,而前者还能够判断基本数据类型数据的等价性,那么equals()方法就显得多余了,其实不然,equals()方法在比较一些特定的引用类型(如java.lang.String、java.io.File、java.util.Date以及封装类)数据时,允许改变先前严格的等价性标准——只有两个对象同为上述的特例类型且其内容相同(对象各自封装的属性值对应相同),equals()方法即判为等价,而“==”判断则不存在任何“变通”的可能。[/color]
例如:“==”和equals()方法使用举例2
public class TestEquals2{
public static void main(String args[]){
String s1 = new String("abc");
String s2 = new String("abc");
System.out.println(s1 == s2);
System.out.println(s1.equals(s2));
s2 = s1;
System.out.println(s1 == s2);
System.out.println(s1.equals(s2));
}
}

程序运行结果如下:
[color=blue]false
true
true
true[/color]
之所以这样处理,是因为在实际应用开发中,人们更关心的常常是两个字符串的内容是否相同,比如身份验证时输入的用户名/密码等是否与数据库中读取出来的注册信息相匹配,而不在乎是否是同一个对象,而文件的名称和存储路径以及时间等信息的性质也是如此。需要特别说明的是,[b][color=red]String常量内容相同的话,在内存中将只保存一份[/color][/b]。
例如:
String s1 = "abc";
String s2 = "abc";
System.out.println(s1 == s2);
System.out.println(s1.equals(s2));

运行结果都为“true”。
其实,要实现上述“特例”并不困难,只需在Object的子类中重写其equals()方法,给出用户定义的等价性标准就是了,我们也可以在应用开发时根据需要进行类似处理。
例如:用户自定义等价性标准
源文件:Person1.java
public class Person1{
private int age;
public Person1(int age){
this.age = age;
}
public void setAge(int age){
this.age = age;
}
public int getAge(){
return age;
}
public boolean equals(Object o){
if(o instanceof Person1){
Person1 p = (Person1)o;
if(this.age == p.age){
return true;
}
else
return false;
}
return false;
}
}

源文件:TestEquals3.java
public class TestEquals3{
public static void main(String args[]){
Person1 p1 = new Person1(18);
Person1 p2 = new Person1(18);
System.out.println(p1 == p2);
System.out.println(p1.equals(p2));
}
}

程序运行结果如下:
[color=blue]false
true[/color]
上述程序中Person1类中重写了equals()方法,重新定义了Person1类型数据的等价性判定标准——只要对象均为Person1类型且其age属性值相等,则认为等价,无论是否为同一个对象。其equals()方法体中的代码也可简化为:
if(o instanceof Person1){
Person1 p = (Person1)o;
if(this.age == p.age)
return true;
}
return false;

或者:
return (o instanceof Person1)&&(((Person1)o)age == this.age);


[size=medium]4、finalize()方法[/size]

Java运行时环境中的垃圾收集器在销毁一个对象之前,会自动调用该对象的finalize()方法,然后才释放对象的内存空间,该方法在Object类中的原始定义如下:
protected void finalize() throws Throwable{}

请注意,这里finalize()方法修饰符是protected,而不是public,这种访问控制等级使得在外界(子类以外的范围)对于该方法是不可见的,相信读者能够理解,要使其发挥作用,应该在子类中重写finalize()方法,而且,重写方法的修饰符应改为public,否则重写仍然没有实际意义。finalize()方法的用途是在子类中重写,以加入所需的逻辑代码来配置系统资源或执行其他清除操作。
例如:
源文件:Person2.java
public class Person2{
private String name;
public Person2(String name){
this.name = name;
System.out.println("创建Person2对象,name:"+name);
}
public void fianlize(){
System.out.println("销毁Person2对象,name:"+name);
}
}

源文件:TestFinalize.java
public class TestFinalize{
public static void main(String args[]){
for(int i = 0;i < 10;i ++){
Person2 p = new Person2("Tom" + i);
for(int j = 0;j < 1000;j ++){
String[] test = {new String("abc"),new String("def"),new String("ghi")};
}
}
}
}

程序运行的类似结果如下(程序每次运行的结果可能不一样,内存不同的机器运行结果也是不一样的):
[color=blue]创建Person2对象,name:Tom0
创建Person2对象,name:Tom1
创建Person2对象,name:Tom2
创建Person2对象,name:Tom3
创建Person2对象,name:Tom4
创建Person2对象,name:Tom5
创建Person2对象,name:Tom6
创建Person2对象,name:Tom7
销毁Person2对象,name:Tom6
销毁Person2对象,name:Tom5
创建Person2对象,name:Tom8
创建Person2对象,name:Tom9[/color]
程序中的内层for循环起到消耗内存空间的作用,读者可能奇怪为什么创建10个Person2类的对象,却只销毁了其中两个,其实Java虚拟机的垃圾回收操作对于应用程序而言是完全透明的——[b]程序无法预料或精确控制某个无用对象何时被销毁,也就无法控制其的finalize()方法的调用时机,而且,除非垃圾回收器认为程序的可用内存空间已经不足,否则它不会试图释放无用对象占用的内存的内存[/b]。换句话说,下述情况是完全可能发生的:一个程序只占用了少量的内存,于是垃圾回收器没有在程序运行的过程中销毁无用对象并释放它们所占用的内存,也就没有调用过这些对象的finalize()方法,程序就终止了。不必担心,JVM最终关闭时还是会释放其所占用的所有内存空间。
由于finalize()方法最终是否会执行,以及何时会执行都是不确定的,在应用程序层面无法精确控制和干预,即使在应用程序中显式调用System.gc()或Runtime.gc()方法强制系统清理无用内存空间,也不能保证这一点,因此finalize()方法并不可靠,在应用程序开发中不建议使用。

[size=medium]5、clone()方法[/size]

在应用开发过程中,我们可能会需要拷贝(copy,复制)一个现有的对象,即得到一个新对象并希望其与现有对象封装完全相同的信息(属性值),主要是为了此后两者互不相干,修改其中的一个对象不会影响到另一个,我们知道,简单地进行引用变量间的赋值是不能解决问题的,因为并没有创建新对象;而自己编写代码先创建一个新对象,再将原始对象的属性值一一复制过来也比较烦琐,且存在后述的“浅度拷贝”问题;这种情况下,利用clone()方法来实现对象拷贝不失为一种明智的选择。
Object类中的clone()方法专门提供拷贝当前对象的功能,其原型如下:
protected native Object clone() throws CloneNotSupportedException;

其中的修饰符native标明此方法是一个本地方法,即调用了其运行时所在平台/操作系统的底层功能,当然这是早就实现好的,读者不必为此分心。[b]该方法能够创建并返回当前对象的一个副本,可以理解为将当前对象的所有信息(一段连续的内存空间中存储的数据)直接复制一份并单独保存,因此其返回的是已经包含了原有对象信息的一个新对象,而不是原有对象的引用。[/b]
和finalize()方法类似,clone()方法在Object类中也被定义为protected的,因此只有在其子类中进行重写才能真正发挥作用,Java语言规定,所有要进行“克隆”的对象所属的类必须实现java.lang.Cloneable接口,这是一种安全性保护。
例如:实现简单的克隆操作
源文件:Person3.java
public class Person3 implements Cloneable{
private String name;
private int age;
public Person3(String name,int age){
this.name = name;
this.age = age;
}
public void setName(String name){
this.name = name;
}
public void setAge(int age){
this.age = age;
}
public String getName(){
return name;
}
public int getAge(){
return age;
}
public void display(){
System.out.println("Name:"+name+"\tAge:"+age);
}
public Object clone(){
Person3 p = null;
try{
p = (Person3)super.clone();
}catch(CloneNotSupportedException e){
e.printStackTrace();
}
return p;
}
}

源文件:TestClone.java
public class TestClone{
public static void main(String args[]){
Person3 p1 = new Person3("Tom",18);
Person3 p2 = (Person3)p1.clone();
System.out.println(p1 == p2);
System.out.println(p1.equals(p2));
p2.setAge(25);
p2.display();
p1.display();
}
}

程序运行结果如下:
[color=blue]false
false
Name:Tom Age:25
Name:Tom Age:18[/color]
查看java.lang.Cloneable接口大源代码,你会发现该接口中没有任何内容,其源代码如下:
package java.lang;
public interface Cloneable(){
}

这样的接口被称为[b]空接口[/b],实际上只是起到标识的作用——[b][color=red]必须是该接口实现类的实例才能进行克隆操作[/color][/b],因此这样的接口也称“标记性接口”。
需要小心的是,使用上述的clone()方法进行对象拷贝可能出现“浅度拷贝”(Low Copy)的问题。
例如:[b][size=medium]浅度拷贝[/size][/b]
源文件:Person3.java
源文件:Book.java
public class Book implements Cloneable{
String bookName;
double price;
Person3 author;
public Book(String bn,double p,Person3 a){
bookName = bn;
price = p;
author = a;
}
public Object clone(){
Book b = null;
try{
b = (Book)super.clone();
}catch(CloneNotSupportedException e){
e.printStackTrace();
}
return b;
}
public void display(){
System.out.println(bookName+"\t"+price+"\t");
author.display();
}
}

源文件:TestLowCopy.java
public class TestLowCopy{
public static void main(String args[]){
Book b1 = new Book("Java编程思想",99,new Person3("张三",48));//p0
Book b2 = (Book)b1.clone();//p1
b2.bookName = "Java核心技术";//p2
b2.price = 100;
b2.author.setName("李四");
b2.author.setAge(36);
b1.display();
b2.display();
}
}

程序运行结果如下:
[color=blue]Java编程思想 99.0
Name:李四 Age:36
Java核心技术 100.0
Name:李四 Age:36[/color]
可以看出克隆后b2对象对其属性author所引用的Person对象的改动影响到了b1,这是由clone()方法的实现机制决定的——[b]clone()方法先在内存中开辟一块目标对象所需的存储空间(只要是同属于一个类型,则对象占有的存储空间大小也一定相同),然后直接将原始对象存储空间中的内容(包括各属性的值)原样拷贝过来[/b]。对基本类型的属性,其属性值就是真正要用的信息,这样的操作当然没有问题,但对于引用类型的属性,其值只是所引用的其他对象的句柄,这就导致clone后“副本”对象与原始对象的引用类型属性指向同样的对象,为更直观,现给出上述程序运行到关键点时的内存状态。如图1~3:

[img]http://dl.iteye.com/upload/attachment/307236/623075dd-44c0-3ebc-b601-976139e14136.jpg[/img]

[img]http://dl.iteye.com/upload/attachment/307238/083f923a-58c2-30f6-98f7-14e041abcbff.jpg[/img]

[img]http://dl.iteye.com/upload/attachment/307240/a31ff473-fbc5-3100-b2b5-413bce45d7af.jpg[/img]

这种不够彻底的拷贝也称浅度拷贝,浅度拷贝可能造成“原件”和“副本”对象之间的“藕断丝连”,往往导致我们所不希望的结果。与之相应的彻底拷贝操作被称为“深度拷贝”(Deep Copy),实现起来也不算困难,只需在拷贝目标对象时对与其有关联的对象,比如上例中要拷贝的Book对象通过其属性author所引用的Person3对象,也同时进行显式拷贝处理。
例如:[b][size=medium]深度拷贝[/size][/b]
源文件:Person3.java
源文件:Book1.java
public class Book1 implements Cloneable{
String bookName;
double price;
Person3 author;
public Book1(String bn,double p,Person3 a){
bookName = bn;
price = p;
author = a;
}
public Object clone(){
Book1 b = null;
try{
b = (Book1)super.clone();
}catch(CloneNotSupportedException e){
e.printStackTrace();
}
b.author = (Person3)author.clone();
return b;
}
public void display(){
System.out.println(bookName+"\t"+price+"\t");
author.display();
}
}

源文件:TestDeepCopy.java
public class TestDeepCopy{
public static void main(String args[]){
Book1 b1 = new Book1("Java编程思想",99,new Person3("张三",48));
Book1 b2 = (Book1)b1.clone();
b2.bookName = "Java核心技术";
b2.price = 100;
b2.author.setName("李四");
b2.author.setAge(36);
b1.display();
b2.display();
}
}

程序运行结果如下:
[color=blue]Java编程思想 99.0
Name:张三 Age:48
Java核心技术 100.0
Name:李四 Age:36[/color]

Object类中的wait()和notify()等方法也很有用,专门用于多线程编程中的线程同步性处理,以后再介绍。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值