简单易懂Java反射的setAccessible()方法

前言:在使用Field类的对象访问我自定义的Employee类对象的name域时,抛出异常illegalAccessException。查询原因为:在访问nam

前言:在使用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()内容请搜索好代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持好代码网!