#値オブジェクトがなぜ安全か
- 実践コードを通じて理解を深めましょう🌟
##基本型の場合
- 基本型はgetValueで、とってきた値を上書きしても、中で管理してる本当の値は変更されない
- 図らずとも防御的コピーになってた
- 以下のPrice.javaの中身は実際には基本型
- unitPriceでgetValue()してるが、上書きしてもオブジェクトの中の値は変わらない🙆♀️
Main.java
import java.util.*;
public class Main {
public static void main(String[] args) throws Exception {
Price price = new Price(123);
int value1 = price.getValue();
System.out.println("変更前value1 " +value1); //変更前value1 123
value1 = 234;
int value2= price.getValue();
System.out.println("変更後value1 " +value1); //変更後value1 234
System.out.println("value2 " +value2); //value2 123
}
}
Price.java
public class Price {
private int value;
public Price(int value){
this.value = value;
}
public int getValue() {
return value;
}
}
##リストの場合
###Shallow Copy / 破壊的コピー
- list1初期化(全体コードは下記参考)
List <Integer> list1 = intList.getList();
System.out.println("変更前list1 " +list1); //変更前list1 [1, 2, 3]
- list1をコピーしてlist2を作成
List<Integer> list2 = list1;
- list1に5を追加
list1.add(5);
- 返ってきた値は。。。list1もlist2も
[1, 2, 3, 5]
System.out.println("変更後list1 " +list1); //変更後list1 [1, 2, 3, 5]
System.out.println("list2(Shallow Copy) " +list2); //list2(Shallow Copy) [1, 2, 3, 5]
- list1への変更がlist2にも影響!→両方メモリアドレスが同じだから!
- 参照型はアドレス自体をわたす
- そのアドレスに要素を追加するのは、コピー元に要素を追加するに等しい
-
Shallow Copy / 破壊的コピーという
- これでは
list1.clear();
してしまうと全部消えてしまう。😱 - →Deep Copy / 防御コピーにしよう
- これでは
###Deep Copy / 破壊的コピー
List <Integer> list3 = intList.getList();
- 5を追加してもlist1のみが変更されlist3には影響しない
- 自分自身がもつオブジェクトが外部から影響されない
- →**不変的なオブジェクト(Immutable)**という
IntList.java
import java.util.ArrayList;
import java.util.List;
public class IntList {
private List<Integer> list;
public IntList(List<Integer> list) {
this.list =list;
}
public List<Integer> getList() {
return new ArrayList<Integer>(this.list);
}
}
Main.java
import java.util.*;
public class Main {
public static void main(String[] args) throws Exception {
List<Integer> sourceList = new ArrayList<>();
sourceList.add(1);
sourceList.add(2);
sourceList.add(3);
IntList intList = new IntList (sourceList);
List <Integer> list1 = intList.getList();
System.out.println("変更前list1 " +list1); //変更前list1 [1, 2, 3]
List<Integer> list2 = list1;
List <Integer> list3 = intList.getList();
list1.add(5);
System.out.println("変更後list1 " +list1); //変更後list1 [1, 2, 3, 5]
System.out.println("list2(Shallow Copy) " +list2); //list2(Shallow Copy) [1, 2, 3, 5]
System.out.println("list3(Deep Copy) " +list3); //list3(Deep Copy) [1, 2, 3]
}
}
#変更に強いコード実践
- よし、じゃあ変更に強いコードが書きたい!
- ということでImmutableなオブジェクトサンプル実践編
-
Valueオブジェクトを使ってタイポがない、凡ミスがないコードにしようo(^^)o
- 建物の緯度経度を表すBuildeingクラスを作ります
Project Root
└─src
└─ main
└─ java
└─ Main
└─ values
└─ Building
└─ Building2
└─ BuildingName
└─ Longitude
└─ Latitude
└─ Main
Latitude.java
package values;
public class Latitude {
private float value;
public Latitude(float value){
this.value = value;
}
public float getValue() {
return value;
}
}
- LatitudeクラスをコピーしてLongitudeクラス作成
- IntelliJではクラスをコピーするといい感じに書き換えてくれる
Longitude.java
package values;
public class Longitude {
private float value;
public Longitude(float value){
this.value = value;
}
public float getValue() {
return value;
}
}
Building.java
package values;
public class Building {
private String name;
private Latitude latitude;
private Longitude longitude;
public Building(String name, Latitude latitude,Longitude longitude){
this.name = name;
this.latitude = latitude;
this.longitude = longitude;
}
}
- Buildeingクラス内に"mitaka"と"totoro"の2オブジェクト作成
- "mitaka"というvalue、"totoro"というvalueに、さらに緯度経度の値オブジェクトが入っている
Main.java
package values;
public class Main {
public static void main(String[] args) {
Building mitaka = new Building(
"三鷹の森",
//fつけないとリテラルとして認識されない
new Latitude(35.6962303f),
new Longitude(139.5704895f)
);
Building totoro = new Building(
"トトロの森",
new Latitude(35.7818004f),
new Longitude(139.4219994f)
);
System.out.println("pause");
}
}
##値オブジェクトを使わない場合
- もしBuilding2クラスを作成し、float型にしたらどうなるでしょうか。。
Building2.java
public class Building2 {
private String name;
private float latitude;
private float longitude;
//中略
}
- 商用化では間違いがあるのが前提なので、例えば緯度経度引数の順番をまちがえてしまうこともあると思います
Main.java
Building2 totoro2 = new Building2(
"トトロの森",
139.4219994f, //経度
35.7818004f //緯度
);
- コンストラクタでfloat、floatで受けとるので、コンパイルは通ってしまう!!( ; _ ; )
- 上の例では**経度緯度を入れ替えてしまってもコンパイルが通らない!**ミスを未然に防ぐのです
Main.java
//コンパイルエラー
Building totoro = new Building(
new BuildingName ("トトロの森"),
new Longitude(139.4219994f),
new Latitude(35.7818004f)
);
- オブジェクト指向を実践してちゃんとクラスとして定義したら
- ✨タイポがない、凡ミスがない✨
- ✨**longitudeで問題があればそのクラスのみ修正することができる!**✨
Building2.java
//値オブジェクトを使わないBuilding2クラス
public class Building2 {
private String name;
private float latitude;
private float longitude;
public Building2(String name, float latitude, float longitude){
this.name = name;
this.latitude = latitude;
this.longitude = longitude;
}
}
Main.java
//Latitude,Longitudeが逆なのにコンパイルが通ってしまうコード
package values;
public class Main {
public static void main(String[] args) {
Building totoro = new Building(
"トトロの森",
new Latitude(35.7818004f), //緯度
new Longitude(139.4219994f) //経度
);
Building2 totoro2 = new Building2(
"トトロの森",
139.4219994f, //経度
35.7818004f //緯度
);
System.out.println("pause");
}
}
#論理モデル設計技量は大事ですね
- システムのニーズによってはさらにBuildingNameクラスで
-
new BuildingName("totoro")
にことも可能 - 論理モデル設計技量をつけて、いけてるエンジニアになりたいです。。!
BuildingName.java
package values;
public class BuildingName {
private String value;
public BuildingName(String value){
this.value = value;
}
public String getValue() {
return value;
}
}