標籤:java解惑
某些時候,對於一個類來說,跟蹤其建立出來的執行個體個數會非常用有,其典型實現是通過讓它的構造器遞增一個私人靜態域來完成的。在下面的程式中,Creature類展示了這種技巧,而Creator類對其進行了操練,將列印出已經建立的Creature執行個體的數量。那麼,這個程式會列印出什麼呢?
<span style="font-size:18px;">public class Creator { public static void main(String[] args) { for (int i = 0; i < 100; i++) Creature creature = new Creature(); System.out.println(Creature.numCreated()); }}class Creature { private static long numCreated = 0; public Creature() { numCreated++; } public static long numCreated() { return numCreated; }}</span>
這是一個捉弄人的問題。該程式看起來似乎應該列印100,但是它沒有列印任何東西,因為它根本就不能編譯。如果你嘗試著去編譯它,你就會發現編譯器的診斷資訊基本沒什麼用處。下面就是javac列印的東西:
<span style="font-size:18px;">Creator.java:4: not a statement Creature creature = new Creature(); ^Creator.java:4: ';' expected Creature creature = new Creature(); ^</span>
一個本地變數聲明看起來像是一條語句,但是從技術上說,它不是;它應該是一個本地變數聲明語句(local variable declaration statement)[JLS 14.4]。
Java語言規範不允許一個本地變數聲明語句作為一條語句在for、while或do迴圈中重複執行[JLS 14.12-14]。一個本地變數聲明作為一條語句只能直接出現在一個語句塊中。(一個語句塊是由一對花括弧以及包含在這對花括展中的語句和聲明構成的。)
有兩種方式可以訂正這個問題。最顯而易見的方式是將這個聲明至於一個語句塊中:
<span style="font-size:18px;">for (int i = 0; i < 100; i++) { Creature creature = new Creature();}</span>
然而,請注意,該程式沒有使用本地變數creature。因此,將該聲明用一個無任何修飾的構造器調用來替代將更具實際意義,這樣可以強調對新建立對象的引用正在被丟棄:
for (int i = 0; i < 100; i++)
new Creature();
無論我們做出了上面的哪種修改,該程式都將列印出我們所期望的100。
請注意,用於跟蹤Creature執行個體個數的變數(numCreated)是long類型而不是int類型的。我們很容易想象到,一個程式建立出的某個類的執行個體可能會多餘int數值的最大值,但是它不會多於long數值的最大值。
int數值的最大值是231-1,即大約2.1×109,而long數值的最大值是263-1,即大約9.2×1018。當前,每秒鐘建立108個對象是可能的,這意味著一個程式在long類型的對象計數器溢出之前,不得不運行大約三千年。即使是面對硬體速度的提升,long類型的對象計數器也應該足以應付可預見的未來。
還要注意的是,本謎題中的建立計數策略並不是安全執行緒的。如果多個線程可以並行地建立對象,那麼遞增計數器的代碼和讀取計數器的代碼都應該被同步:
<span style="font-size:18px;">// Thread-safe creation counterclass Creature { private static long numCreated; public Creature() { synchronized (Creature.class) { numCreated++; } } public static synchronized long numCreated() { return numCreated; }}</span>
或者,如果你使用的是5.0或更新的版本,你可以使用一個AtomicLong執行個體,它在面臨並發時可以繞過對同步的需求。
<span style="font-size:18px;">// Thread-safe creation counter using AtomicLong;import java.util.concurrent.atomic.AtomicLong;class Creature { private static AtomicLong numCreated = new AtomicLong(); public Creature() { numCreated.incrementAndGet(); } public static long numCreated() { return numCreated.get(); }}</span>
請注意,把numCreated聲明為瞬時的是不足以解決問題的,因為volatile修飾符可以保證其他線程將看到最近賦予該域的值,但是它不能進行原子性的遞增操作。
總之,一個本地變數聲明不能被用作for、while或do迴圈中的重複執行語句,它作為一條語句只能出現在一個語句塊中。另外,在使用一個變數來對執行個體的建立進行計數時,要使用long類型而不是int類型的變數,以防止溢出。最後,如果你打算在多線程中建立執行個體,要麼將對執行個體計數器的訪問進行同步,要麼使用一個AtomicLong類型的計數器。
Java解惑 55 -- 特創論