原文: http://www.wfuyu.com/php/22254.html
未做測試 !
最近學習了下java類加載相干的知識。然后看到網上有1道面試題是
能不能自己寫個類叫java.lang.System?
網上提供的答案:通常不可以,但可以采取另類方法到達這個需求。所謂的另類方法指自己寫個類加載器來加載java.lang.System到達目的。
首先表明下我的觀點。上述答案完全是誤導讀者,是不正確的答案。我就疑惑了網上怎樣把這類完全不正確的搜索結果排在前面,而且幾近搜到的都是這類不正確的答案。可能很多不明真相的朋友就這么被誤導了,所以還是希望大家對網上的內容先持懷疑態度為好。下面詳細說明為何。
首先,摘抄網上毛病答案的詳細解釋
“為了不讓我們寫System類,類加載采取拜托機制,這樣可以保證爸爸們優先,爸爸們能找到的類,兒子就沒有機會加載。而System類是Bootstrap加載器加載的,就算自己重寫,也總是使用Java系統提供的System,自己寫的System類根本沒有機會得到加載。
但是,我們可以自己定義1個類加載器來到達這個目的,為了不雙親拜托機制,這個類加載器也必須是特殊的。由於系統自帶的3個類加載器都加載特定目錄下的類,如果我們自己的類加載器放在1個特殊的目錄,那末系統的加載器就沒法加載,也就是終究還是由我們自己的加載器加載。”
然后,說明下上面解釋中提到的1些概念
類加載器可分為兩類:1是啟動類加載器(Bootstrap ClassLoader),是C++實現的,是JVM的1部份;另外一種是其它的類加載器,是Java實現的,獨立於JVM,全部都繼承自抽象類java.lang.ClassLoader。jdk自帶了3種類加載器,分別是啟動類加載器(Bootstrap ClassLoader),擴大類加載器(Extension ClassLoader),利用程序類加載器(Application ClassLoader)。后兩種加載器是繼承自抽象類java.lang.ClassLoader。關於這3種加載器各自的作用這里不做詳細說明,有興趣的可以自己了解下。
類加載器是有層次的
1般是: 自定義類加載器 >> 利用程序類加載器 >> 擴大類加載器 >> 啟動類加載器
上面的層次關系被稱為雙親委派模型(Parents Delegation Model)。除最頂層的啟動類加載器外,其余的類加載器都有對應的父類加載器。
再簡單說下雙親拜托機制:如果1個類加載器收到了類加載的要求,它首先不會自己嘗試去加載這個類,而是把這個要求委派給父類加載器,每個層次的類加載器都是加此,因此所有的加載要求終究到達頂層的啟動類加載器,只有當父類加載器反饋自己沒法完成加載要求時(指它的搜索范圍沒有找到所需的類),子類加載器才會嘗試自己去加載。
再回去看下解釋內容,我相信前面的部份大家應當很看懂了,也沒甚么大問題。最后的如果部份“如果我們自己的類加載器放在1個特殊的目錄,那末系統的加載器就沒法加載,也就是終究還是由我們自己的加載器加載。” 我就不明白所以了,邏輯完全不通。我想它的本意多是,將自己的java.lang.System類放置在特殊目錄,然后系統自帶的加載器沒法加載,這樣終究還是由我們自己的加載器加載(由於我們自己的加載器知道其所在的特殊目錄)。這類說法好像邏輯上沒有問題,那末我們就來實驗下了。
代碼驗證
測試類結構及內容以下:
上面的測試代碼沒用自定義java.lang.System類,由於測試代碼用到了JDK自帶的System類進行輸出打印,會沖突,所以改用為自定義的java.lang.Math類。如果自定義的Math類能加載,那末自定義的System類一樣能加載。
我們先直接運行下Math類,輸出以下:
java.lang.NoSuchMethodError: main
Exception in thread "main"
提示Math類沒有main方法。首先大家要明白1個概念,當類首次主動使用時,必須進行類的加載,這部份工作是由類加載器來完成的。根據雙親拜托原則,Math類首先由啟動類加載器去嘗試加載,很明顯,它找到rt.jar中的java.lang.Math類並加載進內存(其實不會加載我們自定義的Math類),然后履行main方法時,發現不存在該方法,所以報方法不存在毛病。也就是說,默許情況下JVM不會加載我們自定義的Math類。
再直接運行MyMath類,輸出以下:
java.lang.SecurityException: Prohibited package name: java.lang
at java.lang.ClassLoader.preDefineClass(ClassLoader.java:479)
at java.lang.ClassLoader.defineClassCond(ClassLoader.java:625)
at java.lang.ClassLoader.defineClass(ClassLoader.java:615)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:141)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:283)
at java.net.URLClassLoader.access$000(URLClassLoader.java:58)
at java.net.URLClassLoader$1.run(URLClassLoader.java:197)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
Exception in thread "main"
注意紅色部份的內容。由堆棧異常信息可知道,當利用程序類加載器類(AppClassLoader)嘗試加載MyMath類時,ClassLoader.java的479行拋出了SecurityException
制止使用包名:java.lang。
直接查看抽象類java.lang.ClassLoader的preDefineClass方法代碼,摘抄以下:
可以看到如果加載的類全名稱以“java.”開頭時,將會拋出SecurityException,這也是為何直接履行MyMath類會出現SecurityException。
照這樣,我們自定義的類加載器必須繼承自ClassLoader,其loadClass()方法里調用了父類的defineClass()方法,並終究調到preDefineClass()方法,因此我們自定義的類加載器也是不能加載以“java.”開頭的java類的。我們繼續運行下ClassLoaderTest類,輸出以下:
/java/lang/Math.class
sun.net.www.protocol.jar.JarURLConnection$JarURLInputStream@a981ca
java.lang.SecurityException: Prohibited package name: java.lang
at java.lang.ClassLoader.preDefineClass(ClassLoader.java:479)
at java.lang.ClassLoader.defineClassCond(ClassLoader.java:625)
at java.lang.ClassLoader.defineClass(ClassLoader.java:615)
at java.lang.ClassLoader.defineClass(ClassLoader.java:465)
at com.tq.MyClassLoader.loadClass(MyClassLoader.java:28)
at com.tq.ClassLoaderTest.main(ClassLoaderTest.java:8)
Exception in thread "main" java.lang.ClassNotFoundException
at com.tq.MyClassLoader.loadClass(MyClassLoader.java:31)
at com.tq.ClassLoaderTest.main(ClassLoaderTest.java:8)
紅色部份清楚表明,也是在preDefineClass方法中拋出的SecurityException。
通過代碼實例及源碼分析可以看到,對自定義的類加載器,強行用defineClass()方法去加載1個以"java."開頭的類也是會拋出異常的。
總結
不能自己寫以"java."開頭的類,其要末不能加載進內存,要末即便你用自定義的類加載器去強行加載,也會收到1個SecurityException。
碼字不容易,源頭來自http://blog.csdn.net/tang9140。
那些抓取網上內容,放在自家網站上的請自覺。百度搜索你為啥老把原創網頁排后面呢,請反思,不要只想着賺錢。