- 浏览: 521856 次
- 性别:
- 来自: 杭州
-
文章分类
- 全部博客 (114)
- C基础 (1)
- C指针 (0)
- C语言库函数相关 (1)
- Linux (2)
- Linux网络编程 (1)
- PostgreSQL (0)
- Redis (2)
- Java Web (2)
- JAVA基础 (35)
- Ubuntu (8)
- Android (2)
- MySQL (3)
- 日志 (1)
- 书虫 (1)
- 数据结构 (0)
- 算法 (0)
- 开发工具 (1)
- 转载 (13)
- 英语 (18)
- tomcat启动脚本分析 (3)
- Oracle基础 (4)
- tomcat源码分析 (3)
- tomcat (1)
- Java相关 (1)
- Oracle基本原理--Oracle体系结构 (0)
- Oracle基本原理--表 (0)
- Oracle基本原理--索引 (0)
- Oracle基本原理--事务 (0)
- Oracle开发--SQL (1)
- Oracle基本原理--PL/SQL (0)
- Oracle基本原理--常用函数 (0)
- Oralce管理--用户及权限管理 (0)
- Oracle管理--安装调试 (0)
- Oracle管理--备份恢复 (0)
- Oralce管理--数据迁移 (0)
- Oracle管理--闪回 (0)
- Oracle管理--故障处理 (0)
- Oracle优化原理--统计信息 (0)
- Oracle优化原理--执行计划 (0)
- Oracle优化原理--诊断工具 (0)
- Oracle优化原理--深入理解表 (0)
- Oracle优化原理--深入理解索引 (0)
- Oracle优化原理--表连接原理 (0)
- Java--OOP (0)
- Java--异常 (0)
- Java--泛型 (0)
- Java--集合 (0)
- Java--IO (0)
- Java--枚举类型 (0)
- Java--注释 (0)
- Java--多线程 (0)
- Java--XML (0)
- Java--JDBC (3)
- Servlet (0)
- JSP (0)
- JSTL (0)
- 设计模式 (0)
- DAO与MVC (0)
- Javascript (2)
- Ajax (0)
- JQuery (0)
- HTML/CSS (0)
- 前端相关 (1)
- HTTP (0)
- TCP/IP (0)
- GO基础 (0)
最新评论
-
jsonmong:
推荐一个开发平台,采用的是插件化的设计思想,效果很不错的。ht ...
构建Java Web开发环境 -
wxm198427:
首先表示辛苦了!我想问个问题:我的是windows 7 x64 ...
Oracle 11g R2 for Win7旗舰版(64位)的安装步骤 -
握着橄榄枝的人:
我之前按照你的update mysql.user set pa ...
Windows7下MySQL5.5.20免安装版的配置 -
confident_f:
安装了32的客户端后,用plsql导入导出表有问题,生成不了d ...
Oracle 11g R2 for Win7旗舰版(64位)的安装步骤 -
confident_f:
安装数据库的时候第9步卡住了 是怎么回事呢?
Oracle 11g R2 for Win7旗舰版(64位)的安装步骤
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
程序运行结果如下:
false
false
Name:Tom Age:25
Name:Tom Age:18
查看java.lang.Cloneable接口大源代码,你会发现该接口中没有任何内容,其源代码如下:
这样的接口被称为空接口,实际上只是起到标识的作用——必须是该接口实现类的实例才能进行克隆操作,因此这样的接口也称“标记性接口”。
需要小心的是,使用上述的clone()方法进行对象拷贝可能出现“浅度拷贝”(Low Copy)的问题。
例如:浅度拷贝
源文件:Person3.java
源文件:Book.java
源文件:TestLowCopy.java
程序运行结果如下:
Java编程思想 99.0
Name:李四 Age:36
Java核心技术 100.0
Name:李四 Age:36
可以看出克隆后b2对象对其属性author所引用的Person对象的改动影响到了b1,这是由clone()方法的实现机制决定的——clone()方法先在内存中开辟一块目标对象所需的存储空间(只要是同属于一个类型,则对象占有的存储空间大小也一定相同),然后直接将原始对象存储空间中的内容(包括各属性的值)原样拷贝过来。对基本类型的属性,其属性值就是真正要用的信息,这样的操作当然没有问题,但对于引用类型的属性,其值只是所引用的其他对象的句柄,这就导致clone后“副本”对象与原始对象的引用类型属性指向同样的对象,为更直观,现给出上述程序运行到关键点时的内存状态。如图1~3:
这种不够彻底的拷贝也称浅度拷贝,浅度拷贝可能造成“原件”和“副本”对象之间的“藕断丝连”,往往导致我们所不希望的结果。与之相应的彻底拷贝操作被称为“深度拷贝”(Deep Copy),实现起来也不算困难,只需在拷贝目标对象时对与其有关联的对象,比如上例中要拷贝的Book对象通过其属性author所引用的Person3对象,也同时进行显式拷贝处理。
例如:深度拷贝
源文件:Person3.java
源文件:Book1.java
源文件:TestDeepCopy.java
程序运行结果如下:
Java编程思想 99.0
Name:张三 Age:48
Java核心技术 100.0
Name:李四 Age:36
Object类中的wait()和notify()等方法也很有用,专门用于多线程编程中的线程同步性处理,以后再介绍。
1、hashCode()方法
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()); } }
程序运行结果如下:
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()); }
该方法以字符串形式返回当前对象的有关信息,从其原始定义可以看出,所返回的是对象所属的类型名称及其哈希码。当使用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()); } }
程序运行结果如下:
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); }
其功能是比较当前对象和方法参数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)); } }
程序运行结果如下:
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)); } }
程序运行结果如下:
false
true
true
true
之所以这样处理,是因为在实际应用开发中,人们更关心的常常是两个字符串的内容是否相同,比如身份验证时输入的用户名/密码等是否与数据库中读取出来的注册信息相匹配,而不在乎是否是同一个对象,而文件的名称和存储路径以及时间等信息的性质也是如此。需要特别说明的是,String常量内容相同的话,在内存中将只保存一份。
例如:
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)); } }
程序运行结果如下:
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;
或者:
return (o instanceof Person1)&&(((Person1)o)age == this.age);
4、finalize()方法
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")}; } } } }
程序运行的类似结果如下(程序每次运行的结果可能不一样,内存不同的机器运行结果也是不一样的):
创建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;
其中的修饰符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; } }
源文件: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(); } }
程序运行结果如下:
false
false
Name:Tom Age:25
Name:Tom Age:18
查看java.lang.Cloneable接口大源代码,你会发现该接口中没有任何内容,其源代码如下:
package java.lang; public interface Cloneable(){ }
这样的接口被称为空接口,实际上只是起到标识的作用——必须是该接口实现类的实例才能进行克隆操作,因此这样的接口也称“标记性接口”。
需要小心的是,使用上述的clone()方法进行对象拷贝可能出现“浅度拷贝”(Low Copy)的问题。
例如:浅度拷贝
源文件: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(); } }
程序运行结果如下:
Java编程思想 99.0
Name:李四 Age:36
Java核心技术 100.0
Name:李四 Age:36
可以看出克隆后b2对象对其属性author所引用的Person对象的改动影响到了b1,这是由clone()方法的实现机制决定的——clone()方法先在内存中开辟一块目标对象所需的存储空间(只要是同属于一个类型,则对象占有的存储空间大小也一定相同),然后直接将原始对象存储空间中的内容(包括各属性的值)原样拷贝过来。对基本类型的属性,其属性值就是真正要用的信息,这样的操作当然没有问题,但对于引用类型的属性,其值只是所引用的其他对象的句柄,这就导致clone后“副本”对象与原始对象的引用类型属性指向同样的对象,为更直观,现给出上述程序运行到关键点时的内存状态。如图1~3:



这种不够彻底的拷贝也称浅度拷贝,浅度拷贝可能造成“原件”和“副本”对象之间的“藕断丝连”,往往导致我们所不希望的结果。与之相应的彻底拷贝操作被称为“深度拷贝”(Deep Copy),实现起来也不算困难,只需在拷贝目标对象时对与其有关联的对象,比如上例中要拷贝的Book对象通过其属性author所引用的Person3对象,也同时进行显式拷贝处理。
例如:深度拷贝
源文件: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(); } }
程序运行结果如下:
Java编程思想 99.0
Name:张三 Age:48
Java核心技术 100.0
Name:李四 Age:36
Object类中的wait()和notify()等方法也很有用,专门用于多线程编程中的线程同步性处理,以后再介绍。
发表评论
-
foreach循环
2013-06-24 16:15 1495从JDK1.5开始,Java提供了一个更简单的循环:forea ... -
可变参数
2013-06-24 15:38 1224从JDK1.5开始,Java允许使用可变参数为方法指定数量不确 ... -
泛型(core java 笔记)
2013-06-18 16:18 20741.为什么引入泛型 package generic; ... -
两个程序的说明
2010-10-19 09:26 11661、程序1的结果是: clas ... -
构造器初始化
2010-10-18 14:42 1535可以用构造器来进行初始化。在运行时刻,可以调用方法或执行某些动 ... -
成员初始化
2010-10-18 07:55 1247Java尽力保证:所有变量在使用前都能得到恰当的初始化。 对 ... -
线程的死锁
2010-10-11 19:21 1524当两个线程相互等待对方释放同步监视器时就会发生死锁,Java虚 ... -
线程的同步
2010-10-11 19:00 1290一个经典的关于线程安全性的问题:银行取钱问题。 银行取钱的基 ... -
java网站收集
2010-10-10 18:13 1295JAVA开发者最常去的25个英文网站:http://www.i ... -
控制线程
2010-10-10 16:06 20271、线程睡眠:sleep 如果我们需要让当前正在执行的线程暂 ... -
线程的状态
2010-09-28 19:00 1078线程从创建到执行完毕的整个过程称为线程的生命周期,在整个生命周 ... -
Java中Thread类的start()和run()的区别
2010-09-27 15:33 41391、start()方法来启动线程,真正实现了多线程运行,这时无 ... -
Java中创建线程的两种方法
2010-09-26 10:18 5648在Java中创建线程有两种方法:继承Thread类和实现Run ... -
创建String对象过程的内存分配小结
2010-09-23 20:32 2796常量池(Constant Pool):指的是在编译期被确定,并 ... -
Java堆和栈的区别 经典总结(转载)
2010-09-18 16:48 1289栈与堆都是Java用来在Ram中存放数据的地方。 与C++不 ... -
Java初学者都必须理解的七大问题
2010-09-18 10:36 1133问题一:我声明了什么 ... -
关于计算java程序运行时间(转载)
2010-09-18 09:22 1136//第一种,伪代码 long startTime= ... -
for循环的优化
2010-09-17 20:29 2159在程序中经常用到for循环,当一些算法实时性要求非常高时,对f ... -
详细解析Java中抽象类和接口的区别(转载)
2010-09-17 10:16 1127在Java语言中,abstract class和inter ... -
集合类(四):Map集合
2010-09-16 20:26 21555、Map集合 Map集合为映射类型,映射与集和列表有明显的区 ...
相关推荐
在Java中,抽象类和接口是抽象的表示形式,它们定义了共同的行为规范,但不提供具体实现。 压缩包中的文件名暗示了章节结构,可能对应于教材的不同章节: - **ch1**:通常介绍Java的基础知识,如安装环境、基本...
《Object Oriented Design in Java》是Mitchell Waite Signature Series中的一部著作,由Stephen Gilbert和Bill McCarty共同撰写。这本书深入探讨了如何在Java语言中进行面向对象设计(OOD),帮助开发者掌握这一...
Borror共同编著的一本关于Java编程的著作,主要探讨了面向对象编程(Object-Oriented Programming,OOP)在Java语言中的应用。面向对象编程是现代软件开发中的核心思想,Java作为一门强类型、面向对象的语言,其...
* 如果定义一个类没有 extends 关键字,那么这个类默认继承自 object 类,object 类是 Java 中所有类的直接或间接的父类,是 Java 中所有类的根类 重写(Override) 重写是 Java 中的一种机制,允许子类对从父类...
在 Java 中,Object 类定义和实现了 Java 系统所需要的众多类的共同行为,是所有类的根类,所有的类都是由这个类继承、扩充而来的。 派生类的定义 派生类(子类)定义的一般格式为: [ 类修饰符 ] class 子类名 ...
6. **抽象类与接口**:抽象类是不能实例化的类,用于定义共同接口或者提供部分实现。接口则是一种完全抽象的类型,只包含常量和抽象方法,可以实现多重继承效果。 7. **构造函数**:构造函数是类的一个特殊方法,...
Java中的类与继承是面向对象编程(Object-Oriented Programming, OOP)的核心概念,它们在构建复杂的软件系统中起到至关重要的作用。面向对象编程是一种编程范式,它基于对象和类的概念,通过继承、封装和多态等特性...
`String`类是处理文本字符串的基石,而`Object`类则是所有Java类的父类,提供了诸如`equals()`、`hashCode()`和`toString()`等基本方法。`System`类提供了与操作系统交互的功能,如获取当前时间、读写标准输入输出等...
面向对象编程(Object-Oriented Programming, OOP)是Java的核心特性,它通过类、对象、继承、封装、多态等概念来组织代码,提高可读性和可维护性。 1. **面向对象编程**: - **类与对象**:类是具有共同属性和...
14. **Object类的作用**: `Object`类是所有Java类的根类,提供了一些基础方法,如`equals()`、`hashCode()`、`toString()`等。它的构造方法`Object()`主要用作创建一个对象的默认初始化。 以上是关于Java编程中的...
`Object`类的父类为`null`。 - **`public java.net.URL getResource(String name)`**:根据名称获取资源。 - **其他方法** - `public boolean isEnum()`:判断是否为枚举类型。 - `public native boolean ...
8. `Object`类:所有Java类都隐式地继承自`java.lang.Object`类,这是Java中的顶级父类。通过`Object`数组,可以存储不同类型的对象,体现了多态性。 9. 私有成员的隐藏:父类的私有成员对子类不可见,这意味着子类...
`Object`类是所有Java类的父类,而`System`类提供了系统级的服务,例如获取当前时间或打印输出。 2. **java.io**:这个包提供了输入和输出的功能,包括文件操作、流处理和数据转换。例如,`File`类用于处理文件,`...
Java中的Object类作为所有类的共同祖先,其提供的几个基础方法对于Java对象的操作至关重要。理解这些方法的作用和如何适当地重写它们,对于编写可读性好、易于维护的代码十分重要。学会正确地使用getClass()、...
13. Java类的父类:所有Java类的父类是`Object`,选项D正确。 14. Java程序段输出:程序段将字符'f'和'k'相减得到5,因此选项D正确。 15. 八进制数:在Java中,以0开头的数字表示八进制,所以026是八进制,但028...
在Java中,一个类可以实现多个接口,但只能继承一个父类(除了Object类,它是所有类的最终父类)。 继承是类之间的“is-a”关系,允许一个类(子类)继承另一个类(父类)的属性和方法。子类可以重写父类的方法,以...
`Object` 类是所有 Java 类的顶级父类。类的访问权限修饰符决定了子类能访问父类的哪些成员。方法的重写(覆盖)是在子类中改进或扩展父类方法的功能,以满足特定需求。重写时需注意保持方法签名的一致性,并遵循...
- 每个没有指定父类的类,默认都会继承Java的根类`Object`。 2. **子类继承父类的结果**: - 子类继承了父类的所有非私有属性和方法,包括受保护的和公共的。 - 子类可以在同一包或不同包内调用父类的相应访问...