信息发布软件,b2b软件,广告发布软件

标题: JAVA中浅复制与深复制和多方法实例教程 [打印本页]

作者: 信息发布软件    时间: 2016-10-17 11:06
标题: JAVA中浅复制与深复制和多方法实例教程
本帖最后由 信息发布软件 于 2016-10-17 11:09 编辑

 1.浅复制与深复制概念

⑴浅复制(浅克隆)

被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象。


⑵深复制(深克隆)

被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深复制把要复制的对象所引用的对象都复制了一遍。


  2.Java的clone()方法

⑴clone方法将对象复制了一份并返回给调用者。一般而言,clone()方法满足:

①对任何的对象x,都有x.clone() !=x//克隆对象与原对象不是同一个对象

②对任何的对象x,都有x.clone().getClass()= =x.getClass()//克隆对象与原对象的类型一样

③如果对象x的equals()方法定义恰当,那么x.clone().equals(x)应该成立。


⑵Java中对象的克隆

①为了获取对象的一份拷贝,我们可以利用Object类的clone()方法。

②在派生类中覆盖基类的clone()方法,并声明为public。

③在派生类的clone()方法中,调用super.clone()。

④在派生类中实现Cloneable接口。


请看如下代码:


class Student implements Cloneable

{

String name;

int age;

Student(String name,int age)

{

this.name=name;

this.age=age;

}

public Object clone()

{

Object o=null;

try

{

o=(Student)super.clone();//Object中的clone()识别出你要复制的是哪一

// 个对象。

}


catch(CloneNotSupportedException e)

{

System.out.println(e.toString());

}

return o;

}

}


public static void main(String[] args)

{

Student s1=new Student("zhangsan",18);

Student s2=(Student)s1.clone();

s2.name="lisi";

s2.age=20;

System.out.println("name="+s1.name+","+"age="+s1.age);//修改学生2后,不影响

//学生1的值。

}


说明:

①为什么我们在派生类中覆盖Object的clone()方法时,一定要调用super.clone()呢?在运行时刻,Object中的clone()识别出你要复制的是哪一个对象,然后为此对象分配空间,并进行对象的复制,将原始对象的内容一一复制到新对象的存储空间中。

②继承自java.lang.Object类的clone()方法是浅复制。以下代码可以证明之。


class Professor

{

String name;

int age;

Professor(String name,int age)

{

this.name=name;

this.age=age;

}

}

class Student implements Cloneable

{

String name;//常量对象。

int age;

Professor p;//学生1和学生2的引用值都是一样的。

Student(String name,int age,Professor p)

{

this.name=name;

this.age=age;

this.p=p;

}

public Object clone()

{

Student o=null;

try

{

o=(Student)super.clone();

}


catch(CloneNotSupportedException e)

{

System.out.println(e.toString());

}

o.p=(Professor)p.clone();

return o;

}

}

public static void main(String[] args)

{

Professor p=new Professor("wangwu",50);

Student s1=new Student("zhangsan",18,p);

Student s2=(Student)s1.clone();

s2.p.name="lisi";

s2.p.age=30;

System.out.println("name="+s1.p.name+","+"age="+s1.p.age);//学生1的教授

//成为lisi,age为30。

}

  那应该如何实现深层次的克隆,即修改s2的教授不会影响s1的教授?代码改进如下。


改进使学生1的Professor不改变(深层次的克隆)

class Professor implements Cloneable

{

String name;

int age;

Professor(String name,int age)

{

this.name=name;

this.age=age;

}

public Object clone()

{

Object o=null;

try

{

o=super.clone();

}

catch(CloneNotSupportedException e)

{

System.out.println(e.toString());

}

return o;

}

}

class Student implements Cloneable

{

String name;

int age;

Professor p;

Student(String name,int age,Professor p)

{

this.name=name;

this.age=age;

this.p=p;

}


public Object clone()

{

Student o=null;

try

{

o=(Student)super.clone();

}

catch(CloneNotSupportedException e)

{

System.out.println(e.toString());

}

o.p=(Professor)p.clone();

return o;

}

}

public static void main(String[] args)

{

Professor p=new Professor("wangwu",50);

Student s1=new Student("zhangsan",18,p);

Student s2=(Student)s1.clone();

s2.p.name="lisi";

s2.p.age=30;

System.out.println("name="+s1.p.name+","+"age="+s1.p.age);//学生1的教授不改变。

}


  3.利用串行化来做深复制

把对象写到流里的过程是串行化(Serilization)过程,但是在Java程序师圈子里又非常形象地称为“冷冻”或者“腌咸菜(picking)”过程;而把对象从流中读出来的并行化(Deserialization)过程则叫做“解冻”或者“回鲜(depicking)”过程。应当指出的是,写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面,因此“腌成咸菜”的只是对象的一个拷贝,Java咸菜还可以回鲜。

在Java语言里深复制一个对象,常常可以先使对象实现Serializable接口,然后把对象(实际上只是对象的一个拷贝)写到一个流里(腌成咸菜),再从流里读出来(把咸菜回鲜),便可以重建对象。

如下为深复制源代码。

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());

}


这样做的前提是对象以及对象内部所有引用到的对象都是可串行化的,否则,就需要仔细考察那些不可串行化的对象可否设成transient,从而将之排除在复制过程之外。上例代码改进如下。


class Professor implements Serializable

{

String name;

int age;

Professor(String name,int age)

{

this.name=name;

this.age=age;

}


}

class Student implements Serializable

{

String name;//常量对象。

int age;

Professor p;//学生1和学生2的引用值都是一样的。

Student(String name,int age,Professor p)

{

this.name=name;

this.age=age;

this.p=p;

}

public Object deepClone() throws IOException,

OptionalDataException,ClassNotFoundException

{

//将对象写到流里

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());

}


}

public static void main(String[] args)

{

Professor p=new Professor("wangwu",50);

Student s1=new Student("zhangsan",18,p);

Student s2=(Student)s1.deepClone();

s2.p.name="lisi";

s2.p.age=30;

System.out.println("name="+s1.p.name+","+"age="+s1.p.age); //学生1的教授不改变。

}



作者: 信息发布软件    时间: 2016-10-17 11:11
实例方法2

假如说你想复制一个简单变量。很简单:


                int apples = 5;                int pears = apples;
不仅仅是int类型,其它七种原始数据类型(boolean,char,byte,short,float,double.long)同样适用于该类情况。

但是如果你复制的是一个对象,情况就有些复杂了。

假设说我是一个beginner,我会这样写:


class Student {        private int number;        public int getNumber() {                return number;        }        public void setNumber(int number) {                this.number = number;        }        }public class Test {                public static void main(String args[]) {                                Student stu1 = new Student();                stu1.setNumber(12345);                Student stu2 = stu1;                                System.out.println("学生1:" + stu1.getNumber());                System.out.println("学生2:" + stu2.getNumber());        }}

打印结果:


学生1:12345学生2:12345
这里我们自定义了一个学生类,该类只有一个number字段。
我们新建了一个学生实例,然后将该值赋值给stu2实例。(Student stu2 = stu1;)
再看看打印结果,作为一个新手,拍了拍胸腹,对象复制不过如此,
难道真的是这样吗?

我们试着改变stu2实例的number字段,再打印结果看看:


                stu2.setNumber(54321);                        System.out.println("学生1:" + stu1.getNumber());                System.out.println("学生2:" + stu2.getNumber());
打印结果:



学生1:54321学生2:54321
这就怪了,为什么改变学生2的学号,学生1的学号也发生了变化呢?


原因出在(stu2 = stu1) 这一句。该语句的作用是将stu1的引用赋值给stu2,
这样,stu1和stu2指向内存堆中同一个对象。如图:
JAVA中浅复制与深复制和多方法实例教程 b2b软件

那么,怎样才能达到复制一个对象呢?
是否记得万类之王Object。它有11个方法,有两个protected的方法,其中一个为clone方法。
该方法的签名是:
protected native Object clone() throws CloneNotSupportedException;

因为每个类直接或间接的父类都是Object,因此它们都含有clone()方法,但是因为该方法是protected,所以都不能在类外进行访问。
要想对一个对象进行复制,就需要对clone方法覆盖。

一般步骤是(浅复制):
1. 被复制的类需要实现Clonenable接口(不实现的话在调用clone方法会抛出CloneNotSupportedException异常) 该接口为标记接口(不含任何方法)
2. 覆盖clone()方法,访问修饰符设为public。方法中调用super.clone()方法得到需要的复制对象,(native为本地方法)

下面对上面那个方法进行改造:


class Student implements Cloneable{        private int number;        public int getNumber() {                return number;        }        public void setNumber(int number) {                this.number = number;        }                @Override        public Object clone() {                Student stu = null;                try{                        stu = (Student)super.clone();                }catch(CloneNotSupportedException e) {                        e.printStackTrace();                }                return stu;        }}public class Test {                public static void main(String args[]) {                                Student stu1 = new Student();                stu1.setNumber(12345);                Student stu2 = (Student)stu1.clone();                                System.out.println("学生1:" + stu1.getNumber());                System.out.println("学生2:" + stu2.getNumber());                                stu2.setNumber(54321);                        System.out.println("学生1:" + stu1.getNumber());                System.out.println("学生2:" + stu2.getNumber());        }}
打印结果:



学生1:12345学生2:12345学生1:12345学生2:54321
如果你还不相信这两个对象不是同一个对象,那么你可以看看这一句:



                System.out.println(stu1 == stu2); // false
上面的复制被称为浅复制(Shallow Copy),还有一种稍微复杂的深度复制(deep copy):
我们在学生类里再加一个Address类。


class Address  {        private String add;        public String getAdd() {                return add;        }        public void setAdd(String add) {                this.add = add;        }        }class Student implements Cloneable{        private int number;        private Address addr;                public Address getAddr() {                return addr;        }        public void setAddr(Address addr) {                this.addr = addr;        }        public int getNumber() {                return number;        }        public void setNumber(int number) {                this.number = number;        }                @Override        public Object clone() {                Student stu = null;                try{                        stu = (Student)super.clone();                }catch(CloneNotSupportedException e) {                        e.printStackTrace();                }                return stu;        }}public class Test {                public static void main(String args[]) {                                Address addr = new Address();                addr.setAdd("杭州市");                Student stu1 = new Student();                stu1.setNumber(123);                stu1.setAddr(addr);                                Student stu2 = (Student)stu1.clone();                                System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());                System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());        }}
打印结果:



学生1:123,地址:杭州市学生2:123,地址:杭州市
乍一看没什么问题,真的是这样吗?

我们在main方法中试着改变addr实例的地址。


                addr.setAdd("西湖区");                                System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());                System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
打印结果:



学生1:123,地址:杭州市学生2:123,地址:杭州市学生1:123,地址:西湖区学生2:123,地址:西湖区
这就奇怪了,怎么两个学生的地址都改变了?

原因是浅复制只是复制了addr变量的引用,并没有真正的开辟另一块空间,将值复制后再将引用返回给新对象。
所以,为了达到真正的复制对象,而不是纯粹引用复制。我们需要将Address类可复制化,并且修改clone方法,完整代码如下:



package abc;class Address implements Cloneable {        private String add;        public String getAdd() {                return add;        }        public void setAdd(String add) {                this.add = add;        }                @Override        public Object clone() {                Address addr = null;                try{                        addr = (Address)super.clone();                }catch(CloneNotSupportedException e) {                        e.printStackTrace();                }                return addr;        }}class Student implements Cloneable{        private int number;        private Address addr;                public Address getAddr() {                return addr;        }        public void setAddr(Address addr) {                this.addr = addr;        }        public int getNumber() {                return number;        }        public void setNumber(int number) {                this.number = number;        }                @Override        public Object clone() {                Student stu = null;                try{                        stu = (Student)super.clone();        //浅复制                }catch(CloneNotSupportedException e) {                        e.printStackTrace();                }                stu.addr = (Address)addr.clone();        //深度复制                return stu;        }}public class Test {                public static void main(String args[]) {                                Address addr = new Address();                addr.setAdd("杭州市");                Student stu1 = new Student();                stu1.setNumber(123);                stu1.setAddr(addr);                                Student stu2 = (Student)stu1.clone();                                System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());                System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());                                addr.setAdd("西湖区");                                System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());                System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());        }}
打印结果:



学生1:123,地址:杭州市学生2:123,地址:杭州市学生1:123,地址:西湖区学生2:123,地址:杭州市
这样结果就符合我们的想法了。
总结:浅拷贝是指在拷贝对象时,对于基本数据类型的变量会重新复制一份,而对于引用类型的变量只是对引用进行拷贝,
没有对引用指向的对象进行拷贝。
而深拷贝是指在拷贝对象时,同时会对引用指向的对象进行拷贝。
区别就在于是否对  对象中的引用变量所指向的对象进行拷贝。

最后我们可以看看API里其中一个实现了clone方法的类:
java.util.Date:


    /**     * Return a copy of this object.     */    public Object clone() {        Date d = null;        try {            d = (Date)super.clone();            if (cdate != null) {                d.cdate = (BaseCalendar.Date) cdate.clone();            }        } catch (CloneNotSupportedException e) {} // Won't happen        return d;    }
该类其实也属于深度复制。


作者: 信息发布软件    时间: 2016-10-17 11:12
实例教程3

Java语言的一个优点就是取消了指针的概念,但也导致了许多程序员在编程中常常忽略了对象与引用的区别,本文会试图澄清这一概念。并且由于Java不能通过简单的赋值来解决对象复制的问题,在开发过程中,也常常要要应用clone()方法来复制对象。本文会让你了解什么是影子clone与深度clone,认识它们的区别、优点及缺点。
      看到这个标题,是不是有点困惑:Java语言明确说明取消了指针,因为指针往往是在带来方便的同时也是导致代码不安全的根源,同时也会使程序的变得非常复杂难以理解,滥用指针写成的代码不亚于使用早已臭名昭著的"GOTO"语句。Java放弃指针的概念绝对是极其明智的。但这只是在Java语言中没有明确的指针定义,实质上每一个new语句返回的都是一个指针的引用,只不过在大多时候Java中不用关心如何操作这个"指针",更不用象在操作C++的指针那样胆战心惊。唯一要多多关心的是在给函数传递对象的时候。

[java] view plain copy



[java] view plain copy



      这段代码的主要部分调用了两个很相近的方法,changeObj()和changePri()。唯一不同的是它们一个把对象作为输入参数,另一个把Java中的基本类型int作为输入参数。并且在这两个函数体内部都对输入的参数进行了改动。看似一样的方法,程序输出的结果却不太一样。changeObj()方法真正的把输入的参数改变了,而changePri()方法对输入的参数没有任何的改变。

      从这个例子知道Java对对象和基本的数据类型的处理是不一样的。和C语言一样,当把Java的基本数据类型(如int,char,double等)作为入口参数传给函数体的时候,传入的参数在函数体内部变成了局部变量,这个局部变量是输入参数的一个拷贝,所有的函数体内部的操作都是针对这个拷贝的操作,函数执行结束后,这个局部变量也就完成了它的使命,它影响不到作为输入参数的变量。这种方式的参数传递被称为"值传递"。而在Java中用对象作为入口参数的传递则缺省为"引用传递",也就是说仅仅传递了对象的一个"引用",这个"引用"的概念同C语言中的指针引用是一样的。当函数体内部对输入变量改变时,实质上就是在对这个对象的直接操作。
      除了在函数传值的时候是"引用传递",在任何用"="向对象变量赋值的时候都是"引用传递"。就是类似于给变量再起一个别名。两个名字都指向内存中的同一个对象。
      在实际编程过程中,我们常常要遇到这种情况:有一个对象A,在某一时刻A中已经包含了一些有效值,此时可能会需要一个和A完全相同新对象B,并且此后对B任何改动都不会影响到A中的值,也就是说,A与B是两个独立的对象,但B的初始值是由A对象确定的。在Java语言中,用简单的赋值语句是不能满足这种需求的。要满足这种需求虽然有很多途径,但实现clone()方法是其中最简单,也是最高效的手段。
      Java的所有类都默认继承java.lang.Object类,在java.lang.Object类中有一个方法clone()。JDK API的说明文档解释这个方法将返回Object对象的一个拷贝。要说明的有两点:一是拷贝对象返回的是一个新对象,而不是一个引用。二是拷贝对象与用new操作符返回的新对象的区别就是这个拷贝已经包含了一些原来对象的信息,而不是对象的初始信息。
      怎样应用clone()方法?

      一个很典型的调用clone()代码如下:

[java] view plain copy



      有三个值得注意的地方,一是希望能实现clone功能的CloneClass类实现了Cloneable接口,这个接口属于java.lang包,java.lang包已经被缺省的导入类中,所以不需要写成java.lang.Cloneable。另一个值得请注意的是重载了clone()方法。最后在clone()方法中调用了super.clone(),这也意味着无论clone类的继承结构是什么样的,super.clone()直接或间接调用了java.lang.Object类的clone()方法。下面再详细的解释一下这几点。
      应该说第三点是最重要的,仔细观察一下Object类的clone()一个native方法,native方法的效率一般来说都是远高于java中的非native方法。这也解释了为什么要用Object中clone()方法而不是先new一个类,然后把原始对象中的信息赋到新对象中,虽然这也实现了clone功能。对于第二点,也要观察Object类中的clone()还是一个protected属性的方法。这也意味着如果要应用clone()方法,必须继承Object类,在Java中所有的类是缺省继承Object类的,也就不用关心这点了。然后重载clone()方法。还有一点要考虑的是为了让其它类能调用这个clone类的clone()方法,重载之后要把clone()方法的属性设置为public。
       那么clone类为什么还要实现Cloneable接口呢?稍微注意一下,Cloneable接口是不包含任何方法的!其实这个接口仅仅是一个标志,而且这个标志也仅仅是针对Object类中clone()方法的,如果clone类没有实现Cloneable接口,并调用了Object的clone()方法(也就是调用了super.Clone()方法),那么Object的clone()方法就会抛出CloneNotSupportedException异常。
      以上是clone的最基本的步骤,想要完成一个成功的clone,还要了解什么是"影子clone"和"深度clone"。
      什么是影子clone?

[java] view plain copy



       输出结果:
before clone,b1.aInt = 11
before clone,b1.unCA = 111
=================================
after clone,b1.aInt = 11
after clone,b1.unCA = 222
=================================
after clone,b2.aInt = 22
after clone,b2.unCA = 222

       输出的结果说明int类型的变量aInt和UnCloneA的实例对象unCA的clone结果不一致,int类型是真正的被clone了,因为改变了b2中的aInt变量,对b1的aInt没有产生影响,也就是说,b2.aInt与b1.aInt已经占据了不同的内存空间,b2.aInt是b1.aInt的一个真正拷贝。相反,对b2.unCA的改变同时改变了b1.unCA,很明显,b2.unCA和b1.unCA是仅仅指向同一个对象的不同引用!从中可以看出,调用Object类中clone()方法产生的效果是:先在内存中开辟一块和原始对象一样的空间,然后原样拷贝原始对象中的内容。对基本数据类型,这样的操作是没有问题的,但对非基本类型变量,我们知道它们保存的仅仅是对象的引用,这也导致clone后的非基本类型变量和原始对象中相应的变量指向的是同一个对象。
       大多时候,这种clone的结果往往不是我们所希望的结果,这种clone也被称为"影子clone"。要想让b2.unCA指向与b2.unCA不同的对象,而且b2.unCA中还要包含b1.unCA中的信息作为初始信息,就要实现深度clone。
       怎么进行深度clone?
       把上面的例子改成深度clone很简单,需要两个改变:一是让UnCloneA类也实现和CloneB类一样的clone功能(实现Cloneable接口,重载clone()方法)。二是在CloneB的clone()方法中加入一句o.unCA = (UnCloneA)unCA.clone();

[java] view plain copy




输出结果:
before clone,b1.aInt = 11
before clone,b1.unCA = 111
=================================
after clone,b1.aInt = 11
after clone,b1.unCA = 111
=================================
after clone,b2.aInt = 22
after clone,b2.unCA = 222

      可以看出,现在b2.unCA的改变对b1.unCA没有产生影响。此时b1.unCA与b2.unCA指向了两个不同的UnCloneA实例,而且在CloneB b2 = (CloneB)b1.clone();调用的那一刻b1和b2拥有相同的值,在这里,b1.i = b2.i = 11。
        要知道不是所有的类都能实现深度clone的。例如,如果把上面的CloneB类中的UnCloneA类型变量改成StringBuffer类型,看一下JDK API中关于StringBuffer的说明,StringBuffer没有重载clone()方法,更为严重的是StringBuffer还是一个final类,这也是说我们也不能用继承的办法间接实现StringBuffer的clone。如果一个类中包含有StringBuffer类型对象或和StringBuffer相似类的对象,我们有两种选择:要么只能实现影子clone,要么就在类的clone()方法中加一句(假设是SringBuffer对象,而且变量名仍是unCA): o.unCA = new StringBuffer(unCA.toString()); //原来的是:o.unCA = (UnCloneA)unCA.clone();
       还要知道的是除了基本数据类型能自动实现深度clone以外,String对象是一个例外,它clone后的表现好象也实现了深度clone,虽然这只是一个假象,但却大大方便了我们的编程。
       Clone中String和StringBuffer的区别
       应该说明的是,这里不是着重说明String和StringBuffer的区别,但从这个例子里也能看出String类的一些与众不同的地方。
       下面的例子中包括两个类,CloneC类包含一个String类型变量和一个StringBuffer类型变量,并且实现了clone()方法。在StrClone类中声明了CloneC类型变量c1,然后调用c1的clone()方法生成c1的拷贝c2,在对c2中的String和StringBuffer类型变量用相应的方法改动之后打印结果:

[java] view plain copy




执行结果:

[java] view plain copy




        打印的结果可以看出,String类型的变量好象已经实现了深度clone,因为对c2.str的改动并没有影响到c1.str!难道Java把Sring类看成了基本数据类型?其实不然,这里有一个小小的把戏,秘密就在于c2.str = c2.str.substring(0,5)这一语句!实质上,在clone的时候c1.str与c2.str仍然是引用,而且都指向了同一个String对象。但在执行c2.str = c2.str.substring(0,5)的时候,它作用相当于生成了一个新的String类型,然后又赋回给c2.str。这是因为String被Sun公司的工程师写成了一个不可更改的类(immutable class),在所有String类中的函数都不能更改自身的值。






欢迎光临 信息发布软件,b2b软件,广告发布软件 (http://postbbs.com/) Powered by Discuz! X3.2