2、构造器的陷阱
构造器是Java每个类都会提供的一个“特殊方法”。构造器负责对Java对象执行初始化操作,不管是定义实例变量时指定的初始值,还是在非静态初始化块中所执行的操作,实际上都会被提取到构造器中来执行。
2、1 构造器返回类型
构造器不能声明返回值类型,也不能使用void声明构造器没有返回值。当为构造器声明添加任何返回值类型声明,或者添加void声明该构造器没有返回值时,编译器不会提示这个构造器有错误,只是把这个所谓的“构造器”当成普通方法处理。
2、2 创建对象
实际上构造器并不会创建Java对象,构造器只负责执行初始化,在构造器执行之前,Java对象所需要的内存空间,应该是由new关键字申请的。绝大部分时候,程序使用new关键字为一个Java对象申请空间之后,都需要使用构造器为这个对象执行初始化。但在某些时候,程序创建Java对象无须调用构造器,以下两种方式创建java对象无须使用构造器。
- 使用反序列化的方式恢复Java对象。
- 使用clone方法复制Java对象。
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
class Person implements Serializable{
private String name;
public Person(String name) {
System.out.println("调用有参数的构造器");
this.name = name;
}
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj.getClass() == Person.class) {
Person target = (Person)obj;
return target.name.equals(this.name);
}
return false;
}
public int hashCode() {
return name.hashCode();
}
}
public class SerializebleTest{
public static void main(String[] args) throws Exception {
Person p = new Person("张三");
System.out.println("Person对象创建完成");
Person p1 = null;
try (
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("F:\\优快云博客\\a.txt"));
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("F:\\优快云博客\\a.txt"));
)
{
oos.writeObject(p);
oos.flush();
p1 = (Person)ois.readObject();
System.out.println(p.equals(p1));
System.out.println(p == p1);
}
}
}
输出结果为:
调用有参数的构造器
Person对象创建完成
true
false
Person对象创建完成
true
false
当创建Person对象时,程序调用了相应的构造器来对该对象执行初始化;当程序通过反序列化机制恢复Java对象时,系统无须再调用构造器来执行初始化。通过反序列化机制恢复出来的Person对象当然和原来的Person对象具有完全相同的实例变量值,但系统会产生两个Person对象。
注意
:程序完全可以通过反序列化机制破坏单例类的规则。如果真的想要保证反序列化也不会产生多个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;
class Singleton implements Serializable {
private static Singleton instance;
private String name;
private Singleton(String name) {
System.out.println("调用有参数的构造器");
this.name = name;
}
public static Singleton getInstance(String name) {
if (instance == null) {
instance = new Singleton(name);
}
return instance;
}
private Object readResolve() throws ObjectStreamException {
return instance;
}
}
public class SingletonTest {
public static void main(String[] args) throws Exception{
Singleton s = Singleton.getInstance("张三");
Singleton s1 = null;
try (
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("F:\\优快云博客\\b.txt"));
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("F:\\优快云博客\\b.txt"));
) {
oos.writeObject(s);
oos.flush();
s1 = (Singleton)ois.readObject();
System.out.println(s == s1);
}
}
}
输出结果为:
调用有参数的构造器
true
true
上面程序为Singleton类提供了readResolve()方法,当JVM反序列化回复一个对象时,系统会自动调用该方法,从而保证系统通过序列化机制不会产生多个Java对象。
除了可以使用反序列化机制恢复Java对象无须构造器之外,使用clone()方法复制Java对象也无须调用构造器。如果希望某个Java类的实例是可复制的,则对该Java类有如下两个要求。
- 让该Java类实现Cloneable接口。
- 为该Java类提供clone()方法,则方法负责进行复制。
class Dog implements Cloneable {
private String name;
private double weight;
public Dog(String name , double weight) {
this.name = name;
this.weight = weight;
}
public Object clone() {
Dog dog = null;
try {
dog = (Dog)super.clone();
}
catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return dog;
}
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj.getClass() == Dog.class) {
Dog target = (Dog)obj;
return target.name.equals(this.name) && target.weight == this.weight;
}
return false;
}
}
public class CloneTest {
public static void main(String[] args) {
Dog dog = new Dog("Blue" , 22.5);
Dog dog1 = (Dog)dog.clone();
System.out.println(dog1.equals(dog));
System.out.println(dog1 == dog);
}
}
输出结果为:
true
false
false
通过clone()方法复制出来的Dog对象和原来的Dog对象具有完全相同的实例变量值,但系统中将会产生两个Dog对象。
注意:
- 尽量不要在定义实例变量时指定实例变量的值为当前类的实例。
- 尽量不要在初始化块中创建当前类的实例。
- 尽量不要在构造器内调用本构造器创建Java对象。