java 委托_java 能不能自己写一个类叫 java.lang.System/String 正确答案
來自:一汪清水?|?責編:樂樂
鏈接:blog.csdn.net/tang9140/article/details/42738433
? ?正文? ?
最近學習了下java類加載相關的知識。然后看到網上有一道面試題是
能不能自己寫個類叫java.lang.System?
網上提供的答案:通常不可以,但可以采取另類方法達到這個需求。所謂的另類方法指自己寫個類加載器來加載java.lang.System達到目的。
首先表明下我的觀點。上述答案完全是誤導讀者,是不正確的答案。我就納悶了網上怎么把這種完全不正確的搜索結果排在前面,而且幾乎搜到的都是這種不正確的答案??赡芎芏嗖幻髡嫦嗟呐笥丫瓦@么被誤導了,所以還是希望大家對網上的內容先持懷疑態度為好。下面詳細說明為什么。
首先,摘抄網上錯誤答案的詳細解釋
“為了不讓我們寫System類,類加載采用委托機制,這樣可以保證爸爸們優先,爸爸們能找到的類,兒子就沒有機會加載。而System類是Bootstrap加載器加載的,就算自己重寫,也總是使用Java系統提供的System,自己寫的System類根本沒有機會得到加載。
但是,我們可以自己定義一個類加載器來達到這個目的,為了避免雙親委托機制,這個類加載器也必須是特殊的。由于系統自帶的三個類加載器都加載特定目錄下的類,如果我們自己的類加載器放在一個特殊的目錄,那么系統的加載器就無法加載,也就是最終還是由我們自己的加載器加載。”
然后,說明下上面解釋中提到的一些概念
類加載器可分為兩類:一是啟動類加載器(Bootstrap ClassLoader),是C++實現的,是JVM的一部分;另一種是其它的類加載器,是Java實現的,獨立于JVM,全部都繼承自抽象類java.lang.ClassLoader。jdk自帶了三種類加載器,分別是啟動類加載器(Bootstrap ClassLoader),擴展類加載器(Extension ClassLoader),應用程序類加載器(Application ClassLoader)。后兩種加載器是繼承自抽象類java.lang.ClassLoader。關于這三種加載器各自的作用這里不做詳細說明,有興趣的可以自己了解下。
類加載器是有層次的
一般是:自定義類加載器? >> 應用程序類加載器? >> 擴展類加載器? >> 啟動類加載器
上面的層次關系被稱為雙親委派模型(Parents Delegation Model)。除了最頂層的啟動類加載器外,其余的類加載器都有對應的父類加載器。
再簡單說下雙親委托機制:如果一個類加載器收到了類加載的請求,它首先不會自己嘗試去加載這個類,而是把這個請求委派給父類加載器,每一個層次的類加載器都是加此,因此所有的加載請求最終到達頂層的啟動類加載器,只有當父類加載器反饋自己無法完成加載請求時(指它的搜索范圍沒有找到所需的類),子類加載器才會嘗試自己去加載。
再回去看下解釋內容,我相信前面的部分大家應該很看懂了,也沒什么大問題。最后的如果部分“如果我們自己的類加載器放在一個特殊的目錄,那么系統的加載器就無法加載,也就是最終還是由我們自己的加載器加載。” 我就不明白所以了,邏輯完全不通。我想它的本意可能是,將自己的java.lang.System類放置在特殊目錄,然后系統自帶的加載器無法加載,這樣最終還是由我們自己的加載器加載(因為我們自己的加載器知道其所在的特殊目錄)。這種說法好像邏輯上沒有問題,那么我們就來實驗下了。
代碼驗證
測試類結構及內容如下:
public class MyClassLoader extends ClassLoader{public MyClassLoader() {
super(null);
}
@Override
public Class> loadClass(String name) throws ClassNotFoundException {
try{
String className = null;
if(name.startsWith("java.lang")){
className = "/" + name.replace('.', '/') + ".class";
}else{
className = name.substring(name.lastIndexOf('.') + 1) + ".class";
}
System.out.println(className);
InputStream is = getClass().getResourceAsStream(className);
System.out.println(is);
if(is == null)
return super.loadClass(name);
byte[] b = new byte[is.available()];
is.read(b);
return defineClass(name, b, 0, b.length);
}catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
}public class ClassLoaderTest {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
ClassLoader myLoader = new MyClassLoader();
Object obj = myLoader.loadClass("java.lang.Math").newInstance();
System.out.println(obj);
}
}public final class Math {
public static void main(String[] args) {
System.out.println("hello world");
}
}public class MyMath {
public static void main(String[] args) {
System.out.println("hello world");
}
}
上面的測試代碼沒用自定義java.lang.System類,因為測試代碼用到了JDK自帶的System類進行輸出打印,會沖突,所以改用為自定義的java.lang.Math類。如果自定義的Math類能加載,那么自定義的System類同樣能加載。
我們先直接運行下Math類,輸出如下:
提示Math類沒有main方法。首先大家要明白一個概念,當類首次主動使用時,必須進行類的加載,這部分工作是由類加載器來完成的。根據雙親委托原則,Math類首先由啟動類加載器去嘗試加載,很顯然,它找到rt.jar中的java.lang.Math類并加載進內存(并不會加載我們自定義的Math類),然后執行main方法時,發現不存在該方法,所以報方法不存在錯誤。也就是說,默認情況下JVM不會加載我們自定義的Math類。
再直接運行MyMath類,輸出如下:
注意紅色部分的內容。由堆棧異常信息可知道,當應用程序類加載器類(AppClassLoader)嘗試加載MyMath類時,ClassLoader.java的479行拋出了SecurityException
禁止使用包名:java.lang。
直接查看抽象類java.lang.ClassLoader的preDefineClass方法代碼,摘抄如下:
可以看到如果加載的類全名稱以“java.”開頭時,將會拋出SecurityException,這也是為什么直接執行MyMath類會出現SecurityException。
照這樣,我們自定義的類加載器必須繼承自ClassLoader,其loadClass()方法里調用了父類的defineClass()方法,并最終調到preDefineClass()方法,因此我們自定義的類加載器也是不能加載以“java.”開頭的java類的。我們繼續運行下ClassLoaderTest類,輸出如下:
紅色部分清楚表明,也是在preDefineClass方法中拋出的SecurityException。
通過代碼實例及源碼分析可以看到,對于自定義的類加載器,強行用defineClass()方法去加載一個以"java."開頭的類也是會拋出異常的。
總結
不能自己寫以"java."開頭的類,其要么不能加載進內存,要么即使你用自定義的類加載器去強行加載,也會收到一個SecurityException。
總結
以上是生活随笔為你收集整理的java 委托_java 能不能自己写一个类叫 java.lang.System/String 正确答案的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python enumerate函数_P
- 下一篇: android ui自动化框架选型,Ap