构造器陷阱(序列化恢复Java对象,clone复制Java对象,无限递归的构造器)

来源:互联网 发布:视频软件 编辑:程序博客网 时间:2024/06/05 07:00

1.1  构造器的陷阱

1.构造器不能声明返回值类型,也不能使用void声明构造器没有返回值。当为构造器声明添加任何返回值类型声明,或者添加void声明该构造器没有返回值时,编译器并不会提示这个构造器有错误,只是系统会把这个所谓的“构造器”当成普通方法处理。这个时候初始化类实例时系统会调用默认的无参数的构造器。

2.构造器创建对象吗?大部分java书籍都笼统的说:通过构造器来创建一个java对象。这样容易给人一个感觉,构造器负责创建java对象。但是实际上构造器并不会创建java对对象,构造器只是负责执行初始化,在构造器执行之前,java对象所需要的内存空间,应该说是有new关键字申请出来的。

3.绝大部分,程序使用new关键字为一个java对象申请空间之后,都需要使用构造器为这个对象执行初始化。但在某些时候,程序创建java对象无需调用构造器,以下两种方式创建java对象无需使用构造器

(1)使用反序列化的方式恢复java对象

(2)使用clone方法复制java对象

1.1.1使用序列化恢复java对象无需构造器:

 

import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.Serializable;public class Student implements Serializable {//注意必须实现序列化接口private String name;public Student(String name) {System.out.println("调用构造器");this.name = name;}public boolean equals(Object object) {if (this == object) {return true;}if (object.getClass() == Student.class) {Student s = (Student) object;return s.name.equals(this.name);}return false;}public int hashCode() {return name.hashCode();}public static void main(String args[]) throws Exception {Student student_new = new Student("liyafang");Student student_recover = null;ObjectOutputStream oos = null;ObjectInputStream ois = null;try {//创建对象输出流oos = new ObjectOutputStream(new FileOutputStream("liyafang.txt"));//创建对象输入流ois = new ObjectInputStream(new FileInputStream("liyafang.txt"));//序列化输出由构造器初始化的java对象oos.writeObject(student_new);oos.flush();//反序列化恢复java对象student_recover = (Student) ois.readObject();//两个对象的实例变量值完全相等,下面输出为trueSystem.out.println(student_recover.equals(student_new));//两个对象不同,下面输出falseSystem.out.println(student_recover == student_new);} finally {if (oos != null) {oos.close();}if (ois != null) {ois.close();}}}}

由此可见,程序完全通过这种反序列化机制确实会破坏单例类的规则,当然,大部分时候不会主动使用反序列化技术去破坏单例规则的。如果想保证反序列化时也不会产生多个java实例,则应该为单例类提供readResolve()方法,该方法保证反序列化时得到已有的java对象。

以下是完美的单例模式:

import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.ObjectStreamException;import java.io.Serializable;public class President implements Serializable {// 注意必须实现序列化接口private static President instance;private String name;private President(String name) {System.out.println("调用构造器");this.name = name;}//注意这是lazy的单例模式,要使用同步方法,否则多线程情况下会出现多个实例。public synchronized static President getInstance() {if (instance == null) {instance = new President("hejicheng");}return instance;}        //该方法保证反序列化时得到已有的java对象。当JVM反序列化地恢复一个新对象时,//系统对自动调用这个readResolve()方法返回指定的对象,从而保证系统通过反序//列化机制不会产生多个java对象。private Object readResolve() throws ObjectStreamException {return this.instance;}public static void main(String args[]) throws Exception {President president_new = President.getInstance();President president_recover = null;ObjectOutputStream oos = null;ObjectInputStream ois = null;try {oos = new ObjectOutputStream(new FileOutputStream("liyafang.txt"));ois = new ObjectInputStream(new FileInputStream("liyafang.txt"));oos.writeObject(president_new);oos.flush();president_recover = (President) ois.readObject();System.out.println(president_recover.equals(president_new));//为trueSystem.out.println(president_recover == president_new);//为true} finally {if (oos != null) {oos.close();}if (ois != null) {ois.close();}}}}

1.1.2使用clone()方法复制java对象也无需构造器:

public class Student implements Cloneable {//注意必须Cloneable接口private String name;    private int age;public Student(String name,int age) {System.out.println("调用构造器");this.name = name;}public Object clone(){Student s = null;try {s = (Student)super.clone();} catch (CloneNotSupportedException e) {// TODO Auto-generated catch blocke.printStackTrace();}return s;}public boolean equals(Object object) {if (this == object) {return true;}if (object.getClass() == Student.class) {Student s = (Student) object;return s.name.equals(this.name)&&s.age==(this.age);}return false;}public int hashCode() {return name.hashCode()*17+age;}public static void main(String args[]) throws Exception {Student student_new = new Student("liyafang",22);Student student_clone = (Student)student_new.clone();//没有调用构造器System.out.println(student_new.equals(student_clone));//trueSystem.out.println(student_new==(student_clone));//false}}

补充:

当对象作为集合里的key时,需要复写equals()和hashCode()方法

Set的存储机制是equals与hashcode相结合的。一般ADD一个对象会先根据equals方法判断与其他对象是否相等,因为Set是不允许重复add的。如你不覆盖equals方法,JAVA默认所有的对象都是不同的,也就是它们的内存地址。假如你NEW一个对象,人,你认为只要它们名字相同就是同一个对象,此时你就需要覆盖equals方法了,否则同名也是两个对象。java先通过equals方法判断存储位置,如果不同直接存入;如果通过equals方法比较现在要存入的对象与集合中的某个对象相等,那么它就会再根据hashcode来判断它们是否hashcode也相等,如果相等那就存不进去了,说明它们确实是同一个对象,不等就可存入。所以一般在写程序的时候,两个对象你认为它们不同就去覆盖equals方法。这样可以提高效率,不要让JAVA再去判断hashcode

 

1.2  无限递归的构造器

public class ConstrucorRecursion {ConstrucorRecursion cr;{cr = new ConstrucorRecursion();}public ConstrucorRecursion() {System.out.println("程序执行无参数的构造函数");}public static void main(String args[]) {ConstrucorRecursion rc = new ConstrucorRecursion();}}


 

运行结果:

Exception in thread "main" java.lang.StackOverflowError

         at ConstrucorRecursion.<init>(ConstrucorRecursion.java:4)

         at ConstrucorRecursion.<init>(ConstrucorRecursion.java:4)

原因:

表面上看,程序没有什么问题,ConstrucorRecursion类的构造器中没有任何代码,它的构造器中只有一行简单的输出语句。但是不要忘记了,不管是定义实例变量时指定的初始值,还是在非静态初始化块中执行的初始化操作,最终都将提取到构造器中执行。

教训:

尽量不要在定义实例变量时指定实例变量的值为当前类的实例。

尽量不要初始化块中创建当前类的实例。

尽量不要在构造器内调用本构造器创建java对象。

1.3  持有当前类的实例

但在某些情况下,程序必须让某个类的一个实例持有当前类的另一个实例,例如链表,每个节点都持有一个引用,该引用指向下一个链表节点。

以下是一个简单的链表结构:

public class LinkNode {String name;LinkNode node;public LinkNode(){};public String getName() {return name;}public LinkNode getNode() {return node;}public LinkNode(String name) {this.name = name;this.node = new LinkNode();}public static void main(String args[]) {LinkNode n1 = new LinkNode("软件0801班");LinkNode n2 = new LinkNode("软件0802班");LinkNode n3 = new LinkNode("软件0803班");n1.node = n2;n2.node = n3;System.out.println(n1.getNode().getNode().getName());}}


 

	
				
		
原创粉丝点击