1. 程式人生 > >如何在女友卸妝後,正確的找到她?---java中使用反射的小祕密

如何在女友卸妝後,正確的找到她?---java中使用反射的小祕密

故事背景

小白是個程式猿,剛畢業兩年,最近交了一個女朋友,是同事介紹的。女朋友和閨蜜住在一起。小白早上很早接到女朋友電話,昨天她的一個檔案錯放到了他的電腦包,希望他幫忙送到她住的地方,她今天要向她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