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

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();
			//两个对象的实例变量值完全相等,下面输出为true
			System.out.println(student_recover.equals(student_new));
			//两个对象不同,下面输出false
			System.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));//为true
			System.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 block
			e.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));//true
		System.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());
	}
}


 





  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值