1、调用new语句创建对象,最常见的一种
2、运用反射手段创建对象,调用java.lang.Class 或者 java.lang.reflect.Constructor 类的newInstance()实例方
3、调用对象的clone()方法
4、使用反序列化,调用java.io.ObjectInputStream 对象的 readObject()方法.
1、直接赋值 2、浅拷贝 3、深拷贝
实体类Person.java
public class Person {
//姓名
private String name;
// 年龄
private int age;
// 编号
private Integer code;
public Person(){
}
public Person(String name, int age, Integer code) {
this.name = name;
this.age = age;
this.code = code;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
}
测试类cloneDemo.java
本篇所有测试均会在该类下完成
public class cloneDemo {
public static void main(String[] args) {
Person p = new Person("小明",11,123);
Person p1 = p;
System.out.println(p);
System.out.println(p1);
}
}
打印结果:
Others.base.cloneDemo.Person@4554617c
Others.base.cloneDemo.Person@4554617c
两者打印出来的地址相同,可见二者的引用是同一个对象,并没有创建出一个新的对象。可以把这种现象叫做引用的复制(或引用拷贝)。
对Person.java追加toString方法,
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", code=" + code +
'}';
}
cloneDemo.java中追加
p1.setCode(1234);
System.out.println("--------windcoder.com----------");
System.out.println(p);
System.out.println(p1);
从打印结果中更能体现出这一点,因为是同一个对象的引用,所以两者改一个,另一个对象的值也随之改变:
Person{name='小明', age=11, code=123}
Person{name='小明', age=11, code=123}
--------windcoder.com----------
Person{name='小明', age=11, code=1234}
Person{name='小明', age=11, code=1234}
浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。
如果属性是基本类型,拷贝的就是基本类型的值;
如果属性是内存地址(引用类型),拷贝的就是内存地址(即复制引用但不复制引用的对象) ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。
实现对象拷贝的类,必须实现Cloneable接口,并覆写clone()方法。
下面改造Person.java
/**
* 实现对象拷贝的类,必须实现Cloneable接口,并覆写clone()方法
*/
public class Person implements Cloneable{
//姓名
private String name;
// 年龄
private int age;
// 编号
private Integer code;
public Person(){
}
public Person(String name, int age, Integer code) {
this.name = name;
this.age = age;
this.code = code;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", code=" + code +
'}';
}
/**
* 覆写clone()方法
* @return
* @throws CloneNotSupportedException
*/
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
cloneDemo.java中追加
Person p2 = p;
p2.setName("小明克隆人");
p2.setAge(12);
p2.setCode(12345);
System.out.println(p);
System.out.println(p2);
System.out.println("--------windcoder.com----------");
得到结果:
Person{name='小明', age=11, code=1234}
Person{name='小明克隆人', age=12, code=12345}
--------windcoder.com----------
此时p2完成了一次拷贝。现在继续改造Person,添加一个Address对象。 Address.java
public class Address {
private String name;
public Address( ) {
}
public Address(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
'}';
}
}
Person.java中添加对应属性
private Address address;
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
// 修改toString,追加address
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", code=" + code +
", address=" + address +
'}';
}
cloneDemo.java追加如下:
p.setAddress(new Address("我是一个小学生"));
Person p2 = (Person)p.clone();
System.out.println(p);
System.out.println(p2);
System.out.println("--------windcoder.com----------");
p2.setName("小明克隆人");
p2.setAge(12);
p2.setCode(12345);
p2.getAddress().setName("我是一个小学生克隆人");
System.out.println(p);
System.out.println(p2);
System.out.println("--------windcoder.com----------");
我们预期是能够获得p还是原来的address,p2的address发生改变,但结果如何呢,打印发现两者都发生了改变:
--------windcoder.com----------
Person{name='小明', age=11, code=1234, address=Student{name='我是一个小学生'}}
Person{name='小明', age=11, code=1234, address=Student{name='我是一个小学生'}}
--------windcoder.com----------
Person{name='小明', age=11, code=1234, address=Student{name='我是一个小学生克隆人'}}
Person{name='小明克隆人', age=12, code=12345, address=Student{name='我是一个小学生克隆人'}}
--------windcoder.com----------
这是由于,此时发生的仅是浅拷贝,即被拷贝对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。针对基本类型及其封装类都是将对应的基本类型值拷贝,对于其余对象,则仅是拷贝其引用,这导致最终里面的对象是同一个,更改一个,另一个的拷贝/原对象的对应值也随之更改。
如果想将上述的address也单独复制一份,这就用到了深拷贝。
被拷贝对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深复制把要复制的对象所引用的对象都复制了一遍。
通俗的说,如果说浅拷贝,开始的时候是两条线,如果在最后有一个其他类的变量,那么这两条线最后会合二为一,共同指向这变量,都能对他进行操作。深拷贝则是完完全全的两条线,互不干涉,因为他已经把所有的内部中的变量的对象全都复制一遍了。
深拷贝在代码中,需要在clone方法中多书写调用这个类中其他类的变量的clone函数。
Address.java 实现Cloneable接口并重写clone
public class Address implements Cloneable{
private String name;
public Address( ) {
}
public Address(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
'}';
}
/**
* 覆写clone()方法
* @return
* @throws CloneNotSupportedException
*/
@Override
protected Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}
Person.java修改clone方法:
protected Object clone() {
try {
// super.clone();
// return super.clone();
// 改为深复制:
Person newPerson = (Person)super.clone();
// 拷贝一份新的address
newPerson.address = (Address) address.clone();
return newPerson;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
此时再运行之前的cloneDemo.java,发现结果已经是预期的效果:
--------windcoder.com----------
Person{name='小明', age=11, code=1234, address=Student{name='我是一个小学生'}}
Person{name='小明', age=11, code=1234, address=Student{name='我是一个小学生'}}
--------windcoder.com----------
Person{name='小明', age=11, code=1234, address=Student{name='我是一个小学生'}}
Person{name='小明克隆人', age=12, code=12345, address=Student{name='我是一个小学生克隆人'}}
--------windcoder.com----------
把对象写到流里的过程是串行化(Serilization)过程,但是在Java程序师圈子里又非常形象地称为“冷冻”或者“腌咸菜(picking)”过程;而把对象从流中读出来的并行化(Deserialization)过程则叫做 “解冻”或者“回鲜(depicking)”过程。
public Object deepClone() {
//写入对象
ByteArrayOutoutStream bo=new ByteArrayOutputStream();
ObjectOutputStream oo=new ObjectOutputStream(bo);
oo.writeObject(this);
//读取对象
ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray());
ObjectInputStream oi=new ObjectInputStream(bi);
return(oi.readObject());
}
Address追加Serializable接口实现
public class Address implements Serializable, Cloneable{
.....
}
Person追加Serializable接口实现
public class Person implements Serializable, Cloneable{
......
/**
* 利用串行化来做深复制
* by: windCoder.com
* @return
*/
public Object deepClone() {
try {
//写入对象
ByteArrayOutputStream bo= new ByteArrayOutputStream();
ObjectOutputStream os = new ObjectOutputStream(bo);
os.writeObject(this);
//读取对象
ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray());
ObjectInputStream oi=new ObjectInputStream(bi);
return(oi.readObject());
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
}
cloneDemo.java追加:
Person p3 = (Person)p.deepClone();
System.out.println(p);
System.out.println(p3);
System.out.println("--------windcoder.com----------");
p3.setName("小明克隆人");
p3.setAge(12);
p3.setCode(12345);
p3.getAddress().setName("我是一个小学生克隆人233333");
System.out.println(p);
System.out.println(p3);
System.out.println("--------windcoder.com----------");
运行结果:
--------windcoder.com----------
Person{name='小明', age=11, code=1234, address=Student{name='我是一个小学生'}}
Person{name='小明', age=11, code=1234, address=Student{name='我是一个小学生'}}
--------windcoder.com----------
Person{name='小明', age=11, code=1234, address=Student{name='我是一个小学生'}}
Person{name='小明克隆人', age=12, code=12345, address=Student{name='我是一个小学生克隆人233333'}}
--------windcoder.com----------
注:可以使用transient关键字,修饰不需要序列化的属性。当序列化对象的时候,这个属性就不会序列化到指定的目的地中。