目录
***:cloneable接口有什么作用,是否可以不实现该接口?
***:使用工具类BeanUtils(org.apache.commons.beanutils)实现拷贝
前言
带着问题学java系列博文之java基础篇。从问题出发,学习java知识。
Java拷贝的概念
拷贝或者叫复制,是指复制一个目标A,生成一个新的目标B。根据拷贝目标的不同,java分为引用拷贝和对象拷贝。而Java实现对象拷贝主要有三种方式,根据实现方式的不同,又分为浅拷贝和深拷贝。
***:拷贝有什么作用,日常哪些场合需要用到拷贝?
引用拷贝:比如定义一个变量,指向的是一个对象;此时我们需要再定义一个同样指向该对象的拷贝,就可以用到引用拷贝。如下例:
public class Test {
public static void main(String[] args) {
Person p1 = new Person("sanzhang",23);
Person p2 = p1;
p2.setAge(25);
System.out.println(p2.getAge());
System.out.println(p1.getAge());
}
}
如代码,直接将p1赋值给了p2,这里就是引用拷贝,此时p1和p2都是指向person对象实例的引用,不管是通过p2还是p1去修改person对象的值,都是修改堆内存空间中的同一对象,所以可以看到打印出来的age都是25.
对象拷贝:可以看到引用拷贝仅仅只是复制了一份引用,通过任一引用的修改都会导致对象的改变。而当我们需要完整复制一个对象,两个对象是完全独立的,此时就需要用到对象拷贝。
public class Test {
public static void main(String[] args) {
Person p1 = new Person("sanzhang",23);
Person p2 = p1.clone();
p2.setAge(25);
System.out.println(p2.getAge());
System.out.println(p1.getAge());
}
}
如代码,p2是p1.clone()产生的一个新对象,p2和p1在堆内存中拥有各自独有的地址空间,通过p2修改age,仅能修改p2对象的值,而p1对象的值不受影响,所以打印出了25和23.
对象拷贝
***:实现对象拷贝的三种方式
- new对象组合set方法赋值的方式拷贝对象
public class Test {
public static void main(String[] args) {
Person p1 = new Person(23,"lisi",new Address("hefei"));
Person p2 = new Person(p1.getAge(),p1.getName(),p1.getAddress());
p2.setAge(25);
System.out.println(p2.getAge());
System.out.println(p1.getAge());
}
}
如上代码,通过new关键字创建一个新的对象,然后将目标对象的值通过set的方式赋值给新的对象,如此组合,实现了对象的拷贝。这里修改p2的age不会影响到p1的age,所以打印出了25和23.
- 利用object的clone方法实现对象拷贝
public class Test {
public static void main(String[] args) {
Person p1 = new Person(23,"lisi",new Address("hefei"));
Person p2 = p1.clone();
p2.setAge(25);
System.out.println(p2.getAge());
System.out.println(p1.getAge());
}
static class Person implements Cloneable{
int age;
String name;
Address address;
@Override
public Person clone(){
try {
return (Person) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
public Person() {
}
public Person(int age, String name, Address address) {
this.age = age;
this.name = name;
this.address = address;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
}
static class Address{
String addr;
public Address() {
}
public Address(String addr) {
this.addr = addr;
}
public String getAddr() {
return addr;
}
public void setAddr(String addr) {
this.addr = addr;
}
}
}
由于object的clone方法是protected修饰的,外部调用受限制;所以这里person类实现cloneable接口,重写object的clone方法。
- 利用序列化实现对象拷贝
//---------------------序列化流-----------------------------
private static void testObjectOS(){
try {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Object.txt"));
Person person = new Person("张三",19);
oos.writeObject(person);
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private static void testObjectIS(){
try {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("Object.txt"));
Person person = (Person) ois.readObject();
System.out.println(person.toString());
ois.close();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
如上代码,使用序列化输出流将对象保存到文件中,然后再通过序列化输入流读取文件内容,生成一个新的对象,实现对象的拷贝。
***:cloneable接口有什么作用,是否可以不实现该接口?
实现对象拷贝的第二种方式是采用clone方法,要求对象类实现cloneable接口,然后重写object类的clone方法。我们看下cloneable接口的源码:
* @see java.lang.CloneNotSupportedException
* @see java.lang.Object#clone()
* @since JDK1.0
*/
public interface Cloneable {
}
可以看到,源码中Cloneable就是一个空接口,没有任何抽象方法。那为什么还要在对象类上去实现该接口呢?再看一下Object类的clone方法源码:
protected native Object clone() throws CloneNotSupportedException;
clone方法是一个native方法,它的实现细节其实是先检查该类型是否支持拷贝,不支持则抛出CloneNotSupportException异常,支持则执行对象拷贝逻辑。这里的检查其实就是看该类型是否实现了Cloneable接口,所以Cloneable接口其实只是一个标识接口,一个类型实现了该接口,相当于告诉jvm,这个类型支持clone。通常再重写clone方法(主要是改修饰符为public,返回类型为当前类),满足外部调用,实现对象拷贝。如果不实现该接口,即使重写了clone方法,在调用clone方法的时候,也会抛出CloneNotSupportException异常。
***:浅拷贝和深拷贝是什么?有什么区别?
在分析之前,我们先看下第二种方式(clone方法)实现对象拷贝的一个现象:
p2 = p1.clone()实现了对象拷贝,生成一个新的对象p2;此时修改p2的age不会影响到p1.但是当修改p2的address
(p2.getAddress().setAddr("anqing"))时,会发现p1的address内容也被改成了“anqing”。我们不是实现了对象拷贝吗,p2是一个新的对象呀,为什么修改p2的值,p1也会被修改呢?
这是因为address属性是一个自定义的类型,一个引用类型,我们调用clone方法只是拷贝基本类型,而引用类型是直接拷贝引用;所以这里address属性只是引用拷贝,p2和p1都是指向同一个address对象,通过p2修改address的值,直接修改了address对象,之后p1去获取对象的值时,当然获取到的是修改后的值。这种仅复制对象本身以及其值类型的成员变量,而引用类型的成员变量是引用拷贝的对象拷贝就是浅拷贝。可以实现对象的真正复制,包括对象的引用类型成员变量,以及引用类型成员变量里面的引用类型变量的对象拷贝就是深拷贝,深拷贝生成的新对象与原来的对象不共享任何内存空间,修改新对象的任何属性都不会影响原来的对象。
***:如何实现深拷贝?
首先序列化的方式实现对象拷贝本身就是深拷贝;clone方式实现对象拷贝,可以通过改写代码实现深拷贝;set方式对于引用类型其实就是引用赋值,所以无法修改成深拷贝。
public class Test {
public static void main(String[] args) {
Person p1 = new Person(23,"lisi",new Address("hefei"));
Person p2 = p1.clone();
p2.getAddress().setAddr("anqing");
System.out.println(p2.getAddress().getAddr());
System.out.println(p1.getAddress().getAddr());
}
static class Person implements Cloneable{
int age;
String name;
Address address;
@Override
public Person clone(){
Person person = null;
try {
person = (Person) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
person.address = address.clone();
return person;
}
public Person() {
}
public Person(int age, String name, Address address) {
this.age = age;
this.name = name;
this.address = address;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
}
static class Address implements Cloneable{
String addr;
@Override
public Address clone() {
try {
return (Address) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
public Address() {
}
public Address(String addr) {
this.addr = addr;
}
public String getAddr() {
return addr;
}
public void setAddr(String addr) {
this.addr = addr;
}
}
}
如上代码,修改clone方法,将浅拷贝改成深拷贝,主要步骤如下:
- 所有的引用类型属性自身都实现cloneable接口,重写clone方法(引用类型属性自身还含有引用类型属性,则继续往下实现);
- 重写对象的clone方法,调用super.clone()实现自身的拷贝,然后再对每个引用类型属性分别调用引用类型的clone方法实现引用类型属性的拷贝;
***:使用工具类BeanUtils(org.apache.commons.beanutils)实现拷贝
public static void main(String[] args) throws Exception{
Person p1 = new Person(23,"lisi",new Address("hefei"));
Person p2 = new Person();
BeanUtils.copyProperties(p2,p1);
System.out.println(p1.toString());
System.out.println(p2.toString());
}
注意使用工具类仅可拷贝基本类型变量,引用类型变量全部都是null。
以上系个人理解,如果存在错误,欢迎大家指正。原创不易,转载请注明出处!