iT邦幫忙

2024 iThome 鐵人賽

DAY 13
0
Software Development

深入淺出Java 30天系列 第 13

Day 13: 明智地覆寫clone(上)

  • 分享至 

  • xImage
  •  

當一個物件需要被clone時,該物件的類別需要實作Cloneable ,但是Cloneable並沒有定義clone()這個method,光靠實作Cloneable,很難認定這個物件可以被clone,因為類別有可能沒有撰寫clone()這個method。
由於所有類別都會繼承Object這個類別,如果類別沒有撰寫clone(),只能依靠reflection的機制,在clone()被呼叫的時候,呼叫Object.clone()Object.clone()會先檢查類別有沒有實作Cloneable ,沒有的話會丟出CloneNotSupportedException。有實作的話,會先生成一個新的物件,並把物件中每一個欄位的值複製到新的物件,然後回傳。下面的範例,p2是從p1clone出來的,所以p2每個欄位的值會跟p1一模一樣。

class PersonForClone implements Cloneable {
    private String name;
    private int age;

    public PersonForClone(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public static void main(String[] args) {
        PersonForClone p1 = new PersonForClone("Lucky", 10);
        try {
            PersonForClone p2 = (PersonForClone) p1.clone();
            System.out.print("Person: " + p2); //Person{name='Lucky', age=10'}
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}

Object.clone()除了檢查類別有沒有實作Cloneable,還會遵守下面幾個規則去clone物件:

  • x.clone() != x:clone出來的物件會是一個新的物件,所以不會跟原本的reference相同
  • x.clone().getClass() == x.getClass():clone出來的物件和原物件的類別一定是相同
  • x.clone().equals(x):clone出來的物件因為跟原物件一模一樣,所以用equals()檢查會是相等的

但這些規則對於能否正確clone物件,其實還遠遠不夠,舉例來說,物件中的欄位是array或list,如果單純地用Object.clone(),clone之後雖然新舊兩個物件的reference不同,但物件中的array或list的reference還是相同。
下面使用System.identityHashCode驗證p1p2的jobs後發現,這兩個list確實為同一個list。這在後面會造成很多問題,例如p1的jobs修改了,要使用p2的jobs時,才發現被修改了,甚至p1的jobs被設為null回收掉,p2jobs因為指到同一個reference,也跟著消失,如果p2jobs後面還有使用,會出現runtime error。

class PersonForClone implements Cloneable {
    private String name;
    private int age;
    private String[] jobs;

    public PersonForClone(String name, int age , String[] jobs) {
        this.name = name;
        this.age = age;
        this.jobs = jobs;
    }

    @Override public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age + '\'' + 
                ", jobs=" + jobs +
                '}';
    }

    public static void main(String[] args) {
        String[] jobs = {"Developer", "Designer", "PM"};

        System.out.println("p1 jobs reference: " + Integer.toHexString(System.identityHashCode(jobs)));
        PersonForClone p1 = new PersonForClone("Lucky", 10, jobs);
        try {
            PersonForClone p2 = (PersonForClone) p1.clone();
            System.out.println("p2: " + p2);
            System.out.println("p2 jobs reference: " + Integer.toHexString(System.identityHashCode(p2.jobs)));
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}


所以這時候就可以發現,自己覆寫clone()處理一些細節是有必要的,至於覆寫clone()有什麼好處,該注意什麼,我們明天再聊聊吧~~


上一篇
Day 12: 覆寫equals時,也要覆寫hashCode(下)
下一篇
Day 14: 明智地覆寫clone(下)
系列文
深入淺出Java 30天30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言