java能不能自己寫一個類叫java.lang.System/String正確答案,java.lang.string
最近學習了下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類,輸出如下:
java.lang.NoSuchMethodError: main
Exception in thread "main"
提示Math類沒有main方法。首先大家要明白一個概念,當類首次主動使用時,必須進行類的載入,這部分工作是由類載入器來完成的。根據雙親委託原則,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方法代碼,摘抄如下:
private ProtectionDomain preDefineClass(String name, ProtectionDomain protectionDomain) {if (!checkName(name)) throw new NoClassDefFoundError("IllegalName: " + name);if ((name != null) && name.startsWith("java.")) { throw new SecurityException("Prohibited package name: " +name.substring(0, name.lastIndexOf('.')));}if (protectionDomain == null) { protectionDomain = getDefaultDomain();}if (name != null) checkCerts(name, protectionDomain.getCodeSource());return protectionDomain; }
可以看到如果載入的類全名稱以“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()方法去載入一個以"java."開頭的類也是會拋出異常的。
總結
不能自己寫以"java."開頭的類,其要麼不能載入進記憶體,要麼即使你用自訂的類載入器去強行載入,也會收到一個SecurityException。
碼字不易,源頭來自http://blog.csdn.net/tang9140。
那些抓取網上內容,放在自家網站上的請自覺。百度搜尋你為啥老把原創網頁排後面呢,請反思,不要只想著賺錢。