欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java 基础知识--反射

程序员文章站 2022-06-15 20:27:52
...

反射在Java编程中是很常用的功能,开发和阅读源码时,总能看到反射的身影。这里要强调一下,Java的反射真的很简单很简单,很多人以为Java的反射是一个很高深的知识点,一直不敢去触碰,以至于成为很多程序员的短板。接下来就一起来看看这个简单了一逼的反射机制

Java的反射

反射概述

Java的反射是 在运行状态中 ,对于任何一个,都能知道它的所有属性方法;对于任何一个对象,都能调用它的所有属性方法。这种能够 动态获取类的信息动态调用对象的方法 的特性就是Java的反射机制

简单的说反射就是:动态获取类的信息 和 动态调用对象的方法(或属性)

反射的关键:获取代表字节码文件(.class文件)的Class对象

注: .java 文件通过编译最终生成 .class 文件
注: Class对象代表着 .class(字节码文件) [ 当然要先得到Class对象 ]

类加载简单示例

先简单了解一下类的加载过程

Java 基础知识--反射

Java编译器会将 .java 文件编译成 .class 文件

1.当程序执行 new User() 时,JVM会查找并加载 User.class 到内存中

2.Jvm 将 .class 加载到内存,自动创建一个Class对象。 Class对象由JVM创建,有且仅有一个,第二次 new User() 不再产生新的 Class 对象

3.一个类对应一个Class对象

反射的本质就是:得到 Class 对象后,反向获取 User 类的各种信息。比如:构造方法,成员变量,方法等

接下来就看看如何使用 Class 对象获取到 User 类的各种信息

Java反射基础API使用

首先,创建一个 User 类

public class User {

}

这个类本身没有什么意义,用于演示反射示例

1.获取Class对象的方式

1.通过 Object 的 getClass()

Object 类中存在 getClass() 方法,所有对象继承自 Object 因此可以使用 Object 的 getClass() 方法获取 Class 对象

2.通过任意类的“静态”class属性 例如:User.class

3.通过Class类的静态方法 Class.forName(String name)

    public static void main(String args[]) {

        //1.使用Object 的 getClass() 方式获取 Class 对象
        User user = new User();
        Class uClass1 = user.getClass();
        System.out.println("type1:" + uClass1.getName());

        //2.使用任意类的"静态"class 属性
        Class uClass2 = User.class;
        System.out.println("type2:" + uClass2.getName());

        //3.使用Class静态方法 Class.
        try {
            Class uClass3 = Class.forName("demo.reflex.User");//参数是包括包名的完整路径
            System.out.println("type3:" + uClass3.getName());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

结果:

type1:demo.reflex.User
type2:demo.reflex.User
type3:demo.reflex.User

注意:运行期间,只有一个Class对象存在

以上几种方式都可以获取Class对象。第一个种能获取到对象了,一般就不需要使用反射;第二种需要导入包,依赖性太强;第三种较为常用,也比较符合反射的场景,根据一个完整的类名获取Class对象

2.通过反射获取类构造函数 Constructor

User 类

public class User {

    User(char ch) {
        System.out.println("默认 构造方法 ch:" + ch);
    }

    public User() {
        System.out.println("public 构造方法 无参");
    }

    public User(String name) {
        System.out.println("public 构造方法 name:" + name);
    }

    public User(String name, int age) {
        System.out.println("public 构造方法 name:" + name + " age:" + age);
    }

    protected User(int age) {
        System.out.println("protected 构造方法 age:" + age);
    }

    private User(boolean flag) {
        System.out.println("private 构造方法 flag:" + flag);
    }

}

User 类构造方法,包括了 public , private , protected , 默认 类型的构造方法

测试使用

    public static void main(String args[]) throws Exception {

        //获取Class
        Class cls = Class.forName("demo.reflex.User");

        System.out.println("-------获取所有公共的构造方法---------");
        Constructor[] consArray = cls.getConstructors();
        for (int i = 0; i < consArray.length; i++) {
            System.out.println(consArray[i]);
        }

        System.out.println("-------获取所有的构造方法 包括:public,private,protected,默认类型---------");
        consArray = cls.getDeclaredConstructors();
        for (int i = 0; i < consArray.length; i++) {
            System.out.println(consArray[i]);
        }

        System.out.println("-------获取 无参 构造方法并调用---------");
        Constructor cons = cls.getConstructor();
        System.out.println(cons);
        cons.newInstance();

        System.out.println("-------获取 private 构造方法并调用---------");
        cons = cls.getDeclaredConstructor(boolean.class);
        System.out.println(cons);
        cons.setAccessible(true);//暴力访问(忽略访问修饰符)
        cons.newInstance(true);

    }

结果输出

-------获取所有公共的构造方法---------
public demo.reflex.User(java.lang.String,int)
public demo.reflex.User(java.lang.String)
public demo.reflex.User()
-------获取所有的构造方法 包括:public,private,protected,默认类型---------
private demo.reflex.User(boolean)
protected demo.reflex.User(int)
public demo.reflex.User(java.lang.String,int)
public demo.reflex.User(java.lang.String)
public demo.reflex.User()
demo.reflex.User(char)
-------获取 无参 构造方法并调用---------
public demo.reflex.User()
public 构造方法 无参
-------获取 private 构造方法并调用---------
private demo.reflex.User(boolean)
private 构造方法 flag:true

api解释

1.获取所有构造方法 返回符合要求的 列表

//获取所有"公共的"构造方法  public
public Constructor<?>[] getConstructors()

//获取所有的构造方法 包括: public,private,protected,默认类型 
public Constructor<?>[] getDeclaredConstructors() 

2.获取单个构造方法 返回符合要求的 对象

//获取"公共的"构造方法   
public Constructor<T> getConstructor(Class<?>... parameterTypes)

/**
 *获取任意访问类型的构造方法  
 *
 *parameterTypes:形参类型(记住是类型)  例:int.class ,String.class
 */
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)

3.调用构造方法

/**
 *调用构造方法  
 *
 *initargs:构造方法参数
 */
public T newInstance(Object ... initargs)

newInstance() 方法属于 Constructor 类,无参构造方法参数可以 null 或者 不填; 对于调用 private 私有构造方法需要调用 cons.setAccessible(true); 表示打破限制,强制访问,否则出错

3.通过反射获取类成员变量 Field

User类

public class User {

    public User() {
    }

    /*成员变量*/
    private int age;
    public String name;
    protected String phone;
    boolean isVip;

    @Override
    public String toString() {
        return "[User name=" + name + ", age=" + age + "]";
    }
}

测试使用

    public static void main(String args[]) throws Exception {

        //获取Class
        Class cls = Class.forName("demo.reflex.User");

        System.out.println("-------获取所有公共的成员变量---------");
        Field[] fieldArray = cls.getFields();
        for (int i = 0; i < fieldArray.length; i++) {
            System.out.println(fieldArray[i]);
        }

        System.out.println("-------获取所有的成员变量 包括:public,private,protected,默认类型---------");
        fieldArray = cls.getDeclaredFields();
        for (int i = 0; i < fieldArray.length; i++) {
            System.out.println(fieldArray[i]);
        }

        System.out.println("-------获取公共属性---------");
        Field field = cls.getField("name");
        System.out.println(field);

        System.out.println("-------设置属性并查看值---------");
        //根据构造函数 .newInstance() 获取类对象
        Object object = cls.getConstructor().newInstance();
        field.set(object, "Ruffian");
        //验证刚刚设置的属性
        User user = (User) object;
        System.out.println("查看name属性值:" + user.name);

        System.out.println("-------获取私有属性---------");
        field = cls.getDeclaredField("age");
        System.out.println(field);

        System.out.println("-------设置私有属性并查看值---------");
        field.setAccessible(true);
        field.set(object, 18);
        //验证刚刚设置的属性
         System.out.println("查看age属性值:" + user.toString());
    }

结果输出:

-------获取所有公共的成员变量---------
public java.lang.String demo.reflex.User.name
-------获取所有的成员变量 包括:public,private,protected,默认类型---------
private int demo.reflex.User.age
public java.lang.String demo.reflex.User.name
protected java.lang.String demo.reflex.User.phone
boolean demo.reflex.User.isVip
-------获取公共属性---------
public java.lang.String demo.reflex.User.name
-------设置属性并查看值---------
查看name属性值:Ruffian
-------获取私有属性---------
private int demo.reflex.User.age
-------设置私有属性并查看值---------
查看age属性值:[User name=Ruffian, age=18]

api解释

1.获取所有属性 返回符合要求的 列表

//获取所有"公共的"属性  public
public Constructor<?>[] getFields()

//获取所有的属性 包括: public,private,protected,默认类型 
public Constructor<?>[] getDeclaredFields() 

2.获取单个属性 返回符合要求的 对象

//获取"公共的"属性
public Field getField(String fieldName)

/**
 *获取任意访问类型的属性
 *
 *fieldName:属性名称  例: name , age 
 */
public Field getDeclaredField(String fieldName)

3.设置属性值

/**
 *1.obj:要设置的字段所在的对象;
 *2.value:要为字段设置的值;
 */
public void set(Object obj,Object value)

field.set(object, "Ruffian"); 表示为属性设置值: user.name=Ruffian

第一个参数:要设置的字段所在的对象
第二个参数:要为字段设置的值

至于上述示例中获取属性值的方式,是为了方便校验通过反射设置值是否成功,一般我们通过反射获取方法,得到属性值。下面看看如何通过反射获取成员方法和调用

4.通过反射获取成员方法 调用方法 Method

User类

public class User {

    public void showName(String name) {
        System.out.println("public showName()  参数name = " + name);
    }

    protected void show() {
        System.out.println("public show()");
    }

    void showDefault() {
        System.out.println(" showDefault() 默认无参方法");
    }

    private String getAge(int age) {
        System.out.println("private getAge() 返回值:字符串  参数:int age = " + age);
        return "age:" + age;
    }
}

使用示例

    public static void main(String args[]) throws Exception {

        //获取Class
        Class cls = Class.forName("demo.reflex.User");

        System.out.println("-------获取所有方法---------");
        //Method[] methodArray = cls.getMethods();
        Method[] methodArray = cls.getDeclaredMethods();
        for (int i = 0; i < methodArray.length; i++) {
            System.out.println(methodArray[i]);
        }

        System.out.println("-------调用任意访问类型方法---------");
        //根据构造函数 .newInstance() 获取类对象
        Object object = cls.getConstructor().newInstance();
        Method method = cls.getDeclaredMethod("getAge", int.class);
        method.setAccessible(true);
        Object result = method.invoke(object, 18);
        System.out.println("方法返回值:" + result.toString());
    }

结果输出

-------获取所有方法---------
private java.lang.String demo.reflex.User.getAge(int)
public void demo.reflex.User.showName(java.lang.String)
protected void demo.reflex.User.show()
void demo.reflex.User.showDefault()
-------调用任意访问类型方法---------
private getAge() 返回值:字符串  参数:int age = 18
方法返回值:age:18

api解释

1.获取所有方法 返回符合要求的 列表

//获取所有"公共的"方法  public
public Method[] getMethods()

//获取所有的方法 包括: public,private,protected,默认类型 
public Method[] getDeclaredMethods()

2.获取单个方法 返回符合要求的 对象

//获取"公共的"方法
public Method getMethod(String name, Class<?>... parameterTypes)


/**
 *获取任意访问类型的方法
 *
 *name:方法名称  例: getAge , showName 
 *parameterTypes:方法形参类型(记住是类型)  例:int.class ,String.class
 */
public Method getDeclaredMethod(String name, Class<?>... parameterTypes)

3.调用方法

/**
 *obj:要调用方法所在的对象
 *args:调用方法所需的参数
 */
public native Object invoke(Object obj, Object... args)

method.invoke(object, 18); 表示调用方法: User user=new User(); user.getAge(18);

第一个参数:

要调用方法所在的对象
如果调用方法是静态的,那么可以忽略指定的 obj 参数。该参数可以为 null

第二个参数:

调用方法所需的参数
调用方法所需的形参个数为 0,则所提供的 args 数组长度可以为 0 或 null

4.Method 类的一点拓展

了解一个方法可以通过: 方法名称,方法修饰符,返回类型,形参类型/个数

看下示例程序

    public static void main(String args[]) throws Exception {

        //获取Class
        Class cls = Class.forName("demo.reflex.User");

        Method[] methodArray = cls.getDeclaredMethods();
        Method method;
        for (int i = 0; i < methodArray.length; i++) {
            method = methodArray[i];

            String name = method.getName();//方法名称
            int modifiers = method.getModifiers();//方法修饰符
            Class returnType = method.getReturnType();//方法返回类型
            Class[] parameterTypes = method.getParameterTypes();//方法参数类型数组(形参类型)
            System.out.println("方法名:" + name + " 修饰符:" + Modifier.toString(modifiers) + " 返回值类型:" + returnType);

            //方法参数(形参)
            for (int j = 0; j < parameterTypes.length; j++) {
                System.out.println("形参" + j + ":" + parameterTypes[j]);
            }
            System.out.println("");
        }
    }

运行结果:

方法名:showName 修饰符:public 返回值类型:void
形参0:class java.lang.String

方法名:show 修饰符:protected 返回值类型:void

方法名:showDefault 修饰符: 返回值类型:void

方法名:getAge 修饰符:private 返回值类型:class java.lang.String
形参0:int
5.Java 反射的一点场景使用
5.1 反射结合配置文件使用

User类

public class User {

    public void show() {
        System.out.println("show方法被调用...");
    }
}

在 D 盘目录下新建一个 pro.txt 文件

className = demo.reflex.User   //类名完整路径
methodName = show              //方法名

示例代码:

    public static void main(String args[]) throws Exception {

            //获取Class
            Class cls = Class.forName(getValue("className"));
            //获取方法
            Method method = cls.getMethod(getValue("methodName"));

            Object object = cls.getConstructor().newInstance();//获取对象
            //调用方法
            method.invoke(object, null);
    }

    /***
     * 根据key获取配置文件中 value
     *
     * @param key
     * @return
     * @throws IOException
     */
    public static String getValue(String key) throws IOException {
        Properties pro = new Properties();//获取配置文件的对象
        File file = new File("D:\\pro.txt");//获取文件
        FileReader in = new FileReader(file);//获取输入流
        pro.load(in);//将流加载到配置文件对象中
        in.close();
        return pro.getProperty(key);//返回根据key获取的value值
    }

输出结果:

show方法被调用...
5.2 通过反射越过泛型检查

还记得 method.invoke(Object obj, Object... args) 方法吗? 第二个参数表示:调用方法所需参数。这是一个 Object 对象,那么,嘿嘿嘿~~~

示例代码:

    public static void main(String args[]) throws Exception {

        ArrayList<String> list = new ArrayList<>();
        list.add("AAA");
        list.add("BBB");

        /**
         * 获取ArrayList的Class对象,反向的调用add()方法,添加数据
         */
        Class listClass = list.getClass(); //获取Class对象
        //获取add()方法
        Method m = listClass.getMethod("add", Object.class);
        //调用add()方法
        m.invoke(list, false);
        m.invoke(list, 100);

        //遍历集合
        for (Object obj : list) {
            System.out.println(obj);
        }

    }

输出结果:

AAA
BBB
false
100

上述代码,ArrayList 指定类型 String 如果直接 list.add(100) 则会包类型错误,通过反射调用 m.invoke(obj, obj); 传入其他类型的对象,从而实现越过泛型的检查。当然这只是一个例子,实际编码中,还是需要严格按照规范去写代码

虽然,博文比较长,但是内容真的很简单,纯属无脑式的API调用,起码通过学习反射 API 的使用先了解反射的基础。这也是为什么文章开篇说反射简单了一逼,就是为了鼓舞大家有勇气面对这些知识点

由于本文都是API的简单使用,也都参考网络资料,就不一一附上连接了。