故事背景
小白是個程序猿,剛畢業兩年,最近交了一個女朋友,是同事介紹的。女朋友和閨蜜住在一起。小白早上很早接到女朋友電話,昨天她的一個文件錯放到了他的電腦包,希望他幫忙送到她住的地方,她今天要向她boss匯報的。
救急如救火,為了好好表現自己,小白趕緊打了個車到女朋友的小區,然后在小區門口等她。早上7點,人流如織,等了許久,沒有見到,遂電話之,被女友的閨蜜告知,女友未化妝素顏下來找他,未帶電話,請他好好辨別。

小白知道有一句話特別流行,說現在女生有着三分的長相,畫着五分的妝容,看着美顏中七分的自己。小伙伴們都說了女生化妝到底有多厲害?

這個怎么辦呢?小白靈機一動,敵不動我不動,敵一動,我以靜制動。遂帶上眼鏡,舉着文件,站在一個顯眼的位置,自己觀察走來的女生,天見可憐,最終將文件交到一個看着有點熟悉又有點陌生的女孩手里,完成任務。
java反射的故事
工作中,小白也碰到同樣的一個問題,他希望下面的程序打印true,
public static void main(String[] args) throws Exception { Set<String> s = new HashSet<String>(); s.add("foo"); Iterator<String> it = s.iterator(); Method m = it.getClass().getMethod("hasNext"); System.out.println(m.invoke(it)); }
運行時,報錯如下:
Exception in thread "main" java.lang.IllegalAccessException: Class com.javapuzzle.davidwang456.ReflectorTest can not access a member of class java.util.HashMap$HashIterator with modifiers "public final" at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102) at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296) at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288) at java.lang.reflect.Method.invoke(Method.java:491) at com.javapuzzle.davidwang456.ReflectorTest.main(ReflectorTest.java:15)
hasNext 方法當然是公共的,所以它在任何地方都是可以被訪問的。那么為什么這個基於反射的方法調用是非法的呢?
我們看一下jsl 定義的規范【https://docs.oracle.com/javase/specs/jls/se12/html/jls-6.html#jls-6.6.1】
If a top level class or interface type is declared public and is a member of a package that is exported by a module, then the type may be accessed by any code in the same module, and by any code in another module to which the package is exported, provided that the compilation unit in which the type is declared is visible to that other module (§7.3). If a top level class or interface type is declared public and is a member of a package that is not exported by a module, then the type may be accessed by any code in the same module. If a top level class or interface type is declared with package access, then it may be accessed only from within the package in which it is declared. A top level class or interface type declared without an access modifier implicitly has package access. A member (class, interface, field, or method) of a reference type, or a constructor of a class type, is accessible only if the type is accessible and the member or constructor is declared to permit access: If the member or constructor is declared public, then access is permitted. All members of interfaces lacking access modifiers are implicitly public. Otherwise, if the member or constructor is declared protected, then access is permitted only when one of the following is true: Access to the member or constructor occurs from within the package containing the class in which the protected member or constructor is declared. Access is correct as described in §6.6.2. Otherwise, if the member or constructor is declared with package access, then access is permitted only when the access occurs from within the package in which the type is declared. A class member or constructor declared without an access modifier implicitly has package access. Otherwise, the member or constructor is declared private, and access is permitted if and only if it occurs within the body of the top level type (§7.6) that encloses the declaration of the member or constructor. An array type is accessible if and only if its element type is accessible.
其中一條,如果類或接口在聲明時沒任何訪問權限修飾符,那么它就隱式地被賦予了包訪問權限控制。 我們看看調用情況:
1.HashSet默認調用HashMap生成方式
/** * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has * default initial capacity (16) and load factor (0.75). */ public HashSet() { map = new HashMap<>(); }
2.調用HashMap.KeyIterator類
final class KeyIterator extends HashIterator implements Iterator<K> { public final K next() { return nextNode().key; } }
hasNext()方法,調用父類HashMap.HashIterator的hasNext()方法
abstract class HashIterator { Node<K,V> next; // next entry to return Node<K,V> current; // current entry int expectedModCount; // for fast-fail int index; // current slot HashIterator() { expectedModCount = modCount; Node<K,V>[] t = table; current = next = null; index = 0; if (t != null && size > 0) { // advance to first entry do {} while (index < t.length && (next = t[index++]) == null); } } public final boolean hasNext() { return next != null; } ........ }
我們看到HashIterator是HashMap的子類,並沒有授予public權限,那么默認情況下的訪問權限是:包訪問權限,即它可以被包內的類調用。
這里的問題並不在於該方法的訪問級別(access level),而在於該方法所在的類型的訪問級別。這個類型所扮演的角色和一個普通方法調用中的限定類型(qualifying type)是相同的[JLS 13.1]。在這個程序中,該方法是從某個類中選擇出來的,而這個類型是由從it.getClass 方法返回的Class 對象表示的。這是迭代器的動態類型(dynamic type),它恰好是私有的嵌套類(nested class)java.util.HashMap.KeyIterator。出現 IllegalAccessException 異常的原因就是這個類不是公共的,它來自另外一個包:訪問位於其他包中的非公共類型的成員是不合法的[JLS 6.6.1]。無論是一般的訪問還是通過反射的訪問,上述的禁律都是有效的。
問題解決思路
在使用反射訪問某個類型時,請使用表示某種可訪問類型的Class 對象。hasNext 方法是聲明在一個公共類型 java.util.Iterator中的,所以它的類對象應該被用來進行反射訪問。經過這樣的修改后,這個程序就會打印出true
public static void main(String[] args) throws Exception { Set<String> s = new HashSet<String>(); s.add("foo"); Iterator<String> it = s.iterator(); Method m = Iterator.class.getMethod("hasNext"); System.out.println(m.invoke(it)); }
經驗教訓
總之,訪問其他包中的非公共類型的成員是不合法的,即使這個成員同時也被聲明為某個公共類型的公共成員也是如此。不論這個成員是否是通過反射被訪問的,上述規則都是成立的。
參考資料:
【1】https://new.qq.com/omn/20190527/20190527A07COH.html?pc
【2】https://docs.oracle.com/javase/specs/jls/se12/html/jls-6.html#jls-6.6.1
【3】java解惑