一個月前剛考研復試完,終於也從一個毫不相關的專業轉了碼。現在在畢設期間找機會摸魚學java。由於機試用的C,之前又沒有接觸過任何OOP語言,對Java的學習一度走了不少彎路。曾經寫的沙雕代碼也犯了不少錯誤(現在仍然在每天生產沙雕Java代碼)。比如下面這段:
public class Main { int a = 1; public class Test{ public void test() { System.out.println("Hello World"); } } public static void main(String[] args) { Test T = new Test();
T.test(); } }
編譯器提示No enclosing instance of type Main is accessible. 也就是說沒有Main的實例。查相應的博客,一群人復讀“靜態類方法無法調用動態實例方法,需要把類修飾符改為static”,但詳細的原因卻很少有人去說明,這一度讓我非常難受,問題也沒得到解決,只得暫時擱置。
直到最近正在讀《Java核心技術卷一》,才明白錯誤原因。下面進行相應的解釋:
為了搞清這個問題,要明白兩個方面的知識:
1) public static void main方法
2) 內部類
先解決第一個問題。首先我們要直到,任何的類都可以自定義一個main方法。在程序運行過程中只會調用唯一一個main方法,具體調用哪個就要看我們要加載哪一個類了。這樣我們也可以明白為什么牛客OJ要求把主類名定義為Main,因為OJ僅僅測試Main中的main方法的輸出結果,而其他的類中的main方法是不會被加載的。這種設計對於我們進行不同模塊的單元測試是十分有利的,只需要把測試代碼寫到不同類的main方法中,調試時加載相應類即可。
雖然main方法是屬於類的,但當執行main方法的時候,main方法所屬的類剛開始並沒有被加載到內存中(先不考慮靜態類),必須要在代碼中new一個Main類,才會把Main的內容加載到內存中。
這樣我們就可以明白為什么錯誤示例代碼中編譯器提示沒有加載Main實例了。由於沒有加載Main實例,自然沒有方法生成Test類,更不可能調用Test類中的方法了。因此我們實例化Main類,做以下修改:
public class Main { int a = 1; public class Test{ public void test() { System.out.println("Hello World"); } } public static void main(String[] args) { Main m = new Main(); m.test(); } }
emmmm.......編譯器的報錯更離譜了,啥都報了。這是因為第二個問題:內部類。
下面解決第二個問題。內部類是Java的一種語法規則,也就是說,允許在類中定義類(之前寫這段demo時不知道這個知識點)。很明顯,Test類是在Main類中定義的內部類。而內部類的對象並不是在外部類實例化的時候就完成實例化的,也必須需要在代碼中完成內部類的實例化:
Main M = new Main(); Main.Test T = M.new Test(); //new關鍵字在.后面,小心別看錯了 T.test();
這樣我們完成了Main類的實例化和Main類的內部類Test類的實例化,終於可以調用Test類的test()方法了,修改后可運行的代碼如下:
public class Main { public class Test { public int a = 1; public void test() { System.out.println("Hello World"); } } public static void main(String[] args) { Main M = new Main(); Main.Test T = M.new Test(); T.test(); } }
這樣問題就解決了。但是顯然,內部類並不是我們想要的結果,它看起來太難受了,如何優雅的解決這個問題呢?
第一種方法,將Test類修飾為static,即將Test類變為靜態類,也就是大多數人復讀的方法。在這種情況下,運行main方法時,雖然並沒有實例化Test對象,但是Test類的靜態方法和靜態屬性已經加載到內存中了,自然可以直接使用,不會提示No Enclosing XXX的內容。
public class Main { public static class Test { public int a = 1; public void test() { System.out.println("Hello World"); } } public static void main(String[] args) { Test t = new Test(); t.test(); } }
第二種方法更簡單,將算法的實現方法和操縱的類分開定義,將包含算法的main定義在Main類中,將類定義在另一個class中,這樣我們直接選擇實例化Test類就可以了,相應的修改代碼如下:
public class Main { // 在被加載的類Main中定義算法過程 public static void main(String[] args) { Test T = new Test(); T.test(); } } class Test { //把使用的類拿到外面定義 public int a = 1; public void test() { System.out.println("Hello World"); } }