您好,登錄后才能下訂單哦!
這篇文章主要介紹“關于Java的拷貝知識有哪些”,在日常操作中,相信很多人在關于Java的拷貝知識有哪些問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”關于Java的拷貝知識有哪些”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
?這是最常用的一種方式,通過 new 關鍵字調用類的有參或無參構造方法來創(chuàng)建對象。比如 Object obj = new Object();
?
?這種默認是調用類的無參構造方法創(chuàng)建對象。比如 Person p2 = (Person) Class. forName("com.ys.test. Person"). newInstance();
?
?這和第二種方法類時,都是通過反射來實現(xiàn)。通過 java.lang.relect.Constructor 類的 newInstance() 方法指定某個構造器來創(chuàng)建對象?! erson p3 = (Person) Person.class.getConstructors()[0].newInstance(); 實際上第二種方法利用 Class 的 newInstance() 方法創(chuàng)建對象,其內部調用還是 Constructor 的 newInstance() 方法。
?
?Clone 是 Object 類中的一個方法,通過 對象A.clone() 方法會創(chuàng)建一個內容和對象 A 一模一樣的對象 B,clone 克隆,顧名思義就是創(chuàng)建一個一模一樣的對象出來。 Person p4 = (Person) p3.clone();
?
?序列化是把堆內存中的 Java 對象數(shù)據(jù),通過某種方式把對象存儲到磁盤文件中或者傳遞給其他網(wǎng)絡節(jié)點(在網(wǎng)絡上傳輸)。而反序列化則是把磁盤文件中的對象數(shù)據(jù)或者把網(wǎng)絡節(jié)點上的對象數(shù)據(jù),恢復成Java對象模型的過程。序列化
?
java賦值是復制「對象引用」,如果我們想要得到一個對象的==副本==,使用賦值操作是無法達到目的的:修改新對象的值會同時修改舊對象的值。
public class Client
{
public static void main(String[] args) throws CloneNotSupportedException
{
Person person = new Person(15, "sowhat", new Address("河北", "建華南大街"));
Person p1 = person;
p1.setAge(45);
System.out.println(p1.hashCode());
System.out.println(person.hashCode());
System.out.println("================");
System.out.println(p1.display());
System.out.println(person.display());
}
}
如果創(chuàng)建一個對象的新的副本,也就是說他們的初始狀態(tài)完全一樣,但以后可以「改變各自的狀態(tài)」,而互不影響,就需要用到java中對象的復制,如原生的clone()方法。本次講解的是 Java 的深拷貝和淺拷貝,其實現(xiàn)方式正是通過調用 Object 類的 clone() 方法來完成。在 Object.class 類中,源碼為:
/**
* ...
* performs a "shallow copy" of this object, not a "deep copy" operation.
* 上面這里已經(jīng)說明了,clone()方法是淺拷貝,而不是深拷貝
* @see java.lang.Cloneable
*/
protected native Object clone() throws CloneNotSupportedException;
這是一個用 native 關鍵字修飾的方法,關于native關鍵字有一篇博客專門有介紹,不理解也沒關系,只需要知道用 native 修飾的方法就是告訴操作系統(tǒng),這個方法我不實現(xiàn)了,讓操作系統(tǒng)去實現(xiàn)(參考JNI)。具體怎么實現(xiàn)我們不需要了解,只需要知道 clone方法的作用就是「復制對象」,產(chǎn)生一個新的對象。那么這個新的對象和原對象是==什么關系呢==?
這里再給大家普及一個概念,在 Java 中「基本類型和引用類型」的區(qū)別。在 Java 中數(shù)據(jù)類型可以分為兩大類:基本類型和引用類型。基本類型也稱為值類型,分別是字符類型 char,布爾類型 boolean以及數(shù)值類型 byte、short、int、long、float、double。引用類型則包括類、接口、數(shù)組、枚舉等?! ava 將內存空間分為「堆和?!?/strong>?;绢愋椭苯釉跅?stack中存儲數(shù)值,而引用類型是將引用放在棧中,實際存儲的值是放在堆 heap中,通過棧中的引用指向堆中存放的數(shù)據(jù)?! ?img src="https://cache.yisu.com/upload/information/20210524/357/10708.png" alt="關于Java的拷貝知識有哪些">
上圖定義的 a 和 b 都是基本類型,其值是「直接存放在棧中」的;而 c 和 d 是 String 聲明的,這是一個引用類型,「引用地址是存放在棧中,然后指向堆的內存空間」?! ∠旅?d = c;這條語句表示將 c 的引用賦值給 d,那么 c 和 d 將指向同一塊堆內存空間。
接下來用代碼看看淺拷貝的效果。
package mytest;
@Data//lombok注解
class Person implements Cloneable
{
private int age;
private String name;
private Address address;
public Person(int age, String name, Address address)
{
this.age = age;
this.name = name;
this.address = address;
}
@Override
protected Object clone() throws CloneNotSupportedException
{
return super.clone();
}
public String display()
{
return "Person [age=" + age + ", name=" + name + ", address=" + address + "]";
}
}
@Data//lombok注解
class Address
{
private String province;
private String street;
public Address(String province, String street)
{
this.province = province;
this.street = street;
}
@Override
public String toString()
{
return "Address [province=" + province + ", street=" + street + "]";
}
}
public class Client
{
public static void main(String[] args) throws CloneNotSupportedException
{
Person person = new Person(15, "sowhat", new Address("河北", "建華南大街"));
Person clonePerson = (Person) person.clone();
System.out.println(person);
System.out.println(clonePerson);
// 信息完全一樣
System.out.println(person.display());
System.out.println(clonePerson.display());
System.out.println("信息完全一致");
System.out.println("原始年齡:" + person.getAge());
System.out.println("克隆后原始年齡:" + clonePerson.getAge());
System.out.println("年齡完全一樣");
System.out.println("原始名字哈希值:" + person.getName().hashCode());
System.out.println("克隆后名字哈希值:" + clonePerson.getName().hashCode());
System.out.println("字符串哈希值完全一樣");
clonePerson.setName("xiaomai");
clonePerson.setAge(20);
clonePerson.getAddress().setStreet("中山路");
System.out.println(clonePerson.display());
System.out.println(person.display());
System.out.println("年齡跟姓名 是完全的深拷貝 副本跟原值無關的!");
System.out.println("地址信息的修改是淺拷貝 ");
}
}
結果如下:
?mytest.Person@15f550a
mytest.Person@6b2d4a
Person [age=15, name=sowhat, address=Address [province=河北, street=建華南大街]]
Person [age=15, name=sowhat, address=Address [province=河北, street=建華南大街]]
信息完全一致
原始年齡:15
克隆后原始年齡:15
年齡完全一樣
原始名字哈希值:-1432601412
克隆后名字哈希值:-1432601412
字符串哈希值完全一樣
Person [age=20, name=xiaomai, address=Address [province=河北, street=中山路]]
Person [age=15, name=sowhat, address=Address [province=河北, street=中山路]]
?
結論:
指向的是同一個對象
。因此,修改clonePerson里面的address內容時,原person里面的address內容會跟著改變。了解了淺拷貝,那么深拷貝是什么也就很清楚了。那么該如何實現(xiàn)深拷貝呢?Object 類提供的 clone 是只能實現(xiàn) 淺拷貝的
。,即將「引用類型的屬性內容也拷貝一份新的」。那么,實現(xiàn)深拷貝我這里收集到兩種方式:「第一種」是給需要拷貝的引用類型也實現(xiàn)Cloneable接口并覆寫clone方法;「第二種」則是利用序列化。接下來分別對兩種方式進行演示。
對于以上演示代碼,利用clone方式進行深拷貝無非就是將Address類也實現(xiàn)Cloneable,然后對Person的clone方法進行調整。讓每個引用類型屬性內部都重寫clone() 方法,既然引用類型不能實現(xiàn)深拷貝,那么我們將每個引用類型都拆分為基本類型,分別進行淺拷貝。比如上面的例子,Person 類有一個引用類型 Address(其實String 也是引用類型,但是String類型有點特殊,后面會詳細講解),我們在 Address 類內部也重寫 clone 方法。如下:
package mytest;
@Data//lombok注解
class Person implements Cloneable
{
private int age;
private String name;
private Address address;
protected int abc = 12;
public Person(int age, String name, Address address)
{
this.age = age;
this.name = name;
this.address = address;
}
@Override // clone 重載
protected Object clone() throws CloneNotSupportedException
{
Person person = (Person) super.clone();
//手動對address屬性進行clone,并賦值給新的person對象
person.address = (Address) address.clone();
return person;
}
public String display()
{
return "Person [age=" + age + ", name=" + name + ", address=" + address + "]";
}
}
@Data//lombok注解
class Address implements Cloneable
{
private String province;
private String street;
public Address(String province, String street)
{
this.province = province;
this.street = street;
}
// 深拷貝時添加
@Override
protected Object clone() throws CloneNotSupportedException
{
return super.clone();
}
@Override
public String toString()
{
return "Address [province=" + province + ", street=" + street + "]";
}
}
public class Client
{
public static void main(String[] args) throws CloneNotSupportedException
{
Person person = new Person(15, "sowhat", new Address("河北", "建華南大街"));
Person p1 = person;
p1.setAge(45);
System.out.println(p1.hashCode());
System.out.println(person.hashCode());
System.out.println(p1.display());
System.out.println(person.display());
System.out.println("-----------");
Person clonePerson = (Person) person.clone();
System.out.println(person);
System.out.println(clonePerson);
// 信息完全一樣
System.out.println(person.display());
System.out.println(clonePerson.display());
System.out.println("信息完全一致");
System.out.println("原始年齡:" + person.getAge());
System.out.println("克隆后原始年齡:" + clonePerson.getAge());
System.out.println("年齡完全一樣");
System.out.println("原始名字哈希值:" + person.getName().hashCode());
System.out.println("克隆后名字哈希值:" + clonePerson.getName().hashCode());
System.out.println("字符串哈希值完全一樣");
clonePerson.setName("sowhat1412");
clonePerson.setAge(20);
clonePerson.getAddress().setStreet("中山路");
System.out.println(clonePerson.display());
System.out.println(person.display());
System.out.println("年齡跟姓名 是完全的深拷貝 副本跟原值無關的!");
System.out.println("地址信息的修改是淺拷貝 ");
}
}
但是這種做法有個弊端,這里我們Person 類只有一個 Address 引用類型,而 Address 類沒有,所以我們只用重寫 Address 類的clone 方法,但是如果 Address 類也存在一個引用類型,那么我們也要重寫其clone 方法,這樣下去,有多少個引用類型,我們就要重寫多少次,如果存在很多引用類型,那么代碼量顯然會很大,所以這種方法不太合適。
序列化是將對象寫到流中便于傳輸,而反序列化則是把對象從流中讀取出來。這里寫到流中的對象則是原始對象的一個拷貝,因為原始對象還存在 JVM 中,所以我們可以利用對象的序列化產(chǎn)生克隆對象,然后通過反序列化獲取這個對象。注意每個需要序列化的類都要實現(xiàn) Serializable 接口,如果有某個屬性不需要序列化,可以將其聲明為 transient,即將其排除在克隆屬性之外。
package mytest;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
/**
* 利用序列化和反序列化進行對象的深拷貝
* @author ljj
*/
class DeepClone implements Serializable
{
private static final long serialVersionUID = 1412L;
public Object deepClone() throws Exception
{
//序列化
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
//反序列化
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return ois.readObject();
}
}
@Data
class Person extends DeepClone
{
private static final long serialVersionUID = 1L;
private int age;
private String name;
private Address address;
public Person(int age, String name, Address address)
{
this.age = age;
this.name = name;
this.address = address;
}
public String display()
{
return "Person [age=" + age + ", name=" + name + ", address=" + address + "]";
}
}
@Data
class Address extends DeepClone
{
private static final long serialVersionUID = 1412L;
private String province;
private String street;
public Address(String province, String street)
{
this.province = province;
this.street = street;
}
@Override
public String toString()
{
return "Address [province=" + province + ", street=" + street + "]";
}
public void setStreet(String street)
{
this.street = street;
}
}
public class Client
{
public static void main(String[] args) throws Exception
{
Person person = new Person(15, "sowhat", new Address("河北", "建華南大街"));
Person clonePerson = (Person) person.deepClone();
System.out.println(person);
System.out.println(clonePerson);
System.out.println(person.display());
System.out.println(clonePerson.display());
clonePerson.setName("sowhat1412");
clonePerson.setAge(20);
Address address = clonePerson.getAddress();
address.setStreet("中山路");
System.out.println(clonePerson.display());
System.out.println(person.display());
}
}
到此,關于“關于Java的拷貝知識有哪些”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續(xù)學習更多相關知識,請繼續(xù)關注億速云網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>
免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內容。