前言:最近在學多線程,寫“哲學家就餐問題(Dining Philosophers)”的時候,需要定義一個全局的變量,即哲學家的人數。常用的做法是在其中一個類中定義一個static final的變量,然后讓其他類通過類名訪問他。在這里,想使用之前實訓項目的第一版應用層協議的設計想法,即使用一個接口類來定義所有子類都會使用到的變量。然后,就引出了一個interface成員變量和static final的問題。
(一)一個簡單的問題
首先,看一段代碼:
1 //Variable.java 2 public interface Variable { 3 public int NUM_PHILOSOPHERS = 5; 4 } 5 6 //DiningPhilosophers.java 7 public class DiningPhilosophers implements Variable{ 8 public static void main(String[] args) { 9 Lock[] chopsticks = new ReentrantLock[NUM_PHILOSOPHERS]; 10 System.out.println(chopsticks.length); 11 } 12 }
上述代碼中,DiningPhilosophers類的static方法直接使用接口中的public int變量,是否會出錯?
(二)解析
可能會有人第一反應是不可以,因為靜態方法不能直接使用類的非靜態成員變量。我的一部分朋友也是這么想的(好吧,不排除我的誤導)。
實際上,上面的程序並沒有問題。
(1)首先,靜態方法的確不能直接使用類的非靜態成員變量。我曾經寫過一篇類似的博文《再學Java 之 解決No enclosing of type * is accessable》大家可以參考一下。
(2)其次,所有的interface成員變量都必須是public static final 的(原因后面會解釋),所以我們在寫代碼的時候可以省略一部分修飾符,所以上面的NUM_PHILOSOPHERS就算聲明語句為 int NUM_PHILOSOPHERS=5;它依然是一個public static final變量(所以,這也解釋了,如果我們不對其賦初始值,為什么會報錯)。我們可以使用javap工具查看Variable類的編譯信息:
(3)最后解釋一下為什么interface的成員變量必須是public static final的。
我在Google上搜到了一篇比較不錯的文章《Why do we have only public static final variables in interfaces?》,下面大概翻譯一下(有改動):
接口定義了行為的協議,而不是行為如何執行實現。實現接口的類支持該接口中定義的行為協議。
接口中聲明的所有字段都是public static final的。為什么?
- 如果一個變量沒有被定義為final,任何類的實現都可以改變變量的值。同時他就會變成類的實現的一部分,而接口是一個不帶任何實現的純粹的規范;
- 如果變量是靜態的,那么這個變量就是屬於接口的,而不是屬於實例對象或者運行時的對象的。(注:由於接口不能被實例化,所以,定義為非靜態,其實也沒有意義。);
- 接口定義了調用者如何跟接口的實現類的實例對象交互,所以如果成員不是public,那么調用者沒辦法去方法它;
接口的每一個字段的聲明都必須是pubic static final 的,它允許我們在聲明時指定所有或者部分修飾符。同時,任何字段的聲明必須有一個初始化表達式,它可以不用一定是一個常量表達式,它的計算和賦值只會進行一次,然后這個接口字段就被初始化了(注:這是一個很有趣的特性,有興趣可以一起聊聊:-))。