在java中,拷貝分為深拷貝和淺拷貝兩種。 java在公共超類別Object中實作了一種叫做clone的方法,這種方法clone出來的新物件為淺拷貝,而透過自己定義的clone方法為深拷貝。
如果我們new出一個新對象,用一個宣告去引用它,之後又用另一個宣告去引用前一個聲明,那麼最後的結果是:這兩個聲明的變數將指向同一個對象,一處被改全部被改。如果我們想要創建一個物件的copy,這個copy和物件的各種屬性完全相同,而且修改這個copy和原物件毫無關係,那麼這個時候我們就要用到clone方法。
package Clone;import java.util.Date;/** * * @author QuinnNorris * java中的两种拷贝机制 */public class Clone { /** * @param args * @throws CloneNotSupportedException */ public static void main(String[] args) throws CloneNotSupportedException { // TODO Auto-generated method stub ClassA valA = new ClassA(1, "old", new Date()); // 声明一个新的ClassA对象,我们不需要太关注ClassA的功能 ClassA valB = valA; // 将valA引用的对象赋给valB valA.setObject("new"); // 更改valA中的值,此时valB也被更改了,因为valA和valB指向同一个对象 valB = valA.clone();//通过clone方法制造副本 } }
ClassA類別中關於clone方法的重寫部分:
//需要实现Cloneable接口public class ClassA implements Cloneable { public ClassA clone() throws CloneNotSupportedException { return (ClassA) super.clone(); //调用父类(Object)的clone方法 } }
有人總結使用clone方法的四條法則,我們一起分享一下:
為了取得物件的一份拷貝,我們可以利用Object類別的clone()方法。
在衍生類別中覆寫基底類別的clone()方法,並宣告為public。
在衍生類別的clone()方法中,呼叫super.clone()。
在衍生類別中實作Cloneable介面。
在java.lang.Object的中,他將clone方法設定為protected修飾,這是很特殊的一種情況。 protected的作用域是:包可見+可繼承。之所以這樣設置,是因為這個方法要返回的是克隆出來的對象,即clone方法要去克隆的類型是未知的,沒有辦法確定返回值的類型,自然只能讓子孫後代來實現它重寫它,為了能夠讓後代繼承而又不過與張開,設置為了protected類型。
那麼我們重寫clone方法的時候為什麼要去實作Cloneable介面呢?事實上,Cloneable接口是java中的一個標記接口,標記接口是指那些沒有方法和屬性的接口,他們存在只是為了讓大家知道一些信息,而且在用:xxx instanceof Cloneable 的時候可以進行判斷。 Cloneable這個介面的出現就是為了讓設計者知道要進行克隆處理了。如果一個物件需要克隆,但是沒有實現(實際上,這裡的“實現”換成“寫上”更準確)Cloneable接口,那麼會產生一個已檢驗異常。
我們為了達到複製一個和呼叫方法的這個物件一模一樣的物件的目的,我們需要使用父類別的clone方法,父類別也以此推,知道達到了Object的clone方法,那麼Object的clone方法有什麼用呢? API中是這樣說的:
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()。
上面就是API中對clone的一部分基本講解。我們可以得出結論的是,只要合理的呼叫了spuer.clone( )它就會傳回一個被克隆的物件。在運行時刻,Object中的clone()識別出你要複製的是哪一個對象,然後為此對象分配空間,並進行對象的複製,將原始對象的內容一一複製到新對象的存儲空間中。在這個克隆物件中,所有的屬性都和被複製的物件的屬性相同,而這些相同的屬性分為兩種:
第一種: 八大原始類型和不可變的對象(例如String)
第二種 : 其他類別物件
對於第一種,clone方法將他們的值設為原始物件的值,沒有任何問題。對於第二種,clone方法只是簡單的將複製的新物件的引用指向原始物件指向的引用,第二種的類別物件會被兩個物件修改。那麼這個時候就涉及一個深淺拷貝的概念了。
#淺拷貝:被拷貝物件的所有變數都含有與原來的物件相同的值,而所有的對其他物件的引用仍然指向原來的對象。換言之,淺複製僅複製所考慮的對象,而不複製它所引用的對象。
比如举个例子,一个类A中有另外一个类B类型的变量。在A重写clone函数调用super.clone的时候,创建的新对象和原来对象中的类B类型的变量是同一个,他们指向了同一个B的类型变量。如果在A中对B的变量做了修改,在新的拷贝出来的对象中B的变量也会被同样的修改。
请记住,直接调用super.clone实现的clone方法全部都是浅拷贝。
深拷贝:被拷贝对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深复制把要复制的对象所引用的对象都复制了一遍。
通俗的说,如果说浅拷贝,开始的时候是两条线,如果在最后有一个其他类的变量,那么这两条线最后会合二为一,共同指向这变量,都能对他进行操作。深拷贝则是完完全全的两条线,互不干涉,因为他已经把所有的内部中的变量的对象全都复制一遍了。
深拷贝在代码中,需要在clone方法中多书写调用这个类中其他类的变量的clone函数。
在框架中,有的时候我们发现其中并没有重写clone方法,那么我们在需要拷贝一个对象的时候是如何去操作的呢?答案是我们经常会使用串行化方法,实现Serializable接口。
去寻找其他的方法来替代深拷贝也是无可奈何的事情,如果采用传统的深拷贝,难道你拷贝一个对象的时候向其中追无数层来拷贝完所有的对象变量么?先不谈这么做的时间消耗,仅仅是写这样的代码都会让人望而生畏。串行化深拷贝就是这样一个相对简单的方法。
把对象写到流里的过程是串行化(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()); }
虽然这种学院派的代码看起来很复杂,其实只是把对象放到流里,再拿出来。相比较分析判断无数的clone,这样简直是再简单不过了。这样做的前提是对象以及对象内部所有引用到的对象都是可串行化的,否则,就需要仔细考察那些不可串行化的对象是否设成transient。
transient:一个对象只要实现了Serilizable接口,这个对象就可以被序列化(序列化是指将java代码以字节序列的形式写出,即我们上面代码前三行写入对象),Java的这种序列化模式为开发者提供了很多便利,可以不必关系具体序列化的过程,只要这个类实现了Serilizable接口,这个的所有属性和方法都会自动序列化。但是有种情况是有些属性是不需要序列号的,所以就用到这个关键字。只需要实现Serilizable接口,将不需要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会序列化到指定的目的地中。
在实际的应用中,深拷贝和浅拷贝只是两个概念,不一定谁比谁好,要按照实际的工作来确定如何去拷贝一个对象。如果在数据库操作方面,为了取出一张表时不涉及其他的表,肯定需要使用浅拷贝,而在框架的Serializable中,虽然耗时,但是深拷贝是非常有必要的。
在java中,拷贝分为深拷贝和浅拷贝两种。java在公共超类Object中实现了一种叫做clone的方法,这种方法clone出来的新对象为浅拷贝,而通过自己定义的clone方法为深拷贝。
以上就是java拷贝机制详解的内容,更多相关内容请关注PHP中文网(m.sbmmt.com)!