弱點掃描遇到這個問題
因此來研究一下
話不多說先看程式碼
範例
import java.util.Arrays;
public class Demo {
private int[] data;
public int[] getData() {
return data;
}
public void setData(int[] data) {
this.data = data;
}
public void doubleData() {
for (int i = 0; i < data.length; i++) {
data[i] *= 2;
}
}
public void printData() {
System.out.println(Arrays.toString(this.data));
}
public static void main(String[] args) {
int[] publicData = {1, 2, 3};
Demo demo = new Demo();
demo.setData(publicData);
demo.printData(); // [1, 2, 3]
demo.doubleData();
demo.printData(); // [2, 4, 6]
publicData[0] = 100;
demo.printData(); // [100, 4, 6]
}
}
問題描述
這個範例是類別中有一個整數陣列
可以看到修改外部陣列會影響類別內部的陣列
原因是它們指向同樣的記憶體地址
換言之它們是同一份資料
這違反了物件導向的「封裝」原則
因為 private 屬性已經對外暴露出來
程式可以繞過 Demo 物件直接存取資料
而且這帶來了不可預測性
例如這個範例是讓數值變為兩倍
但後續結果卻不同
因為程式邏輯被破壞了
如果專案中許多地方都是這種寫法
可能一份資料同時被多個地方修改
造成問題難以追蹤
修改方式
既然問題的核心在於內外共用一份資料
解決方式就是複製一份
讓物件擁有屬於自己的資料,就不會交互干擾了
同樣的邏輯適用於 Array、List、Set、Map…
public void setArr(int[] arr) {
if (arr == null) {
this.arr = null;
} else {
this.arr = new int[arr.length];
System.arraycopy(arr, 0, this.arr, 0, arr.length);
}
}
public void setList(List<Integer> list) {
if (list == null)
this.list = null;
else
this.list = new ArrayList<>(list);
}
public void setSet(Set<Integer> set) {
if (set == null)
this.set = null;
else
this.set = new HashSet<>(set);
}
public void setMap(Map<String, Integer> map) {
if (map == null)
this.map = null;
else
this.map = new HashMap<>(map);
}
淺複製與深複製
在 Java 基礎觀念中
複製物件參考本質算不上複製
因為記憶體位置是相同的
Person p1 = new Person("John");
Person p2 = p1;
System.out.println(p1 == p2); // true
要複製的話應當使用 Object.clone()
import java.util.*;
public class Data implements Cloneable {
private int[] arr;
public int[] getArr() {
return this.arr;
}
public void setArr(int[] arr) {
this.arr = arr;
}
public static void main(String[] args) throws Exception {
Data d1 = new Data();
d1.setArr(new int[] {1, 2, 3});
Data d2 = (Data) d1.clone();
System.out.println(d1 == d2); // false
int[] arr = d1.getArr();
arr[0] = 100;
System.out.println(Arrays.toString(d2.getArr())); // [100, 2, 3]
System.out.println(d1.getArr() == d2.getArr()); // true
}
}
但是這個例子顯示明明改的是 Data 1 的屬性
Data 2 的屬性也受牽連
這就叫做「淺複製」Shallow Copy
只有軀殼不同,靈魂卻相同
沒有把內部屬性一一複製出來
因此真的要複製就要「深複製」Deep Copy
這只能重寫 clone 方法
而且若物件有多層嵌套就很費工
例如 Map<String, Integer[]>
if (map == null) {
this.map = null;
reutrn;
}
this.map = new HashMap<>();
for (Map.Entry<String, Integer[]> entry : map.entrySet()) {
Integer[] original = entry.getValue();
Integer[] copy = original == null ? null : Arrays.copyOf(original, original.length);
this.map.put(entry.getKey(), copy);
}
影響
這在軟體工程中有術語叫「防禦性編程」
也就是預先防範可能的錯誤
就算發生了,程式也不受影響
但凡事都有代價
首先是記憶體開銷
再來是會影響效能
因此到底要做到什麼地步?
零信任嗎?那每個地方都得這麼做
增加許多開發成本
甚至讓 Lombok 的功能大打折扣
安全性與便利性不可兼得