簡單易懂Java反射的setAccessible()方法

前言:在使用Field類的對象訪問我自定義的Employee類對象的name域時,拋出異常illegalAccessException。查詢原因為:在訪問name域時,Java進行瞭訪問檢查,發現該域是private修飾的,不能直接訪問,因此拋出異常。

概要

本文首先詳細介紹訪問檢查的概念,然後介紹關於反射的運行時訪問檢查,並說明可能存在的問題。最後介紹可以通過setAccessible方法屏蔽或者說禁用運行時訪問檢查。讓我們開始把~~

一、 什麼是Java的訪問檢查

訪問檢查,就是查看成員屬性、成員方法的使用是否符合訪問權限(public、protected、default、private)。

有點太理論化瞭,簡單來說,如果一個類的成員(屬性或者方法)的訪問權限是private,那麼該成員隻能在當前類中使用;如果一個類的成員的訪問權限是public,那麼該成員可以在任意類中使用;如果一個類的成員的訪問權限是default,那麼該成員隻能在同一個包下面的類中使用;如果一個類的成員的訪問權限是protected,那麼該成員可以在同一個包下面的類中和其他包下面的該類的子類中使用。

如果,類的成員的訪問權限是default,你卻在另一個包中使用瞭該成員,當編譯時,編譯器會進行訪問檢查,發現成員的使用與給定的訪問權限不一致,因此會報錯。

舉個例子,在com.example包下創建People類,有四個成員變量。在com.example.app包下(它是不同於com.example的包)下,使用People類的四個成員變量。

package com.example;

package com.example;

public class People {

    private int privateVar = 1;
            int defaultVar = 2;
    protected int protectedVar = 3;
    public int publicVar = 4;
}


package com.example.app;

import com.example.People;
public class TestMain {
    public static void main(String[] args) {
        People p = new People();
        System.out.println(p.privateVar);
        System.out.println(p.defaultVar);
        System.out.println(p.protectedVar);
        System.out.println(p.publicVar);
    }
}

編譯後提示,publicVar的使用符合public的訪問權限,所以沒有出錯。

相信大傢都理解瞭訪問檢查是什麼,那麼,反射對象的訪問檢查是怎麼的呢?

一個類的成員屬性、成員方法、構造函數,在反射中分別被抽象為Field、Method、Counstructor類。

我們可以使用Field訪問對象的成員屬性,成員屬性的訪問權限,編譯器是不知道的,隻有運行時才知道。因此對於反射對象(例如Field)訪問權限的檢查隻能交給虛擬機。

如果,虛擬機在運行時,發現成員的使用與給定的訪問權限不一致,如下代碼

package com.example.app;

import com.example.People;
import java.lang.reflect.Field;
public class TestMain {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        People p = new People();
        Class cl = p.getClass();
        
        // 利用反射訪問private修飾的成員變量 
        Field f = cl.getDeclaredField("privateVar")
        System.out.println(f.get(p));        
    }
}

運行時,拋出異常:java.lang.IllegalAccessException

綜上,訪問檢查可以時編譯器在編譯時進行或者虛擬機在運行時進行(主要是針對反射)

二、 setAccessible() 方法介紹

 setAccessible(boolean flag)方法是AccessibleObject類中的一個方法,它是Field、 Method、Constructor的公共父類。當Field、Method或Constructor (三者都是反射對象)分別用於設置字段(set(Object obj, Object value))或獲取字段(get(Object obj))、調用方法(invoke(Object obj, Object... args))或創建和初始化類的新實例(newInstance(Object... initargs))時,將執行運行時訪問檢查

引用自《Java核心技術 第十版》

註意:方法名setAccessible很容易讓人產生誤解,給人的感覺是設置瞭成員的可訪問性,例如,覺得public修飾的成員是任意類都可以訪問的,所以可訪問標志是true;覺得private修飾的成員隻有本類可以訪問,所以可訪問標志是false。其實不然,不管是什麼訪問權限,其可訪問標志的值都為false。

測試代碼如下:

package com.example.app;

import com.example.People;
import java.lang.reflect.Field;
public class TestMain {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        People p = new People();
        Class cl = p.getClass();
        // 打印輸出所有成員變量的名字及可訪問標志
        for (Field f : cl.getDeclaredFields()) {
            System.out.println(f.getName() + ": " + f.isAccessible());
        }
    }
}

輸入結果:

上面中的API說得很清楚,這個可訪問標志表示是否屏蔽Java語言的訪問檢查默認值是false,(上面已經測試)

可以通過setAccessible(true) 修改默認值,如此會屏蔽Java語言的(運行時)訪問檢查,使得對象的私有成員可以訪問,而不報錯。

package com.example.app;

import com.example.People;

import java.lang.reflect.Field;

public class TestMain {

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        People p = new People();
        Class cl = p.getClass();

        for (Field f : cl.getDeclaredFields()) {
            //屏蔽對象的訪問檢查
            f.setAccessible(true);
            // 訪問不符合訪問權限的成員屬性
            System.out.println(f.getName() + " = " + f.get(p));
        }
    }
}

輸入結果:

 到此這篇關於簡單易懂Java反射的setAccessible()方法的文章就介紹到這瞭,更多相關Java反射setAccessible()內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: