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は正常に動作することが分かりました