java.lang.Object类是所有Java类的最高层次父类,该类中没有定义任何属性,方法也只有几个,但正是这些方法提供了面向对象编程技术的基本机制,下面将分别介绍:
1、hashCode()方法
hashCode()方法的格式如下:
其功能是返回当前对象的哈希码(HashCode)数值,哈希码可以理解为系统为每个Java对象自动创建的整型编号,任何两个不同的Java对象的哈希码一定不同,而在Java应用程序的一次执行期间,在同一个对象上多次调用hashCode()方法时,必须一致的返回相同的整数。这样哈希码就可以起到标识对象的功能,引用类型变量所记录的对象句柄实际上就包含了该对象的哈希码信息。例如:测试hashCode()方法
源文件:Person.java
源文件:TestHashCode.java
程序运行结果如下:
374283533
164f1d0d
Person@164f1d0d
------------
603737068
程序中,变量p1和p2所引用的两个Person类型对象虽然内容相同(属性age的值相同),但其哈希码不同,且保存在不同的空间中。还可看出,使用System.out.println()方法直接打印引用类型数据时输出的信息中包含了对象的哈希码信息,这一点在下面的toString()方法中会做解释。
2、toString()方法
Object类中toString()方法的原始定义如下:
该方法以字符串形式返回当前对象的有关信息,从其原始定义可以看出,所返回的是对象所属的类型名称及其哈希码。当使用System.out.println()方法直接打印输出引用类型变量时,println()方法中会自动调用其toString()方法,再将所返回的字符串信息输出到屏幕上。
如:
等价于:
由于Java语言中允许子类对父类中继承来的方法进行重写,以改变其实现细节,因此我们也可以根据需要在自己定义的Java类中重写其toString()方法,以提供更适合的说明信息。
例如:重写toString()方法
源文件:Person.java
源文件:TestOverride.java
程序运行结果如下:
374283533
This is a instance of Person,age=18
This is a instance of Person,age=18
回想一下,我们以前使用System.out.println()方法直接打印输出java.lang.String、java.util.Data等类型数据时输出的也不是对象的哈希码,而是更有意义的字符串信息,原理也是相同的——这些类中也根据需要重写了各自的toString()方法。
3、equals()方法
Object类中equals()方法的原始定义如下:
其功能是比较当前对象和方法参数obj所引用的对象两者的等价性,如果等价则返回值为true,否则返回false。在进一步讲解之前,让我们先明确Java语言中的等价性标准:基本类型数据比较的是数据的值,而引用数据类型比较的则是对象的句柄,即对象的hashCode编码或者说引用类型变量的值,而非对象本身。简单地说,比较的永远是变量的值是否相等。
例如:“==”和equals()方法使用举例1
源文件:Person.java
源文件:TestEquals1.java
程序运行结果如下:
true
fals
fals
true
true
可以看出比较引用类型数据的等价性时,其标准比较苛刻,只有当两个引用变量的值相等,实际上是指向同一个对象时才算做等价。看起来使用“==”运算符与equals()方法效果似乎相同,而前者还能够判断基本数据类型数据的等价性,那么equals()方法就显得多余了,其实不然,equals()方法在比较一些特定的引用类型(如java.lang.String、java.io.File、java.util.Date以及封装类)数据时,允许改变先前严格的等价性标准——只有两个对象同为上述的特例类型且其内容相同(对象各自封装的属性值对应相同),equals()方法即判为等价,而“==”判断则不存在任何“变通”的可能。
例如:“==”和equals()方法使用举例2
程序运行结果如下:
false
true
true
true
之所以这样处理,是因为在实际应用开发中,人们更关心的常常是两个字符串的内容是否相同,比如身份验证时输入的用户名/密码等是否与数据库中读取出来的注册信息相匹配,而不在乎是否是同一个对象,而文件的名称和存储路径以及时间等信息的性质也是如此。需要特别说明的是,String常量内容相同的话,在内存中将只保存一份。
例如:
运行结果都为“true”。
其实,要实现上述“特例”并不困难,只需在Object的子类中重写其equals()方法,给出用户定义的等价性标准就是了,我们也可以在应用开发时根据需要进行类似处理。
例如:用户自定义等价性标准
源文件:Person1.java
源文件:TestEquals3.java
程序运行结果如下:
false
true
上述程序中Person1类中重写了equals()方法,重新定义了Person1类型数据的等价性判定标准——只要对象均为Person1类型且其age属性值相等,则认为等价,无论是否为同一个对象。其equals()方法体中的代码也可简化为:
或者:
4、finalize()方法
Java运行时环境中的垃圾收集器在销毁一个对象之前,会自动调用该对象的finalize()方法,然后才释放对象的内存空间,该方法在Object类中的原始定义如下:
请注意,这里finalize()方法修饰符是protected,而不是public,这种访问控制等级使得在外界(子类以外的范围)对于该方法是不可见的,相信读者能够理解,要使其发挥作用,应该在子类中重写finalize()方法,而且,重写方法的修饰符应改为public,否则重写仍然没有实际意义。finalize()方法的用途是在子类中重写,以加入所需的逻辑代码来配置系统资源或执行其他清除操作。
例如:
源文件:Person2.java
源文件:TestFinalize.java
程序运行的类似结果如下(程序每次运行的结果可能不一样,内存不同的机器运行结果也是不一样的):
创建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
程序中的内层for循环起到消耗内存空间的作用,读者可能奇怪为什么创建10个Person2类的对象,却只销毁了其中两个,其实Java虚拟机的垃圾回收操作对于应用程序而言是完全透明的——程序无法预料或精确控制某个无用对象何时被销毁,也就无法控制其的finalize()方法的调用时机,而且,除非垃圾回收器认为程序的可用内存空间已经不足,否则它不会试图释放无用对象占用的内存的内存。换句话说,下述情况是完全可能发生的:一个程序只占用了少量的内存,于是垃圾回收器没有在程序运行的过程中销毁无用对象并释放它们所占用的内存,也就没有调用过这些对象的finalize()方法,程序就终止了。不必担心,JVM最终关闭时还是会释放其所占用的所有内存空间。
由于finalize()方法最终是否会执行,以及何时会执行都是不确定的,在应用程序层面无法精确控制和干预,即使在应用程序中显式调用System.gc()或Runtime.gc()方法强制系统清理无用内存空间,也不能保证这一点,因此finalize()方法并不可靠,在应用程序开发中不建议使用。
5、clone()方法
在应用开发过程中,我们可能会需要拷贝(copy,复制)一个现有的对象,即得到一个新对象并希望其与现有对象封装完全相同的信息(属性值),主要是为了此后两者互不相干,修改其中的一个对象不会影响到另一个,我们知道,简单地进行引用变量间的赋值是不能解决问题的,因为并没有创建新对象;而自己编写代码先创建一个新对象,再将原始对象的属性值一一复制过来也比较烦琐,且存在后述的“浅度拷贝”问题;这种情况下,利用clone()方法来实现对象拷贝不失为一种明智的选择。
Object类中的clone()方法专门提供拷贝当前对象的功能,其原型如下:
其中的修饰符native标明此方法是一个本地方法,即调用了其运行时所在平台/操作系统的底层功能,当然这是早就实现好的,读者不必为此分心。该方法能够创建并返回当前对象的一个副本,可以理解为将当前对象的所有信息(一段连续的内存空间中存储的数据)直接复制一份并单独保存,因此其返回的是已经包含了原有对象信息的一个新对象,而不是原有对象的引用。
和finalize()方法类似,clone()方法在Object类中也被定义为protected的,因此只有在其子类中进行重写才能真正发挥作用,Java语言规定,所有要进行“克隆”的对象所属的类必须实现java.lang.Cloneable接口,这是一种安全性保护。
例如:实现简单的克隆操作
源文件:Person3.java
源文件:TestClone.java
1、hashCode()方法
hashCode()方法的格式如下:
- public int 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;
- }
- }
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());
- }
- }
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());
}
}
程序运行结果如下:
374283533
164f1d0d
Person@164f1d0d
------------
603737068
程序中,变量p1和p2所引用的两个Person类型对象虽然内容相同(属性age的值相同),但其哈希码不同,且保存在不同的空间中。还可看出,使用System.out.println()方法直接打印引用类型数据时输出的信息中包含了对象的哈希码信息,这一点在下面的toString()方法中会做解释。
2、toString()方法
Object类中toString()方法的原始定义如下:
- public String toString (){
- return getClass().getName()+”@”+Integer.toHexString(hashCode());
- }
public String toString (){
return getClass().getName()+”@”+Integer.toHexString(hashCode());
}
该方法以字符串形式返回当前对象的有关信息,从其原始定义可以看出,所返回的是对象所属的类型名称及其哈希码。当使用System.out.println()方法直接打印输出引用类型变量时,println()方法中会自动调用其toString()方法,再将所返回的字符串信息输出到屏幕上。
如:
- System.out.println(p1);
System.out.println(p1);
等价于:
- System.out.println(p1.toString());
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;
- }
- }
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());
- }
- }
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());
}
}
程序运行结果如下:
374283533
This is a instance of Person,age=18
This is a instance of Person,age=18
回想一下,我们以前使用System.out.println()方法直接打印输出java.lang.String、java.util.Data等类型数据时输出的也不是对象的哈希码,而是更有意义的字符串信息,原理也是相同的——这些类中也根据需要重写了各自的toString()方法。
3、equals()方法
Object类中equals()方法的原始定义如下:
- public boolean equals(Object obj){
- return (this==obj);
- }
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;
- }
- }
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));
- }
- }
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));
}
}
程序运行结果如下:
true
fals
fals
true
true
可以看出比较引用类型数据的等价性时,其标准比较苛刻,只有当两个引用变量的值相等,实际上是指向同一个对象时才算做等价。看起来使用“==”运算符与equals()方法效果似乎相同,而前者还能够判断基本数据类型数据的等价性,那么equals()方法就显得多余了,其实不然,equals()方法在比较一些特定的引用类型(如java.lang.String、java.io.File、java.util.Date以及封装类)数据时,允许改变先前严格的等价性标准——只有两个对象同为上述的特例类型且其内容相同(对象各自封装的属性值对应相同),equals()方法即判为等价,而“==”判断则不存在任何“变通”的可能。
例如:“==”和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));
- }
- }
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));
}
}
程序运行结果如下:
false
true
true
true
之所以这样处理,是因为在实际应用开发中,人们更关心的常常是两个字符串的内容是否相同,比如身份验证时输入的用户名/密码等是否与数据库中读取出来的注册信息相匹配,而不在乎是否是同一个对象,而文件的名称和存储路径以及时间等信息的性质也是如此。需要特别说明的是,String常量内容相同的话,在内存中将只保存一份。
例如:
- String s1 = "abc";
- String s2 = "abc";
- System.out.println(s1 == s2);
- System.out.println(s1.equals(s2));
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;
- }
- }
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));
- }
- }
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));
}
}
程序运行结果如下:
false
true
上述程序中Person1类中重写了equals()方法,重新定义了Person1类型数据的等价性判定标准——只要对象均为Person1类型且其age属性值相等,则认为等价,无论是否为同一个对象。其equals()方法体中的代码也可简化为:
- if(o instanceof Person1){
- Person1 p = (Person1)o;
- if(this.age == p.age)
- return true;
- }
- return false;
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);
return (o instanceof Person1)&&(((Person1)o)age == this.age);
4、finalize()方法
Java运行时环境中的垃圾收集器在销毁一个对象之前,会自动调用该对象的finalize()方法,然后才释放对象的内存空间,该方法在Object类中的原始定义如下:
- protected void finalize() throws Throwable{}
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);
- }
- }
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")};
- }
- }
- }
- }
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")};
}
}
}
}
程序运行的类似结果如下(程序每次运行的结果可能不一样,内存不同的机器运行结果也是不一样的):
创建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
程序中的内层for循环起到消耗内存空间的作用,读者可能奇怪为什么创建10个Person2类的对象,却只销毁了其中两个,其实Java虚拟机的垃圾回收操作对于应用程序而言是完全透明的——程序无法预料或精确控制某个无用对象何时被销毁,也就无法控制其的finalize()方法的调用时机,而且,除非垃圾回收器认为程序的可用内存空间已经不足,否则它不会试图释放无用对象占用的内存的内存。换句话说,下述情况是完全可能发生的:一个程序只占用了少量的内存,于是垃圾回收器没有在程序运行的过程中销毁无用对象并释放它们所占用的内存,也就没有调用过这些对象的finalize()方法,程序就终止了。不必担心,JVM最终关闭时还是会释放其所占用的所有内存空间。
由于finalize()方法最终是否会执行,以及何时会执行都是不确定的,在应用程序层面无法精确控制和干预,即使在应用程序中显式调用System.gc()或Runtime.gc()方法强制系统清理无用内存空间,也不能保证这一点,因此finalize()方法并不可靠,在应用程序开发中不建议使用。
5、clone()方法
在应用开发过程中,我们可能会需要拷贝(copy,复制)一个现有的对象,即得到一个新对象并希望其与现有对象封装完全相同的信息(属性值),主要是为了此后两者互不相干,修改其中的一个对象不会影响到另一个,我们知道,简单地进行引用变量间的赋值是不能解决问题的,因为并没有创建新对象;而自己编写代码先创建一个新对象,再将原始对象的属性值一一复制过来也比较烦琐,且存在后述的“浅度拷贝”问题;这种情况下,利用clone()方法来实现对象拷贝不失为一种明智的选择。
Object类中的clone()方法专门提供拷贝当前对象的功能,其原型如下:
- protected native Object clone() throws CloneNotSupportedException;
protected native Object clone() throws CloneNotSupportedException;
其中的修饰符native标明此方法是一个本地方法,即调用了其运行时所在平台/操作系统的底层功能,当然这是早就实现好的,读者不必为此分心。该方法能够创建并返回当前对象的一个副本,可以理解为将当前对象的所有信息(一段连续的内存空间中存储的数据)直接复制一份并单独保存,因此其返回的是已经包含了原有对象信息的一个新对象,而不是原有对象的引用。
和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;
- }
- }
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