Java中的对象“克隆”

版权声明:请附链接,自由转载 https://blog.csdn.net/kangkanglou/article/details/82113213

前言

前一章节中,我们讨论了构建Java对象的五种方式,其中,clone(克隆)也是我们比较常见的一种方式。

protected native Object clone() throws CloneNotSupportedException;

如果想要克隆一个对象,我们需要:

  • 实现Cloneable接口,否则当我们调用clone方法时JVM将会抛出CloneNotSupportedException异常
  • 包含clone方法,用以处理CloneNotSupportedException异常
  • 我们通过调用父类的clone()方法来执行克隆操作,注意这是一个链式调用操作,当我们调用父类的clone()方法,父类会调用父类的clone()方法,直至Object类的clone()方法,实际上是Object类的clone方法执行了你所定义对象的克隆操作,我们定义的clone方法实际是委托给当前父类的clone操作。

对象克隆的分类

对象克隆有浅拷贝和深拷贝两种分类,下面我们例子说明两者区别。

class Person implements Cloneable {
    private String name; 
    private int income; 
    private City city; 
    public String getName() {
        return name;
    }
    public void setName(String firstName) {
        this.name = firstName;
    }
    public int getIncome() {
        return income;
    }
    public void setIncome(int income) {
        this.income = income;
    }
    public City getCity() {
        return city;
    }
    public void setCity(City city) {
        this.city = city;
    }
    public Person(String firstName, int income, City city) {
        super();
        this.name = firstName;
        this.income = income;
        this.city = city;
    }
    @Override
    public Person clone() throws CloneNotSupportedException {
        return (Person) super.clone();
    }
    @Override
    public String toString() {
        return "Person [name=" + name + ", income=" + income + ", city=" + city + "]";
    }
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((city == null) ? 0 : city.hashCode());
        result = prime * result + income;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Person other = (Person) obj;
        if (city == null) {
            if (other.city != null)
                return false;
        } else if (!city.equals(other.city))
            return false;
        if (income != other.income)
            return false;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }
}

注意,Person类本身包含一个引用型变量City

class City implements Cloneable {
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public City(String name) {
        super();
        this.name = name;
    }
    public City clone() throws CloneNotSupportedException {
        return (City) super.clone();
    }
    @Override
    public String toString() {
        return "City [name=" + name + "]";
    }
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        City other = (City) obj;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }
}

下面我们来测试下对象克隆

public class CloningExample {
    public static void main(String[] args) throws CloneNotSupportedException {
        City city = new City("Dehradun");
        Person person1 = new Person("Naresh", 10000, city);
        System.out.println(person1);
        Person person2 = person1.clone();
        System.out.println(person2);
        if (person1 == person2) { 
            System.out.println("Both person1 and person2 holds same object");
        }
        if (person1.equals(person2)) { 
            System.out.println("But both person1 and person2 are equal and have same content");
        }
        if (person1.getCity() == person2.getCity()) {
            System.out.println("Both person1 and person2 have same city object");
        }
    }
}

对象克隆是指将一个对象的内容逐位复制到另一个对象,这意味着一个对象的所有实例变量的值将被复制到其他对象的实例变量中去。

因此:

  • person1==person2 =》 false,内存地址不一致
  • person1.equals(person2) =》 true,内容副本,内容一致
  • person1.getCity() == person2.getCity() =》true,引用型变量,引用型变量本身存在各自栈空间中,实际指向堆中的同一个对象,因此结果为true。

这就是我们Object.clone的默认克隆策略:浅拷贝,如果我们想执行深拷贝,那么我们需要改造下Person类的clone方法:


public Person clone() throws CloneNotSupportedException {
    Person clonedObj = (Person) super.clone();
    clonedObj.city = this.city.clone();
    return clonedObj;
}

那么在这个前提下,person1.getCity() == person2.getCity()将返回false,因为他们指向的是堆中不同的对象。

实现方式

我们已经知道,在Java中对象克隆分两种:浅克隆(浅拷贝)与深克隆(拷贝),本章,我们将讨论如何具体实现对象克隆,这里我们讨论两种方式:

  • 构造函数拷贝:Copy Constructors
  • Object.clone()

首先,我们来看Object.clone()的优缺点。

Object.clone

优点

  • 代码简洁(浅拷贝方式),但注意:如果需要深拷贝,则需要重写clone方法
  • 实现简单,特别是当我们需要针对已研发或上线的旧系统做改造,我们只需要实现一个公共父类,并且提供一个公共clone方法,所以的子类自动继承。
  • 对于数组对象来说,克隆是拷贝数组最快的方式。
  • 自JDK1.5之后,针对数组上的克隆无需额外的类型转换。

缺点

  • 使用Object.clone()需要我们代码中增加需要额外的语法:实现Cloneable接口,定义clone()方法体,处理 CloneNotSupportedException异常,最后调用Object.clone()方法,并转换为我们程序所需要的对象
  • Cloneable接口本身不包含clone方法,Cloneable本身只是一个标识接口,其本身不包含任何方法,但我们仍旧需要继承实现它以告诉JVM我们的对象支持Clone操作。
  • 在JDK中Object.clone()方法被定义为protected,注意,这是一种包访问和继承访问修饰符,所以一般情况下(对于非java.lang包中的类),我们无法直接调用Object.clone方法,只能通过调用链的方式来间接调用
  • Object.clone()本身是native的,这是一种本地方法实现,所以我们无法控制对象的构建
  • 如果我们在子类中定义了clone()方法,那么如果要实现支持对象克隆,那么子类的所有父类必须直接或间接的实现clone方法,否则super.clone()调用链就会失效。
  • Object.clone只支持浅拷贝,所以拷贝对象中的引用型变量仍然指向原始引用型变量所指对象,引用型变量所指对象的改变会直接影响两个父类持有者。所以,为了消除这种影响,你需要额外的代码实现深拷贝逻辑。
  • 无法修改final类型字段,final类型字段只能通过构造函数来修改。比如对于Person中id字段,业务场景中肯定是需要id标识唯一,但事实上,我们通过Object.clone()获取到的是重复对象。

因此,因为这些设计问题的存在,所以多数开发人员更乐意去采取别的方式来拷贝对象,比如:

注意,以上这些实现方式都需要我们引入额外的函数库,事实上,这些函数库内部实现也是调用序列化或者拷贝构造函数的方式实现,如果我们不希望引入额外库,那么我们可以通过以下方式自己实现对象克隆。

  • 序列化
  • 拷贝构造函数

序列化技术我们在此不多赘述,这里我们看下拷贝构造函数方式。


public Person(Person original) {
    this.id = original.id + 1;
    this.name = new String(original.name);
    this.city = new City(original.city);
}

拷贝构造函数

优点

  • 无需我们强制实现任意接口或者处理任何异常,当然如果需要随时可增加实现。
  • 无需任何类型实现。
  • 无需依赖任何黑盒对象创建方式,对象创建可控可见。
  • 无需父类必须遵循或者实现合约。
  • 支持修改final类型字段
  • 对象创建可控可见,我们可以根据实际业务场景定制初始化逻辑。

此外,通过使用拷贝构造函数策略,我们还可以创建转换构造函数,通过转换构造函数,我们可以很方便的将一个对象转换为另一个对象,比如:

 ArrayList(Collection<? extends E> c)

通过该构造函数,可以将任意Collection对象转换为ArrayList对象。

猜你喜欢

转载自blog.csdn.net/kangkanglou/article/details/82113213
今日推荐