簡介
mutable(可變)和immutable(不可變)對象是我們在java程式編寫的過程中經常會使用到的。
可變類型對象就是說,對象在建立之後,其内部的資料可能會被修改。是以它的安全性沒有保證。
而不可變類型對象就是說,對象一旦建立之後,其内部的資料就不能夠被修改,我們可以完全相信這個對象。
雖然mutable對象安全性不夠,但是因為其可以被修改,是以會有效的減少對該對象的拷貝。
而immutable對象因為不可改變,是以嘗試對該對象的修改都會導緻對象的拷貝,進而生成新的對象。
我們最常使用的String就是一個immutable對象。
那麼可變性在java的安全編碼中的最佳實踐是怎麼樣的呢? 一起來看看吧。
可變對象和不可變對象
知道了可變對象和不可變對象的不同之處之後,我們看一下怎麼才能判斷這個對象是可變對象還是不可變對象呢?
首先,最簡單的一點就是,不可變對象建立之後就不能夠被修改,是以不可變對象裡面基本上沒有setXXX之類的方法,而可變對象提供了setXXX這些可以修改内部變量狀态的方法。
看一個例子java.util.Date是一個可變對象,而java.time.LocalTime是不可變對象。
看下他們的方法定義有什麼差別呢?

首先是Date,我們可以看到在其中定義了很多setXXX方法。
而在LocalTime中,我們基本上看不到setXXX方法。
同時不可變對象的字段基本上都是final的,防止被二次修改。
第二,不可變對象一般來說是不可繼承的,在java中就是以final關鍵字做限定的:
public class Date
public final class LocalTime
第三,不可變對象一般會隐藏構造函數,而是使用類似工廠模式的方法來建立對象,這樣為執行個體的建立提供了更多的機動性。
建立mutable對象的拷貝
那麼如果我們想使用mutable對象,又不想被别人修改怎麼辦呢?
簡單的辦法就是拷貝一份要使用的對象:
public class CopyOutput {
private final java.util.Date date;
...
public java.util.Date getDate() {
return (java.util.Date)date.clone();
}
}
這裡大家還要注意深拷貝和淺拷貝的問題。
為mutable類建立copy方法
既然要為mutable對象建立拷貝,那麼相應的mutable類也需要提供一個copy方法來協助拷貝。
這裡需要考慮一個深拷貝和淺拷貝的問題。
不要相信equals
我們知道在HashMap中怎麼去查找一個key呢?先去找這個key的hash值,然後去判斷key.equals方法是否相等,考慮下面這種情況:
private final Map<Window,Extra> extras = new HashMap<>();
public void op(Window window) {
Extra extra = extras.get(window);
}
op方法接收一個Window對象,然後将其當成key從HashMap中取出對應的value。
如果,這個時候,我們有一個類A繼承了Window,并且hash值和equals都和另外一個Window對象B相同,那麼使用A這個key可以擷取到B這個key存儲的資料!
怎麼解決這個問題呢?
Java中有一個特别的HashMap:IdentityHashMap,這個Map的key和value比較是用==而不是equals方法,是以可以有效的避免上面出現的問題。
private final Map<Window,Extra> extras = new IdentityHashMap<>();
public void op(Window window) {
Extra extra = extras.get(window);
}
如果沒有這樣的Map可用,那麼可以使用不可變對象作為key或者使用Window的私有變量,進而惡意攻擊者無法獲得這個變量。
public class Window {
/* pp */
class PrivateKey {
Window getWindow() {
return Window.this;
}
}
final PrivateKey privateKey = new PrivateKey();
private final Map<Window.PrivateKey,Extra> extras =
new WeakHashMap<>();
...
}
public class WindowOps {
public void op(Window window) {
// Window.equals may be overridden,
// but safe as we don't use it.
Extra extra = extras.get(window.privateKey);
...
}
}
不要直接暴露可修改的屬性
如果一個可變類中的某個屬性确實需要暴露被外部使用,那麼一定要将這個屬性定義為private,并且使用wrapper方法将其包裝起來。
如果直接暴露出去,那麼基本上就沒有權限控制可言,任何程式隻要能夠拿到你這個對象,就可以對屬性進行修改。考慮下下面的應用方式,我們在修改state的方法中加入了一個參數校驗和權限控制。
public final class WrappedState {
// private immutable object
private String state;
// wrapper method
public String getState() {
return state;
}
// wrapper method
public void setState(final String newState) {
this.state = requireValidation(newState);
}
private static String requireValidation(final String state) {
if (...) {
throw new IllegalArgumentException("...");
}
return state;
}
}
public static fields應該被置位final
同樣的,如果你是一個類變量,當然不希望這個變量會被任何人修改,那麼需要将其置位final。
public class Files {
public static final String separator = "/";
public static final String pathSeparator = ":";
}
public static final field 應該是不可變的
如果類變量是public static final的,那麼這個變量一定要是不可變的。
有人會問了,都定義成了final了,是不是就已經不可變了?
其實不然,比如我們定義了一個final的List,雖然這個list不能變化,但是list裡面的值是可以變化的。我們需要将可變變量修改為不可變變量,如下所示:
import static java.util.Arrays.asList;
import static java.util.Collections.unmodifiableList;
...
public static final List<String> names = unmodifiableList(asList(
"Fred", "Jim", "Sheila"
));
如果使用JDK9中引入的of()或者ofEntries()方法,可以直接建立不可修改的集合:
public static final List
<String> names =
List.of("Fred", "Jim", "Sheila");
本文已收錄于 http://www.flydean.com/java-security-code-line-mutability/最通俗的解讀,最深刻的幹貨,最簡潔的教程,衆多你不知道的小技巧等你來發現!
歡迎關注我的公衆号:「程式那些事」,懂技術,更懂你!