您的位置:首页 > 编程语言 > Java开发

java克隆对象clone()的使用方法和作用

2017-07-16 13:05 375 查看
转自:http://www.okrs.cn/blog/news/?997.html

内容摘要

若需改动一个对象,同一时候不想改变调用者的对象。就要制作该对象的一个本地副本。这也是本地副本最常见的一种用途。若决定制作一个本地副本。仅仅需简单地使用clone()方法就可以。Clone是“克隆”的意思,即制作全然一模一样的副本。这种方法在基础类Object中定义成“protected”(受保护)模式。

但在希望克隆的不论什么衍生类中,必须将其覆盖为“public”模式。

比如,标准库类Vector覆盖了clone(),所以能为Vector调用clone(),

写clone()方法时。通常都有一行代码 super.clone();
clone 有缺省行为,super.clone();由于首先要把父类中的成员拷贝到位,然后才是复制自己的成员。


java克隆对象

若需改动一个对象,同一时候不想改变调用者的对象。就要制作该对象的一个本地副本。这也是本地副本最常见的一种用途。若决定制作一个本地副本,仅仅需简单地使用clone()方法就可以。Clone是“克隆”的意思。即制作全然一模一样的副本。这种方法在基础类Object中定义成“protected”(受保护)模式。但在希望克隆的不论什么衍生类中,必须将其覆盖为“public”模式。比如,标准库类Vector覆盖了clone(),所以能为Vector调用clone(),例如以下所看到的:

import java.util.*;

class Int {
private int i;
public Int(int ii) { i = ii; }
public void increment() { i++; }
public String toString() {
return Integer.toString(i);
}
}

public class Cloning {
public static void main(String[] args) {
Vector v = new Vector();
for(int i = 0; i < 10; i++ )
v.addElement(new Int(i));
System.out.println("v: " + v);
Vector v2 = (Vector)v.clone();
for(Enumeration e = v2.elements();
e.hasMoreElements(); )
((Int)e.nextElement()).increment();
System.out.println("v: " + v);
}
}


clone()方法产生了一个Object,后者必须马上又一次造型为正确类型。

这个样例指出Vector的clone()方法不能自己主动尝试克隆Vector内包括的每一个对象——由于别名问题,老的Vector和克隆的Vector都包括了同样的对象。我们通常把这样的情况叫作“简单复制”或者“浅层复制”,由于它仅仅复制了一个对象的“表面”部分。实际对象除包括这个“表面”以外。还包括句柄指向的全部对象。以及那些对象又指向的其它全部对象。由此类推。

这便是“对象网”或“对象关系网”的由来。若能复制下全部这张网,便叫作“全面复制”或者“深层复制”。

在输出中可看到浅层复制的结果。注意对v2採取的行动也会影响到v:

v: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
v: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]


一般来说,由于不敢保证Vector里包括的对象是“可以克隆”(凝视②)的,所以最好不要试图克隆那些对象。

使类具有克隆能力

虽然克隆方法是在全部类最主要的Object中定义的,但克隆仍然不会在每一个类里自己主动进行。

这似乎有些不可思议,由于基础类方法在衍生类里是肯定能用的。但Java确实有点儿反其道而行之;假设想在一个类里使用克隆方法,唯一的办法就是专门加入一些代码,以便保证克隆的正常进行。

使用protected时的技巧

为避免我们创建的每一个类都默认具有克隆能力,clone()方法在基础类Object里得到了“保留”(设为protected)。这样造成的后果就是:对那些简单地使用一下这个类的客户程序猿来说,他们不会默认地拥有这种方法;其次,我们不能利用指向基础类的一个句柄来调用clone()(虽然那样做在某些情况下特别实用。比方用多形性的方式克隆一系列对象)。在编译期的时候,这实际是通知我们对象不可克隆的一种方式——并且最奇怪的是。Java库中的大多数类都不能克隆。因此。假如我们运行下述代码:

Integer x = new Integer(l);

x = x.clone();

那么在编译期,就有一条讨厌的错误消息弹出,告诉我们不可訪问clone()——由于Integer并没有覆盖它,并且它对protected版本号来说是默认的)。

可是,假若我们是在一个从Object衍生出来的类中(全部类都是从Object衍生的),就有权调用Object.clone(),由于它是“protected”,并且我们在一个继承器中。基础类clone()提供了一个实用的功能——它进行的是对衍生类对象的真正“按位”复制。所以相当于标准的克隆行动。然而。我们随后须要将自己的克隆操作设为public。否则无法訪问。总之,克隆时要注意的两个关键问题是:差点儿肯定要调用super.clone()。以及注意将克隆设为public。

有时还想在更深层的衍生类中覆盖clone()。否则就直接使用我们的clone()(如今已成为public),而那并不一定是我们所希望的(然而,由于Object.clone()已制作了实际对象的一个副本。所以也有可能同意这样的情况)。protected的技巧在这里仅仅能用一次:首次从一个不具备克隆能力的类继承,并且想使一个类变成“可以克隆”。

而在从我们的类继承的不论什么场合。clone()方法都是可以使用的,由于Java不可能在衍生之后反而缩小方法的訪问范围。换言之,一旦对象变得可以克隆,从它衍生的不论什么东西都是可以克隆的。除非使用特殊的机制(后面讨论)令其“关闭”克隆能力。

实现Cloneable接口

为使一个对象的克隆能力功成圆满,还须要做还有一件事情:实现Cloneable接口。这个接口使人稍觉奇怪。由于它是空的!

interface Cloneable {}

之所以要实现这个空接口。显然不是由于我们准备上溯造型成一个Cloneable,以及调用它的某个方法。有些人觉得在这里使用接口属于一种“欺骗”行为。由于它使用的特性打的是别的主意,而非原来的意思。Cloneable interface的实现扮演了一个标记的角色,封装到类的类型中。

双方面的原因促成了Cloneable interface的存在。首先,可能有一个上溯造型句柄指向一个基础类型,并且不知道它是否真的能克隆那个对象。在这样的情况下,可用instanceof关键字(第11章有介绍)调查句柄是否确实同一个能克隆的对象连接:

if(myHandle instanceof Cloneable) // …

第二个原因是考虑到我们可能不愿全部对象类型都能克隆。

所以Object.clone()会验证一个类是否真的是实现了Cloneable接口。

若答案是否定的。则“掷”出一个CloneNotSupportedException违例。所以在普通情况下,我们必须将“implement Cloneable”作为对克隆能力提供支持的一部分。

java的clone实现

理解了实现clone()方法背后的全部细节后,便可创建出能方便复制的类,以便提供了一个本地副本:

import java.util.*;

class MyObject implements Cloneable {
int i;
MyObject(int ii) { i = ii; }
public Object clone() {
Object o = null;
try {
o = super.clone();
} catch (CloneNotSupportedException e) {
System.out.println("MyObject can't clone");
}
return o;
}
public String toString() {
return Integer.toString(i);
}
}

public class LocalCopy {
static MyObject g(MyObject v) {
v.i++;
return v;
}
static MyObject f(MyObject v) {
v.i++;
return v;
}
public static void main(String[] args) {
MyObject a = new MyObject(11);
MyObject b = g(a);
if(a == b)
System.out.println("a == b");
else
System.out.println("a != b");
System.out.println("a = " + a);
System.out.println("b = " + b);
MyObject c = new MyObject(47);
MyObject d = f(c);
if(c == d)
System.out.println("c == d");
else
System.out.println("c != d");
System.out.println("c = " + c);
System.out.println("d = " + d);
}
}


无论如何。clone()必须可以訪问,所以必须将其设为public(公共的)。其次,作为clone()的初期行动,应调用clone()的基础类版本号。

这里调用的clone()是Object内部预先定义好的。

之所以能调用它,是由于它具有protected(受到保护的)属性。所以能在衍生的类里訪问。

Object.clone()会检查原先的对象有多大,再为新对象腾出足够多的内存,将全部二进制位从原来的对象拷贝到新对象。这叫作“按位复制”,并且按一般的想法,这个工作应该是由clone()方法来做的。但在Object.clone()正式開始操作前,首先会检查一个类是否Cloneable,即是否具有克隆能力——换言之,它是否实现了Cloneable接口。若未实现,Object.clone()就掷出一个CloneNotSupportedException违例,指出我们不能克隆它。

因此。我们最好用一个try-catch块将对super.clone()的调用代码包围(或封装)起来,试图捕获一个应当永不出现的违例(由于这里确实已实现了Cloneable接口)。

在LocalCopy中,两个方法g()和f()揭示出两种參数传递方法间的差异。当中,g()演示的是按引用传递,它会改动外部对象,并返回对那个外部对象的一个引用。

而f()是对自变量进行克隆,所以将其分离出来,并让原来的对象保持独立。随后。它继续做它希望的事情。甚至能返回指向这个新对象的一个句柄,并且不会对原来的对象产生不论什么副作用。注意以下这个多少有些古怪的语句:

v = (MyObject)v.clone();

它的作用正是创建一个本地副本。

为避免被这样的一个语句搞混淆。记住这样的相当奇怪的编码形式在Java中是全然同意的,由于有一个名字的全部东西实际都是一个句柄。所以句柄v用于克隆一个它所指向的副本。并且终于返回指向基础类型Object的一个句柄(由于它在Object.clone()中是那样被定义的)。随后必须将其造型为正确的类型。

在main()中,两种不同參数传递方式的差别在于它们分别測试了一个不同的方法。输出结果例如以下:

a == b
a = 12
b = 12
c != d
c = 47
d =48


大家要记住这样一个事实:Java对“是否等价”的測试并不正确所比較对象的内部进行检查,从而核实它们的值是否同样。

==和!=运算符仅仅是简单地对照句柄的内容。若句柄内的地址同样,就觉得句柄指向同样的对象,所以觉得它们是“等价”的。所以运算符真正检測的是“由于别名问题,句柄是否指向同一个对象?”

java Object.clone()的效果

调用Object.clone()时,实际发生的是什么事情呢?当我们在自己的类里覆盖clone()时。什么东西对于super.clone()来说是最关键的呢?根类中的clone()方法负责建立正确的存储容量。并通过“按位复制”将二进制位从原始对象中拷贝到新对象的存储空间。也就是说,它并不仅仅是预留存储空间以及复制一个对象——实际须要调查出欲复制之对象的准确大小,然后复制那个对象。由于全部这些工作都是在由根类定义之clone()方法的内部代码中进行的(根类并不知道要从自己这里继承出去什么),所以大家也许已经猜到,这个过程须要用RTTI推断欲克隆的对象的实际大小。採取这样的方式。clone()方法便可建立起正确数量的存储空间。并对那个类型进行正确的按位复制。

无论我们要做什么。克隆过程的第一个部分通常都应该是调用super.clone()。通过进行一次准确的复制。这样做可为兴许的克隆进程建立起一个良好的基础。随后,可採取还有一些必要的操作。以完毕终于的克隆。

为确切了解其它操作是什么,首先要正确理解Object.clone()为我们带来了什么。

特别地。它会自己主动克隆全部句柄指向的目标吗?以下这个样例可完毕这样的形式的检測:

public class Snake implements Cloneable {
private Snake next;
private char c;
Snake(int i, char x) {
c = x;
if(--i > 0)
next = new Snake(i, (char)(x + 1));
}
void increment() {
c++;
if(next != null)
next.increment();
}
public String toString() {
String s = ":" + c;
if(next != null)
s += next.toString();
return s;
}
public Object clone() {
Object o = null;
try {
o = super.clone();
} catch (CloneNotSupportedException e) {}
return o;
}
public static void main(String[] args) {
Snake s = new Snake(5, 'a');
System.out.println("s = " + s);
Snake s2 = (Snake)s.clone();
System.out.println("s2 = " + s2);
s.increment();
System.out.println(
"after s.increment, s2 = " + s2);
}
}


一条Snake(蛇)由数段构成,每一段的类型都是Snake。

所以。这是一个一段段链接起来的列表。全部段都是以循环方式创建的,每做好一段。都会使第一个构建器參数的值递减,直至终于为零。而为给每段赋予一个独一无二的标记,第二个參数(一个Char)的值在每次循环构建器调用时都会递增。

increment()方法的作用是循环递增每一个标记,使我们能看到发生的变化;而toString则循环打印出每一个标记。

输出例如以下:

s = :a:b:c:d:e

s2 = :a:b:c:d:e

after s.increment, s2 = :a:c:d:e:f

这意味着仅仅有第一段才是由Object.clone()复制的。所以此时进行的是一种“浅层复制”。若希望复制整条蛇——即进行“深层复制”——必须在被覆盖的clone()里採取附加的操作。

通常可在从一个能克隆的类里调用super.clone()。以确保全部基础类行动(包括Object.clone())可以进行。随着是为对象内每一个句柄都明白调用一个clone()。否则那些句柄会别名变成原始对象的句柄。构建器的调用也大致同样——首先构造基础类。然后是下一个衍生的构建器……以此类推,直到位于最深层的衍生构建器。差别在于clone()并不是个构建器,所以没有办法实现自己主动克隆。为了克隆。必须由自己明白进行。

克隆合成对象

试图深层复制合成对象时会遇到一个问题。

必须假定成员对象中的clone()方法也能依次对自己的句柄进行深层复制,以此类推。

这使我们的操作变得复杂。

为了能正常实现深层复制,必须对全部类中的代码进行控制。或者至少全面掌握深层复制中须要涉及的类,确保它们自己的深层复制能正确进行。

以下这个样例总结了面对一个合成对象进行深层复制时须要做哪些事情:

class DepthReading implements Cloneable {
private double depth;
public DepthReading(double depth) {
this.depth = depth;
}
public Object clone() {
Object o = null;
try {
o = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return o;
}
}

class TemperatureReading implements Cloneable {
private long time;
private double temperature;
public TemperatureReading(double temperature) {
time = System.currentTimeMillis();
this.temperature = temperature;
}
public Object clone() {
Object o = null;
try {
o = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return o;
}
}

class OceanReading implements Cloneable {
private DepthReading depth;
private TemperatureReading temperature;
public OceanReading(double tdata, double ddata){
temperature = new TemperatureReading(tdata);
depth = new DepthReading(ddata);
}
public Object clone() {
OceanReading o = null;
try {
o = (OceanReading)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
o.depth = (DepthReading)o.depth.clone();
o.temperature =
(TemperatureReading)o.temperature.clone();
return o;
}
}

public class DeepCopy {
public static void main(String[] args) {
OceanReading reading =
new OceanReading(33.9, 100.5);
OceanReading r =
(OceanReading)reading.clone();
}
}


DepthReading和TemperatureReading很类似;它们都仅仅包括了基本数据类型。

所以clone()方法可以很easy:调用super.clone()并返回结果就可以。注意两个类使用的clone()代码是全然一致的。

OceanReading是由DepthReading和TemperatureReading对象合并而成的。为了对其进行深层复制。clone()必须同一时候克隆OceanReading内的句柄。为达到这个目标。super.clone()的结果必须造型成一个OceanReading对象(以便訪问depth和temperature句柄)。

java.lang.Object里面有一个方法clone()

protected Object clone()

throws CloneNotSupportedException创建并返回此对象的一个副本。

“副本”的准确含义可能依赖于对象的类。这样做的目的是,对于不论什么对象 x,表达式:

x.clone() != x为 true,表达式:

x.clone().getClass() == x.getClass()也为 true。但这些并不是必须要满足的要求。

普通情况下:

x.clone().equals(x)为 true。但这并不是必须要满足的要求。

依照惯例,返回的对象应该通过调用 super.clone 获得。假设一个类及其全部的超类(Object 除外)都遵守此约定。则 x.clone().getClass() == x.getClass()。

依照惯例。此方法返回的对象应该独立于该对象(正被复制的对象)。要获得此独立性,在 super.clone 返回对象之前,有必要对该对象的一个或多个字段进行改动。这通常意味着要复制包括正在被复制对象的内部“深层结构”的全部可变对象,并使用对副本的引用替换对这些对象的引用。假设一个类仅仅包括基本字段或对不变对象的引用,那么通常不须要改动 super.clone 返回的对象中的字段。

Object 类的 clone 方法运行特定的复制操作。

首先,假设此对象的类不能实现接口 Cloneable,则会抛出 CloneNotSupportedException。注意。全部的数组都被视为实现接口 Cloneable。否则,此方法会创建此对象的类的一个新实例,并像通过分配那样,严格使用此对象对应字段的内容初始化该对象的全部字段;这些字段的内容没有被自我复制。所以。此方法运行的是该对象的“浅表复制”,而不“深层复制”操作。

Object 类本身不实现接口 Cloneable,所以在类为 Object 的对象上调用 clone 方法将会导致在运行时抛出异常。

返回:

此实例的一个副本。

抛出:

CloneNotSupportedException - 假设对象的类不支持 Cloneable 接口。则重写 clone 方法的子类也会抛出此异常。以指示无法复制某个实例。

事实上clone()被定义为protected是有原因的,由于有些时候某些类实例我们不希望能被复制.那我们怎么用这种方法呢?仅仅要使用一个super关键字就够了.以下样例可以解释清楚clone()的作用与其使用方法:


public class C
{
static class Strings implements Cloneable
{
private String str;

public void SetStr(String s){
this.str = s;
}

public String GetStr(){
return this.str;
}

public Object clone()throws CloneNotSupportedException{
return super.clone();
}
}

public static void main(String[] args)
{
try{
Strings str1 = new Strings();
str1.SetStr("Hello World!");

System.out.println("-----------------");

System.out.println("str1.SetStr(\"Hello World!\");");
System.out.println("str2 = (Strings)str1.clone();");
Strings str2 = (Strings)str1.clone();
System.out.println("str1:"+str1.GetStr());
System.out.println("str2:"+str2.GetStr());
System.out.print("print object str1:");
System.out.println(str1);
System.out.print("print object str2:");
System.out.println(str2);

System.out.println("-----------------");
System.out.println("str2.setStr(\"Hello!\");");
str2.SetStr("Hello!");
System.out.println("str1:"+str1.GetStr());
System.out.println("str2:"+str2.GetStr());
System.out.print("print object str1:");
System.out.println(str1);
System.out.print("print object str2:");
System.out.println(str2);

System.out.println("-----------------");

System.out.println("str1 = str2;");
str1 = str2;
System.out.print("print object str1:");
System.out.println(str1);
System.out.print("print object str2:");
System.out.println(str2);

System.out.println("-----------------");
}catch(Exception ex){
System.out.println(ex.toString());
}
}
}


运行结果例如以下:

-----------------
str1.SetStr("Hello World!");
str2 = (Strings)str1.clone();
str1:Hello World!
str2:Hello World!
print object str1:C$Strings@de6ced
print object str2:C$Strings@c17164
-----------------
str2.setStr("Hello!");
str1:Hello World!
str2:Hello!
print object str1:C$Strings@de6ced
print object str2:C$Strings@c17164
-----------------
str1 = str2;
print object str1:C$Strings@c17164
print object str2:C$Strings@c17164
-----------------
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: