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()); } }
构造器陷阱(序列化恢复Java对象,clone复制Java对象,无限递归的构造器)
最新推荐文章于 2022-01-23 22:30:25 发布