覚えたら書く

IT関係のデベロッパとして日々覚えたことを書き残したいです。twitter: @yyoshikaw

Lombok - @Data (2)

Lombokの@Data(lombok.Data)アノテーションの利用サンプルです。

@Dataアノテーションにより、JavaBeansを簡単に記述することができます。

以下記事に続いて@Dataを利用したコードをもう少し色々書いてみました。

本エントリでは主に、@Dataによって作成される getter/setterが防御的コピーをしているかどうかの確認をしています。


配列と@Data

配列をメンバに持つクラスに@Dataを付与するとどうなるのかを確認してみました。

■@Dataを付与したクラス

import lombok.Data;

@Data
public class ArrayWrapper {

    private long id;

    private String[] strArray;
}

■利用側のコード

public class ArrayWrapperClient {

    public static void main(String[] args) {
        ArrayWrapper obj = new ArrayWrapper();
        obj.setId(120L);

        String[] strArray = new String[] {"abc", "def", "ghi"};
        obj.setStrArray(strArray);

        System.out.println(obj);

        System.out.println("ArrayWrapper#toString: " + obj);

        System.out.println("-------");

        // 以下の各操作で防御的コピーは行われないことを確認する

        System.out.println("\n--- Before ArrayWrapper: " + obj);

        strArray[0] = "one";  // set元の配列の要素を変更
        strArray[1] = "two";  // set元の配列の要素を変更

        System.out.println("--- After  ArrayWrapper: " + obj);  // 配列内の値が変更されている


        System.out.println("\n--- Before ArrayWrapper: " + obj);

        obj.getStrArray()[2] = "dummy value";  // getterで取得した配列内の要素を変更

        System.out.println("--- After  ArrayWrapper: " + obj);  // 配列内の値が変更されている
    }
}

■実行結果

ArrayWrapper(id=120, strArray=[abc, def, ghi])
ArrayWrapper#toString: ArrayWrapper(id=120, strArray=[abc, def, ghi])
-------

--- Before ArrayWrapper: ArrayWrapper(id=120, strArray=[abc, def, ghi])
--- After  ArrayWrapper: ArrayWrapper(id=120, strArray=[one, two, ghi])

--- Before ArrayWrapper: ArrayWrapper(id=120, strArray=[one, two, ghi])
--- After  ArrayWrapper: ArrayWrapper(id=120, strArray=[one, two, dummy value])

実行結果から見てわかりますが、配列のメンバに対するgetter/setterで防御的コピーは行われていません。
そのため、セット元の配列内の値に変更を加えると、Beanクラス内のメンバの値も変更されます。

■実際に生成されているソースコード

実際に生成されているソースコードをdelombokを使って確認してみました。
防御的コピーが行われていないことが分かります。

public class ArrayWrapper {
    private long id;
    private String[] strArray;

    public ArrayWrapper() {
    }

    public long getId() {
        return this.id;
    }

    public String[] getStrArray() {
        return this.strArray;
    }

    public void setId(final long id) {
        this.id = id;
    }

    public void setStrArray(final String[] strArray) {
        this.strArray = strArray;
    }

    @Override
    public boolean equals(final Object o) {
        if (o == this) return true;
        if (!(o instanceof ArrayWrapper)) return false;
        final ArrayWrapper other = (ArrayWrapper) o;
        if (!other.canEqual((Object) this)) return false;
        if (this.getId() != other.getId()) return false;
        if (!java.util.Arrays.deepEquals(this.getStrArray(), other.getStrArray())) return false;
        return true;
    }

    protected boolean canEqual(final Object other) {
        return other instanceof ArrayWrapper;
    }

    @Override
    public int hashCode() {
        final int PRIME = 59;
        int result = 1;
        final long $id = this.getId();
        result = result * PRIME + (int) ($id >>> 32 ^ $id);
        result = result * PRIME + java.util.Arrays.deepHashCode(this.getStrArray());
        return result;
    }

    @Override
    public String toString() {
        return "ArrayWrapper(id=" + this.getId() + ", strArray=" + java.util.Arrays.deepToString(this.getStrArray()) + ")";
    }
}


Collectionと@Data

List型の値とMap型の値をメンバに持つクラスに@Dataを付与するとどうなるのかを確認してみました。
結論から言うとList型の値とMapの値のいずれも防御的コピーは行われません。そのため元のListやMapを操作することでBeanクラス内の値を変更することができます。

■@Dataを付与したクラス

import java.util.List;
import java.util.Map;

import lombok.Data;

@Data
public class CollectionWrapper {

    private long id;

    private List<String> list;

    private Map<Long, String> map;
}

■利用側のコード

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class CollectionWrapperClient {

    public static void main(String[] args) {
        List<String> srcList = new ArrayList<>();
        srcList.add("value1");
        srcList.add("value2");
        srcList.add("value3");

        Map<Long, String> srcMap = new HashMap<>();
        srcMap.put(100L, "data1");
        srcMap.put(200L, "data2");
        srcMap.put(300L, "data3");

        CollectionWrapper obj = new CollectionWrapper();
        obj.setId(10000L);
        obj.setList(srcList);
        obj.setMap(srcMap);

        System.out.println("CollectionWrapper#getList: " + obj.getList());
        System.out.println("CollectionWrapper2#getMap: " + obj.getMap());
        System.out.println("CollectionWrapper#toString: " + obj);

        System.out.println("-------");

        // 以下の各操作で防御的コピーは行われないことを確認する

        System.out.println("\n--- Before CollectionWrapper: " + obj);

        srcList.add("value999");  // set元のListに要素を追加

        System.out.println("--- After  CollectionWrapper: " + obj);  // Listの要素が増えている


        System.out.println("\n--- Before CollectionWrapper: " + obj);

        srcList.add("value999");  // set元のMapから要素を置換
        srcMap.put(100L, "new data000000001");

        System.out.println("--- After  CollectionWrapper: " + obj);  // Mapの値が置き換わっている


        System.out.println("\n--- Before CollectionWrapper: " + obj);

        obj.getList().add("memo0001");  // getterで取得したListに要素を追加

        System.out.println("--- After  CollectionWrapper: " + obj);  // Listの要素が増えている


        System.out.println("\n--- Before CollectionWrapper: " + obj);

        obj.getMap().remove(300L);  // getterで取得したMapから特定のkeyに紐づく要素を削除

        System.out.println("--- After  CollectionWrapper: " + obj);  // Mapの要素が減っている
    }
}

■実行結果

CollectionWrapper#getList: [value1, value2, value3]
CollectionWrapper2#getMap: {100=data1, 200=data2, 300=data3}
CollectionWrapper#toString: CollectionWrapper(id=10000, list=[value1, value2, value3], map={100=data1, 200=data2, 300=data3})
-------

--- Before CollectionWrapper: CollectionWrapper(id=10000, list=[value1, value2, value3], map={100=data1, 200=data2, 300=data3})
--- After  CollectionWrapper: CollectionWrapper(id=10000, list=[value1, value2, value3, value999], map={100=data1, 200=data2, 300=data3})

--- Before CollectionWrapper: CollectionWrapper(id=10000, list=[value1, value2, value3, value999], map={100=data1, 200=data2, 300=data3})
--- After  CollectionWrapper: CollectionWrapper(id=10000, list=[value1, value2, value3, value999, value999], map={100=new data000000001, 200=data2, 300=data3})

--- Before CollectionWrapper: CollectionWrapper(id=10000, list=[value1, value2, value3, value999, value999], map={100=new data000000001, 200=data2, 300=data3})
--- After  CollectionWrapper: CollectionWrapper(id=10000, list=[value1, value2, value3, value999, value999, memo0001], map={100=new data000000001, 200=data2, 300=data3})

--- Before CollectionWrapper: CollectionWrapper(id=10000, list=[value1, value2, value3, value999, value999, memo0001], map={100=new data000000001, 200=data2, 300=data3})
--- After  CollectionWrapper: CollectionWrapper(id=10000, list=[value1, value2, value3, value999, value999, memo0001], map={100=new data000000001, 200=data2})

■実際に生成されているソースコード

実際に生成されているソースコードをdelombokを使って確認してみました。

import java.util.List;
import java.util.Map;

public class CollectionWrapper {
    private long id;
    private List<String> list;
    private Map<Long, String> map;

    public CollectionWrapper() {
    }

    public long getId() {
        return this.id;
    }

    public List<String> getList() {
        return this.list;
    }

    public Map<Long, String> getMap() {
        return this.map;
    }

    public void setId(final long id) {
        this.id = id;
    }

    public void setList(final List<String> list) {
        this.list = list;
    }

    public void setMap(final Map<Long, String> map) {
        this.map = map;
    }

    @Override
    public boolean equals(final Object o) {
        if (o == this) return true;
        if (!(o instanceof CollectionWrapper)) return false;
        final CollectionWrapper other = (CollectionWrapper) o;
        if (!other.canEqual((Object) this)) return false;
        if (this.getId() != other.getId()) return false;
        final Object this$list = this.getList();
        final Object other$list = other.getList();
        if (this$list == null ? other$list != null : !this$list.equals(other$list)) return false;
        final Object this$map = this.getMap();
        final Object other$map = other.getMap();
        if (this$map == null ? other$map != null : !this$map.equals(other$map)) return false;
        return true;
    }

    protected boolean canEqual(final Object other) {
        return other instanceof CollectionWrapper;
    }

    @Override
    public int hashCode() {
        final int PRIME = 59;
        int result = 1;
        final long $id = this.getId();
        result = result * PRIME + (int) ($id >>> 32 ^ $id);
        final Object $list = this.getList();
        result = result * PRIME + ($list == null ? 43 : $list.hashCode());
        final Object $map = this.getMap();
        result = result * PRIME + ($map == null ? 43 : $map.hashCode());
        return result;
    }

    @Override
    public String toString() {
        return "CollectionWrapper(id=" + this.getId() + ", list=" + this.getList() + ", map=" + this.getMap() + ")";
    }
}


コンストラクタで渡される値は防御的コピーされているか?

getter/setterでは防御的コピーは行われていないことが分かりました。
それでは、finalのメンバが存在する場合に自動的に作成される引数付きコンストラクタでは防御的コピーはされているのかを確認してみます。

■@Dataを付与したクラス

import lombok.Data;

@Data
public class DateWrapper {

    private long id;

    private final java.util.Date date;
}

■利用側のコード

import java.util.Date;

public class DateWrapperClient {

    public static void main(String[] args) {
        Date srcDate = new Date();
        DateWrapper dw = new DateWrapper(srcDate);
        dw.setId(10L);

        System.out.println(dw);

        System.out.println("DateWrapper#toString: " + dw);

        System.out.println("-------");

        // 以下の各操作で防御的コピーは行われないことを確認する

        System.out.println("\n--- Before DateWrapper: " + dw);

        srcDate.setTime(srcDate.getTime() - 100000000L);  // set元のDateの値を変更

        System.out.println("--- After  DateWrapper: " + dw);  // Dateの値が変更されている



        System.out.println("\n--- Before DateWrapper: " + dw);

        srcDate.setTime(dw.getDate().getTime() - 500000000L);  // getterで取得したDateの値を変更

        System.out.println("--- After  DateWrapper: " + dw);  // Dateの値が変更されている
    }
}

■実行結果

DateWrapper(id=10, date=Mon Oct 03 22:45:30 JST 2016)
DateWrapper#toString: DateWrapper(id=10, date=Mon Oct 03 22:45:30 JST 2016)
-------

--- Before DateWrapper: DateWrapper(id=10, date=Mon Oct 03 22:45:30 JST 2016)
--- After  DateWrapper: DateWrapper(id=10, date=Sun Oct 02 18:58:50 JST 2016)

--- Before DateWrapper: DateWrapper(id=10, date=Sun Oct 02 18:58:50 JST 2016)
--- After  DateWrapper: DateWrapper(id=10, date=Tue Sep 27 00:05:30 JST 2016)

結果からわかる通り、コンストラクタで渡した値も防御的コピーは行われていないので、
元の値を変更することで、Beanクラス内の値を変更できています。

■実際に生成されているソースコード

実際に生成されているソースコードをdelombokを使って確認してみました。

public class DateWrapper {
    private long id;
    private final java.util.Date date;

    public DateWrapper(final java.util.Date date) {
        this.date = date;
    }

    public long getId() {
        return this.id;
    }

    public java.util.Date getDate() {
        return this.date;
    }

    public void setId(final long id) {
        this.id = id;
    }

    @Override
    public boolean equals(final Object o) {
        if (o == this) return true;
        if (!(o instanceof DateWrapper)) return false;
        final DateWrapper other = (DateWrapper) o;
        if (!other.canEqual((Object) this)) return false;
        if (this.getId() != other.getId()) return false;
        final Object this$date = this.getDate();
        final Object other$date = other.getDate();
        if (this$date == null ? other$date != null : !this$date.equals(other$date)) return false;
        return true;
    }

    protected boolean canEqual(final Object other) {
        return other instanceof DateWrapper;
    }

    @Override
    public int hashCode() {
        final int PRIME = 59;
        int result = 1;
        final long $id = this.getId();
        result = result * PRIME + (int) ($id >>> 32 ^ $id);
        final Object $date = this.getDate();
        result = result * PRIME + ($date == null ? 43 : $date.hashCode());
        return result;
    }

    @Override
    public String toString() {
        return "DateWrapper(id=" + this.getId() + ", date=" + this.getDate() + ")";
    }
}


@Data + Genericsが絡んだ独自クラス

防御的コピーはあまり関係ないですが、Genericsを利用した独自クラスを定義して、そのクラスをメンバに持つクラスに@Dataを付与してみました。

■@Dataを付与したクラス

import java.util.HashMap;
import java.util.Map;

import lombok.Data;

@Data
public class SampleObject1<K, V> {

    private long id;

    private SampleCache<K, V> cache;

    public static final class SampleCache<K, V> {
        private final Map<K, V> innerMap = new HashMap<>();

        public void register(K key, V value) {
            this.innerMap.put(key, value);
        }

        public V get(K key) {
            return this.innerMap.get(key);
        }
    }
}

■利用側のコード

public class SampleObject1Client {

    public static void main(String[] args) {
        SampleCache<String, Integer> cache = new SampleCache<>();
        cache.register("key1", 1);
        cache.register("key2", 2);
        cache.register("key3", 100);
        cache.register("key5", 999);

        SampleObject1<String, Integer> obj = new SampleObject1<>();
        obj.setId(10000L);
        obj.setCache(cache);

        System.out.println("SampleObject1#getCache: " + obj.getCache());

        System.out.println("SampleObject1#toString: " + obj);
    }
}

■実行結果

SampleObject1#getCache: SampleObject1.SampleCache(innerMap={key1=1, key2=2, key5=999, key3=100})
SampleObject1#toString: SampleObject1(id=10000, cache=SampleObject1.SampleCache(innerMap={key1=1, key2=2, key5=999, key3=100}))

■実際に生成されているソースコード

実際に生成されているソースコードをdelombokを使って確認してみました。

import java.util.HashMap;
import java.util.Map;

public class SampleObject1<K, V> {
    private long id;
    private SampleCache<K, V> cache;


    public static final class SampleCache<K, V> {
        private final Map<K, V> innerMap = new HashMap<>();

        public void register(K key, V value) {
            this.innerMap.put(key, value);
        }

        public V get(K key) {
            return this.innerMap.get(key);
        }
    }

    public SampleObject1() {
    }

    public long getId() {
        return this.id;
    }

    public SampleCache<K, V> getCache() {
        return this.cache;
    }

    public void setId(final long id) {
        this.id = id;
    }

    public void setCache(final SampleCache<K, V> cache) {
        this.cache = cache;
    }

    @Override
    public boolean equals(final Object o) {
        if (o == this) return true;
        if (!(o instanceof SampleObject1)) return false;
        final SampleObject1<?, ?> other = (SampleObject1<?, ?>) o;
        if (!other.canEqual((Object) this)) return false;
        if (this.getId() != other.getId()) return false;
        final Object this$cache = this.getCache();
        final Object other$cache = other.getCache();
        if (this$cache == null ? other$cache != null : !this$cache.equals(other$cache)) return false;
        return true;
    }

    protected boolean canEqual(final Object other) {
        return other instanceof SampleObject1;
    }

    @Override
    public int hashCode() {
        final int PRIME = 59;
        int result = 1;
        final long $id = this.getId();
        result = result * PRIME + (int) ($id >>> 32 ^ $id);
        final Object $cache = this.getCache();
        result = result * PRIME + ($cache == null ? 43 : $cache.hashCode());
        return result;
    }

    @Override
    public String toString() {
        return "SampleObject1(id=" + this.getId() + ", cache=" + this.getCache() + ")";
    }
}

Genericsが絡んでも@Dataは正常に動作することが分かりました



関連エントリ