Effective Javaを読んでいる⑪

cloneを賢くオーバーライドせよ

Clonableインターフェース周辺について何となくわかってから読まないと意味がわからなさそうなので意味がわかってから続きをよむ

Object#clone()

  1. Objectのcloneをオーバーライドする。
  2. Clonableインターフェースを実装する。これをしないとCloneNotSupportedExceptionがスローされる。(cloneできますよマークをつける)

    注意点

  3. フィールドもちゃんとクローンしないとだめ(同じ参照のフィールドをもったクローンができてしまう)
  4. newではなく親クラスのcloneでインスタンスを作ること。(親クラスが持ってるフィールドがクローンされない)
    リストの番号がリセットされないぞ。

Javaクローンメモ(Hishidama's Java Cloneable Memo)

Java - cloneメソッドの正しい実装方法 - Qiita

javadocのcloneの意味があんまりわからん

Object (Java Platform SE 8)

2004-03-04 - Cello弾きのわらわら。

import java.util.ArrayList;
import java.util.List;

public class Example implements Cloneable{
    public List<Integer> list = new ArrayList<>();

    public static void main(String[] args){
        Example ex1 = new Example();
        try {
            //Exampleがcloneをオーバーライドしていない場合シャローコピーされる
            System.out.println(((Example) ex1.clone()).list == ex1.list);
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}

Exampleが親クラスを持っていた時super.cloneを呼び出したらExample固有の(親クラスがもっていない)フィールドはシャローコピーされるの?

public class ExampleChild extends Example {
    String str = null;
    
    public ExampleChild clone() throws CloneNotSupportedException{
        return (ExampleChild) super.clone();
    }
    
    public static void main(String [] args) throws CloneNotSupportedException{
        ExampleChild ex = new ExampleChild();
        ex.str = "unko";
        System.out.println(ex.clone().str);// unkoが出力される
    }
}

された。

親クラスがcloneをコンストラクタで実装していた場合

import java.util.ArrayList;
import java.util.List;

public class ExampleChild extends Example {

    public static void main(String [] args) throws CloneNotSupportedException{
        ExampleChild ex = new ExampleChild();
        //true
        System.out.println(ex.getClass() != ex.clone().getClass());
    }
}

class Example implements Cloneable{
    public List<Integer> list = new ArrayList<>();
    
    @Override
    public Example clone(){
        Example ex = new Example();
        //フィールドのコピーは省略
        return ex;
    }
}

親クラスがsuper.cloneで実装していた場合

import java.util.ArrayList;
import java.util.List;

public class ExampleChild extends Example {

    public static void main(String [] args) throws CloneNotSupportedException{
        ExampleChild ex = new ExampleChild();
        //false
        System.out.println(ex.getClass() != ex.clone().getClass());
    }
}

class Example implements Cloneable{
    public List<Integer> list = new ArrayList<>();
    
    @Override
    public Example clone() throws CloneNotSupportedException{
        return (Example) super.clone();
    }

}
  1. Cloneableを実装したクラスはpublicでcloneをオーバーライドし、そのクラス自身の型を返却しろ
  2. オーバーライドしたcloneメソッドはまずsuper.cloneを呼び出し、必要に応じてフィールドをいじれ
     →いじらないと参照がコピーされるだけだから
     →イミュータブルなオブジェクトへの参照とかprimitiveなフィールドは多分そのままでいいぞ
     →でもオブジェクトの生成時刻とかユニークIDとかはだめだぞ

コピーコンストラクタとかコピーファクトリがcloneより優れている点

  1. よくわからん手順でインスタンスが生成されない
  2. 余計なチェック例外を投げない
  3. finalなフィールドがあっても大丈夫
  4. 入力の型が自由
  5. キャストもいらない
conversion constructorsってなんだ

Conversion Constructor(変換コンストラクタ) - Strategic Choice
new File(String path)みたいの
path.clone()でFile型は返せないよね、みたいなはなし