Javaその他

Cloneableインタフェースを使って独自クラスのオブジェクトのコピー(clone)を作成してみた

Javaで、ある変数が独自クラスのオブジェクトを参照しているとき、参照先の値が変更されると参照元の値も変更されてしまうが、Cloneableインターフェースを実装し、cloneメソッドをオーバーライドすることで、変更前の値をもつオブジェクトのコピーを取ることができる。

今回は、Cloneableインタフェースを使って独自クラスのオブジェクトのコピー(clone)を利用したサンプルプログラムを作成してみたので、共有する。

前提条件

下記記事の「IntelliJ IDEA上でSpring Bootプロジェクトの読み込み」まで完了していること。

IntelliJ IDEA上でGradleを使ってWeb画面のSpring Bootプロジェクトを作成してみたSpring Bootのプロジェクトを新規作成を「IntelliJ IDEA」のメニューから実施しようとしたところ、無料の「Commun...

サンプルプログラムの作成

作成したサンプルプログラムの構成は以下の通り。
サンプルプログラムの構成
なお、上記の赤枠は、今回作成・変更したプログラムである。

「build.gradle」の内容は以下の通りで、lombokを利用するための設定を追加している。

plugins {
    id 'org.springframework.boot' version '2.1.7.RELEASE'
    id 'java'
}

apply plugin: 'io.spring.dependency-management'

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    //lombokを利用するための設定
    compileOnly 'org.projectlombok:lombok:1.18.10'
    annotationProcessor 'org.projectlombok:lombok:1.18.10'
}



Contractクラス・Userクラスの内容は以下の通りで、cloneメソッドによるオブジェクトのコピーを行えるようにするため、Cloneableインタフェースを実装し、cloneメソッドをオーバーライドしている。

package com.example.demo;

import lombok.Data;

// cloneメソッドによるオブジェクトのコピーを行うため、Cloneableインタフェースを実装し、
// cloneメソッドをオーバーライドする
@Data
public class Contract implements Cloneable{

    /** 郵便番号 */
    private String postCode;

    /** 住所 */
    private String address;

    /** 電話番号 */
    private String phone;

    @Override
    public Contract clone(){
        // cloneメソッドをオーバーライドする
        // 今回はString型しかないため、単純に親クラスのcloneメソッドを呼び出す
        try {
            return (Contract) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }
}
package com.example.demo;

import lombok.Data;

// cloneメソッドによるオブジェクトのコピーを行うため
// Cloneableインタフェースを実装し、
// cloneメソッドをオーバーライドする
@Data
public class User implements Cloneable{

    /** ID */
    private int id;

    /** 名前 */
    private String name;

    /** 連絡先 */
    private Contract contract;

    public User clone(){
        // cloneメソッドをオーバーライドする
        // 今回はオブジェクト(Contract)がメンバ変数に含まれているため、
        // 親クラスのcloneメソッドを呼び出した後で、Contractクラスの
        // cloneメソッドを呼び出し設定する
        try {
            User user = (User)super.clone();
            user.contract = this.contract.clone();
            return user;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }
}

メインクラスの内容は以下の通りで、user1を変更したときの、(user1を直接参照している)user2と、(user1をcloneした)user3の値の変化が見れるようにしている。

package com.example.demo;

public class DemoMain {

    /**
     * cloneメソッドの動作を確認するメイン処理
     * @param args 起動時に受け取る引数
     */
    public static void main(String[] args){
        User user1 = makeUser1();
        User user2 = user1;
        User user3 = user1.clone();

        System.out.println("*** user1オブジェクト(変更前)の各値 ***");
        System.out.println("user1 = " + user1.toString());
        System.out.println("user2 = " + user2.toString());
        System.out.println("user3 = " + user3.toString());

        user1 = changeUser1(user1);

        System.out.println("*** user1オブジェクト(変更後)の各値 ***");
        System.out.println("user1 = " + user1.toString());
        System.out.println("user2 = " + user2.toString());
        System.out.println("user3 = " + user3.toString());
    }

    /**
     * user1オブジェクト(変更前)を生成する
     * @return user1オブジェクト(変更前)
     */
    private static User makeUser1(){
        User user1 = new User();
        user1.setId(1);
        user1.setName("テスト プリン1");

        Contract contract1 = new Contract();
        contract1.setPostCode("160-0023");
        contract1.setAddress("東京都新宿区西新宿");
        contract1.setPhone("03-1111-111X");
        user1.setContract(contract1);

        return user1;
    }

    /**
     * 引数のuser1オブジェクトを変更する
     * @param user1 user1オブジェクト(変更前)
     * @return user1オブジェクト(変更後)
     */
    private static User changeUser1(User user1){
        user1.setId(2);
        user1.setName("テスト プリン2");

        Contract contract1 = new Contract();
        contract1.setPostCode("173-0004");
        contract1.setAddress("東京都板橋区板橋");
        contract1.setPhone("03-2222-222Y");
        user1.setContract(contract1);

        return user1;
    }
}



サンプルプログラムの実行結果

サンプルプログラム「DemoMain.java」を実行した結果は以下の通りで、赤枠のように、user1を変更すると、(user1を直接参照している)user2にはuser1の変更が反映されているが、(user1をcloneした)user3にはuser1の変更が反映されないことが確認できる。
サンプルプログラムの実行結果

要点まとめ

  • 変更前の独自クラスのオブジェクトのコピー(clone)を作成できるようにするには、Cloneableインタフェースを実装し、cloneメソッドをオーバーライドする。
  • 独自クラスにオブジェクトがメンバ変数に含まれている時は、親クラスのcloneメソッドを呼び出した後で、独自クラスのメンバ変数のcloneメソッドを呼び出し設定する必要がある。