溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

關于Java的拷貝知識有哪些

發(fā)布時間:2021-10-23 15:49:18 來源:億速云 閱讀:110 作者:iii 欄目:編程語言

這篇文章主要介紹“關于Java的拷貝知識有哪些”,在日常操作中,相信很多人在關于Java的拷貝知識有哪些問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”關于Java的拷貝知識有哪些”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!

1、創(chuàng)建對象的5種方式

  1. 通過 new 關鍵字
    ?      

    這是最常用的一種方式,通過 new 關鍵字調用類的有參或無參構造方法來創(chuàng)建對象。比如 Object obj = new Object();

    ?    
  2. 通過 Class 類的 newInstance() 方法
    ?      

    這種默認是調用類的無參構造方法創(chuàng)建對象。比如 Person p2 = (Person) Class. forName("com.ys.test. Person"). newInstance();

    ?    
  3. 通過 Constructor 類的 newInstance 方法
    ?      

    這和第二種方法類時,都是通過反射來實現(xiàn)。通過 java.lang.relect.Constructor 類的 newInstance() 方法指定某個構造器來創(chuàng)建對象?! erson p3 = (Person) Person.class.getConstructors()[0].newInstance();   實際上第二種方法利用 Class 的 newInstance() 方法創(chuàng)建對象,其內部調用還是 Constructor 的 newInstance() 方法。

    ?    
  4. 利用 Clone 方法
    ?      

    Clone 是 Object 類中的一個方法,通過 對象A.clone() 方法會創(chuàng)建一個內容和對象 A 一模一樣的對象 B,clone 克隆,顧名思義就是創(chuàng)建一個一模一樣的對象出來。  Person p4 = (Person) p3.clone();

    ?    
  5. 序列化
    ?      

    序列化是把堆內存中的 Java 對象數(shù)據(jù),通過某種方式把對象存儲到磁盤文件中或者傳遞給其他網(wǎng)絡節(jié)點(在網(wǎng)絡上傳輸)。而反序列化則是把磁盤文件中的對象數(shù)據(jù)或者把網(wǎng)絡節(jié)點上的對象數(shù)據(jù),恢復成Java對象模型的過程。序列化

    ?    
 

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

Clone 方法

如果創(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=中山路]]

?  

結論:

  1. 原對象與新對象是兩個不同的對象。
  2. 拷貝出來的新對象與原對象內容一致
  3. 接著將新對象里面的信息進行了修改,然后輸出發(fā)現(xiàn)原對象里面的部分信息也跟著變了。其中 基本類型跟 String類型的改變不會影響到 原始對象的改變。而其他的Ojbect 類型改變的時候會影響到原始數(shù)據(jù)。上面的結論稱為淺拷貝     「淺拷貝」:創(chuàng)建一個新對象,然后將當前對象的非靜態(tài)字段復制到該對象,如果字段類型是值類型(基本類型跟String)的,那么對該字段進行     「復制」;如果字段是引用類型的,     「則只復制該字段的引用而不復制引用指向的對象(也就是只復制對象的地址)」。此時新對象里面的引用類型字段相當于是原始對象里面引用類型字段的一個副本,原始對象與新對象里面的引用字段     指向的是同一個對象。因此,修改clonePerson里面的address內容時,原person里面的address內容會跟著改變。
 

深拷貝

了解了淺拷貝,那么深拷貝是什么也就很清楚了。那么該如何實現(xiàn)深拷貝呢?Object 類提供的 clone 是只能實現(xiàn) 淺拷貝的。,即將「引用類型的屬性內容也拷貝一份新的」。那么,實現(xiàn)深拷貝我這里收集到兩種方式:「第一種」是給需要拷貝的引用類型也實現(xiàn)Cloneable接口并覆寫clone方法;「第二種」則是利用序列化。接下來分別對兩種方式進行演示。

 

深拷貝-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>

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內容。

AI