【JAVA零基礎入門系列】Day14 Java對象的克隆

  【JAVA零基礎入門系列】(已完結)導航目錄html

  今天要介紹一個概念,對象的克隆。本篇有必定難度,請先作好心理準備。看不懂的話能夠多看兩遍,仍是不懂的話,能夠在下方留言,我會看狀況進行修改和補充。java

  克隆,天然就是將對象從新複製一份,那爲何要用克隆呢?何時須要使用呢?先來看一個小栗子:數組

  簡單起見,咱們這裏用的是Goods類的簡單版本。ide

public class Goods {
    private String title;
    private double price;
    
    public Goods(String aTitle, double aPrice){
        title = aTitle;
        price = aPrice;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    public void setTitle(String title) {
        this.title = title;
    }
  //用於打印輸出商品信息
public void print(){ System.out.println("Title:"+title+" Price:"+price); } }

  而後咱們來使用這個類。函數

public class GoodsTest {
    public static void main(String[] args){
        Goods goodsA = new Goods("GoodsA",20);
        Goods goodsB = goodsA;
        System.out.println("Before Change:");
        goodsA.print();
        goodsB.print();

        goodsB.setTitle("GoodsB");
        goodsB.setPrice(50);
        System.out.println("After Change:");
        goodsA.print();
        goodsB.print();
    }
}

  咱們建立了一個Goods對象賦值給變量goodsA,而後又建立了一個Goods變量,並把goodsA賦值給它,先調用Goods的print方法輸出這兩個變量中的信息,而後調用Goods類中的setTitle和setPrice方法來修改goodsB中的對象內容,再輸出兩個變量中的信息,下面是輸出:post

Before Change:
Title:GoodsA Price:20.0
Title:GoodsA Price:20.0
After Change:
Title:GoodsB Price:50.0
Title:GoodsB Price:50.0

  這裏咱們發現了靈異事,咱們明明修改的是goodsB的內容,但是goodsA的內容也一樣發生了改變,這到底是爲何呢?別心急,且聽我慢慢道來。優化

  在Java語言中,數據類型分爲值類型(基本數據類型)和引用類型,值類型包括int、double、byte、boolean、char等簡單數據類型,引用類型包括類、接口、數組等複雜類型。使用等號賦值都是進行值傳遞的,如將一個整數型變量賦值給另外一個整數型變量,那麼後者將存儲前者的值,也就是變量中的整數值,對於基本類型如int,double,char等是沒有問題的,可是對於對象,則又是另外一回事了,這裏的goodsA和goodsB都是Goods類對象的變量,可是它們並無存儲Goods類對象的內容,而是存儲了它的地址,也就至關於C++中的指針,若是對於指針不瞭解,那我就再舉個栗子好了。咱們以前舉過一個栗子,把計算機比做是倉庫管理員,內存比做是倉庫,你要使用什麼類型的變量,就須要先登記,而後管理員纔會把東西給你,但若是是給你分配一座房子呢?這時候不是把房子搬起來放到登記簿粒,而是登記下房子的地址,這裏的地址就是咱們的類對象變量裏記錄的內容,因此,當咱們把一個類對象變量賦值給另外一個類對象變量,如goodsB = goodsA時,實際上只是把A指向的對象地址賦值給了B,這樣B也一樣指向這個地址,因此這時候,goodsA和goodsB操做的是同一個對象。this

  因此,若是隻是簡單的賦值的話,以後對於goodsA和goodsB的操做都將影響同一個對象,這顯然不是咱們的本意。也許你還會問,直接再new一個對象不就行了,確實如此,但有時候,若是咱們須要保存一個goodsA的副本,那就不只僅要new一個對象,還須要進行一系列賦值操做才能將咱們的新對象設置成跟goodsA對象同樣,並且Goods類越複雜,這個操做將會越繁瑣,另外使用clone方法還進行本地優化,效率上也會快不少,總而言之,就是簡單粗暴。spa

  那如何使用克隆呢?這裏咱們就要介紹咱們牛逼哄哄的Object類了,全部的類都是Object類的子類,雖然咱們並無顯式聲明繼承關係,但全部類都難逃它的魔掌,它有兩個protected方法,其中一個就是clone方法。指針

  下面我來展現一波正確的騷操做:

//要使用克隆方法須要實現Cloneable接口
public class Goods implements Cloneable{
    private String title;
    private double price;

    public Goods(String aTitle, double aPrice){
        title = aTitle;
        price = aPrice;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public void print(){
        System.out.println("Title:"+title+" Price:"+price);
    }

    //這裏重載了接口的clone方法
    @Override
    protected Object clone(){
        Goods g = null;
    //這裏是異常處理的語句塊,能夠先不用瞭解,只要知道是這樣使用就好,以後的文章中會有詳細的介紹
try{ g = (Goods)super.clone(); }catch (CloneNotSupportedException e){ System.out.println(e.toString()); } return g; } }

  其實修改的地方只有兩個,一個是定義類的時候實現了Cloneable接口,關於接口的知識在以後會有詳細說明,這裏只要簡單理解爲是一種規範就好了,而後咱們重載了clone方法,並在裏面調用了父類也就是(Object)的clone方法。能夠看到咱們並無new一個新的對象,而是使用父類的clone方法進行克隆,關於try catch的知識這裏不作過多介紹,以後會有文章作詳細說明,這裏只須要理解爲try語句塊裏是一個可能發生錯誤的代碼,catch會捕獲這種錯誤並進行處理。

  接下來咱們再使用這個類的克隆方法:

public class GoodsTest {
    public static void main(String[] args){
        Goods goodsA = new Goods("GoodsA",20);
        Goods goodsB = (Goods)goodsA.clone();
        System.out.println("Before Change:");
        goodsA.print();
        goodsB.print();

        goodsB.setTitle("GoodsB");
        goodsB.setPrice(50);
        System.out.println("After Change:");
        goodsA.print();
        goodsB.print();
    }
}

  咱們僅僅是把賦值改爲了調用goodsA的clone方法並進行類型轉換。輸出以下:

Before Change:
Title:GoodsA Price:20.0
Title:GoodsA Price:20.0
After Change:
Title:GoodsA Price:20.0
Title:GoodsB Price:50.0

  看,這樣不就達到咱們目的了嗎?是否是很簡單?

  可是別高興的太早,關於克隆,還有一點內容須要介紹。

  克隆分爲淺克隆和深克隆。咱們上面使用的只是淺克隆,那二者有什麼區別呢?這裏再舉一個栗子,使用的是簡化版的Cart類:

public class Cart implements Cloneable{
    //實例域
    Goods goodsList = new Goods("",0);//簡單起見,這裏只放了一個商品
    double budget = 0.0;//預算

    //構造函數
    public Cart(double aBudget){
        budget = aBudget;
    }

    //獲取預算
    public double getBudget() {
        return budget;
    }

    //修改預算
    public void setBudget(double aBudget) {
        budget = aBudget;
    }

    //這裏只是簡單的將商品進行了賦值
    public void addGoods(Goods goods){
        goodsList = (Goods) goods.clone();
    }

    //這是爲了演示加上的代碼,僅僅將商品標題修改爲新標題
    public void changeGoodsTitle(String title){
        goodsList.setTitle(title);
    }

    //打印商品信息
    public void print(){
        System.out.print("Cart內的預算信息:"+budget+" 商品信息:");
        goodsList.print();
    }

    //重載clone方法
    @Override
    protected Object clone(){
        Cart c = null;
        try{
            c = (Cart)super.clone();
        }catch (CloneNotSupportedException e ){
            e.printStackTrace();
        }
        return c;
    }
}

  這裏將goodsList由數組改爲了單個對象變量,僅僅用於演示方便,還增長了一個changeGoodsTitle方法,用於將商品的標題修改爲另外一個標題,接下來修改一下GoodsTest類:

public class GoodsTest {
    public static void main(String[] args){
        Goods goodsA = new Goods("GoodsA",20);//新建一個商品對象
        Cart cartA = new Cart(5000);//新建一個購物車對象
        cartA.addGoods(goodsA);//添加商品
        Cart cartB = (Cart) cartA.clone();//使用淺克隆

     //輸出修改前信息 System.out.println(
"Before Change:"); cartA.print(); cartB.print();
     //修改購物車A中的商品標題 cartA.changeGoodsTitle(
"NewTitle");
     //從新輸出修改後的信息 System.out.println(
"After Change:"); cartA.print(); cartB.print(); } }

  輸出信息:

Before Change:
Cart內的預算信息:5000.0 商品信息:Title:GoodsA Price:20.0
Cart內的預算信息:5000.0 商品信息:Title:GoodsA Price:20.0
After Change:
Cart內的預算信息:5000.0 商品信息:Title:NewTitle Price:20.0
Cart內的預算信息:5000.0 商品信息:Title:NewTitle Price:20.0

  咱們發現,雖然咱們調用的是cartA中的方法修改購物車A中的商品信息,但購物車B中的信息一樣被修改了,這是由於使用淺克隆模式的時候,成員變量若是是對象等複雜類型時,僅僅使用的是值拷貝,就跟咱們以前介紹的那樣,因此cartB雖然是cartA的一個拷貝,可是它們的成員變量goodsList卻共用一個對象,這樣就藕斷絲連了,顯然不是咱們想要的效果,這時候就須要使用深拷貝了,只須要將Cart類的clone方法修改一下便可:

    @Override
    protected Object clone(){
        Cart c = null;
        try{
            c = (Cart)super.clone();
            c.goodsList = (Goods) goodsList.clone();//僅僅添加了這段代碼,將商品對象也進行了克隆
        }catch (CloneNotSupportedException e ){
            e.printStackTrace();
        }
        return c;
    }

  如今再來運行一下:

Before Change:
Cart內的預算信息:5000.0 商品信息:Title:GoodsA Price:20.0
Cart內的預算信息:5000.0 商品信息:Title:GoodsA Price:20.0
After Change:
Cart內的預算信息:5000.0 商品信息:Title:NewTitle Price:20.0
Cart內的預算信息:5000.0 商品信息:Title:GoodsA Price:20.0

  這樣就獲得了咱們想要的結果了。

  這樣,對象的拷貝就講完了。

  嗎?

  哈哈哈哈,不要崩潰,並無,還有一種更復雜的狀況,那就是當你的成員變量裏也包含引用類型的時候,好比Cart類中有一個CartB類的成員變量,CartB類中一樣存在引用類型的成員變量,這時候,就存在多層克隆的問題了。這裏再介紹一個騷操做,只須要了解便可,那就是序列化對象。操做以下:

import java.io.*;

public class Cart implements Serializable{
    //實例域
    Goods goodsList = new Goods("",0);//簡單起見,這裏只放了一個商品
    double budget = 0.0;//預算

    //構造函數
    public Cart(double aBudget){
        budget = aBudget;
    }

    //獲取預算
    public double getBudget() {
        return budget;
    }

    //修改預算
    public void setBudget(double aBudget) {
        budget = aBudget;
    }

    //這裏只是簡單的將商品進行了賦值
    public void addGoods(Goods goods){
        goodsList = (Goods) goods.clone();
    }

    //這是爲了演示加上的代碼,僅僅將商品標題修改爲新標題
    public void changeGoodsTitle(String title){
        goodsList.setTitle(title);
    }

    //打印商品信息
    public void print(){
        System.out.print("Cart內的預算信息:"+budget+" 商品信息:");
        goodsList.print();
    }
  //這裏是主要是騷操做
    public Object deepClone() throws IOException, OptionalDataException,ClassNotFoundException {
          // 序列化
      ByteArrayOutputStream bo = null;
      ObjectOutputStream oo = null;
      ObjectInputStream oi = null;
      ByteArrayInputStream bi = null;
      try{
        bo = new ByteArrayOutputStream();
         oo
= new ObjectOutputStream(bo); oo.writeObject(this); // 反序列化    bi = new ByteArrayInputStream(bo.toByteArray());     oi = new ObjectInputStream(bi);
        Object obj = oi.readObject();
      }
      catch(Exception e){
        e.printStackTrace();
      }
      finally {
        // 關閉流
        try{
          bo = null;
          bi = null;
          if(oo != null){
            oo.close();
          }
          if(oi != null){
            oi.close();
          }
        }catch(Eception e){
          e.printStackTrace();
        }
      }
return obj; } }

  關於這種方法我就很少作介紹了,你們只須要知道有這樣一種方法就好了,之後若是遇到了須要使用這種狀況,就知道該怎樣處理了。

  這裏總結一下,對象的克隆就是把一個對象的當前狀態從新拷貝一份到另外一個新對象中,兩個對象變量指向不一樣的對象,淺克隆僅僅調用super.clone()方法,對成員變量也只是簡單的值拷貝,因此當成員變量中有數組,對象等複雜類型的時候,就會存在藕斷絲連的混亂關係,深拷貝不只僅調用super.clone()方法進行對象拷貝,將對象中的複雜類型一樣進行了拷貝,這樣兩個對象就再無瓜葛,井水不犯河水了。

  至此,對象的克隆就真正的結束了,歡迎你們繼續關注!若有不懂的問題能夠留言。也歡迎各位大佬來批評指正。喜歡個人教程的話記得動動小手點下推薦,也歡迎關注個人博客。

相關文章
相關標籤/搜索