Java 创建对象的四种方式
- 通过new关键字生成对象
- 通过java反射机制创建对象
- 通过clone对象生成新对象
- 通过序列化生成新对象
通过new关键字生成对象
新建一个Student类,如下
public class Student{
private String name;
private Float grade;
public Student(){
}
public Student(String name, Float grade){
this.name = name;
this.grade = grade;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Float getGrade() {
return grade;
}
public void setGrade(Float grade) {
this.grade = grade;
}
}
通过new关键字创建对象
Student s = new Student(); //调用无参构造器
Student s2 = new Student("Han Meimei", 15f) //调用有参构造器
通过java反射机制创建对象
使用Class.getInstance()
创建新的对象,如下
Student s3 = Student.class.getInstance();
Student s4 = (Student)Class.forName("Student").getInstance();
使用这种方式是调用的类的无参构造器生成的对象,如果想要调用有参构造器生成对象,应该怎么办?
使用Class.getConstructor()
方法
Constructor<Student> studentConstructor = Student.class.getConstructor(String.class, Float.class);
Student s5 = studentConstructor.newInstance("Han Meimei", 15f);
查看Class.getInstance()
方法,内部实现是通过调用类的无参构造器生成新的对象的。
所以使用Class.getInstance()
方法,要确保类有public的无参构造器。
通过clone对象生成新对象
要使用Object.clone()
生成新对象,类必须实现接口Cloneable
,并且重写clone()
方法。
修改Student如下
public class Student implements Cloneable{
...
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
//重写toString方便通过打印检查结果
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", grade=" + grade +
'}';
}
}
使用clone()
生成新的类,如下
Student s6 = (Student)s5.clone();
System.out.println(s6.toString());
可以查看命令行,新生成的对象s6完整复制了s5的属性
Student{name='Han Meimei', grade=15.0}
如果给Student新加一个属性,List<String> subjects
表示学生参加的学科的列表。这时候调用super.clone()
,能成功拷贝s5的subjects属性里的值到s6里了么?
修改Student类添加属性subjects和对应个getter,setter方法如下
public class Student implements Cloneable{
...
private List<String> subjects;
...
public List<String> getSubjects() {
return subjects;
}
public void setSubjects(List<String> subjects) {
this.subjects = subjects;
}
}
给s5的subjects属性赋值,并且通过clone()
重新生成s6
List<String> subjects = new ArrayList<String>();
subjects.add("语文");
subjects.add("数学");
s5.setSubjects(subjects);
Student s6 = (Student) s5.clone();
System.out.println(s6.toString());
查看s6,s6成功复制了s5的subjects属性
Student{name='Han Meimei', grade=15.0, subjects=[语文, 数学]}
但是我们发现s6比s5多修了一门学科-英语,所以需要修改s6
的subjects。
s6.getSubjects().add("英语");
System.out.println(s6.toString());
查看s6
Student{name='Han Meimei', grade=15.0, subjects=[语文, 数学, 英语]}
这时候我们再打印s5看看
System.out.println(s5);
命令行输出
Student{name='Han Meimei', grade=15.0, subjects=[语文, 数学, 英语]}
发现s5的subjects属性也改变了!!
这个地方就是一个要注意的坑,查看clone()
源码的注释发现:
* performs a "shallow copy" of this object, not a "deep copy" operation.
什么意思呢?
这个是浅拷贝,不是深拷贝。当拷贝s5的subjects属性只是拷贝了引用。s6的subjects属性和s5的subjects属性指向的是同一块地址,修改其中一个内容,必会导致另一个的修改。
如何解决这个问题?
重新修改clone()
方法,
@Override
public Object clone() throws CloneNotSupportedException {
Student newStudent = (Student) super.clone();
if(null != this.getSubjects()){
List<String> newSubjects = new ArrayList<>();
newSubjects.addAll(this.getSubjects());
newStudent.setSubjects(newSubjects);
}
return newStudent;
}
重新执行拷贝的代码
Student s6 = (Student) s5.clone();
s6.getSubjects().add("英语");
System.out.println(s6);
System.out.println(s5);
查看命令行输出
Student{name='Han Meimei', grade=15.0, subjects=[语文, 数学, 英语]}
Student{name='Han Meimei', grade=15.0, subjects=[语文, 数学]}
s5的subjects属性并没有改变。
通过序列化生成新对象
通过序列化生成新对象,类必须实现接口Serializable
修改Student类如下
public class Student implements Cloneable, Serializable{
private static final long serialVersionUID = 1L;
...
}
我们将s6转化成二进制写入”Student6.obj”文件中,再通过读取该文件生成新的对象s7
try(ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("Student6.obj"));
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("Student6.obj"))){
objectOutputStream.writeObject(s6);
Student s7 = (Student) objectInputStream.readObject();
System.out.println(s7);
} catch (IOException e) {
e.printStackTrace();
}
查看命令行输出,我们可以看到s7完整生成了。
Student{name='Han Meimei', grade=15.0, subjects=[语文, 数学, 英语]}