【学习笔记】反射(三)获取并操作对象及方法

反射(三)获取并操作对象及方法

 

获取运行时类的结构

 

  • 通过反射获取运行时类的完整结构

Field、Method、Constructor、Superclass、Interface、Annotation...

我们以User类为例:

class User{
    private String name;
    private int age;
​
    public User() {
    }
​
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
​
    public String getName() {
        return name;
    }
​
    public void setName(String name) {
        this.name = name;
    }
​
    public int getAge() {
        return age;
    }
​
    public void setAge(int age) {
        this.age = age;
    }
​
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

 

  1. 获得类的名字

//获取User类的class对象
Class c1 = Class.forName("com.reflection.User");
//获得类的名字
System.out.println(c1.getName());   //类名 + 包名
System.out.println(c1.getSimpleName());  //类名

image-20220805093206240

 

  1. 获得类的属性

//获得类的属性
Field[] fields = c1.getFields();   //只能找到public的属性
for (Field field : fields) {
    System.out.println(field);
}
fields = c1.getDeclaredFields();   //找到全部的属性
for (Field field : fields) {
    System.out.println(field);
}
//获得指定属性,与上面相同getField()只能找到public的属性
Field name = c1.getDeclaredField("name");  
System.out.println(name);

image-20220805094219486

 

  1. 获得类的方法

//获得类的方法
Method[] methods = c1.getMethods();    //获得本类以及父类的public方法
for (Method method : methods) {
    System.out.println("getMethods:"+method);
}
methods = c1.getDeclaredMethods();    //获得本类的所有方法,包括私有的方法
for (Method method : methods) {
    System.out.println("getDeclaredMethods:"+method);
}

image-20220805094928531

获得指定的方法:需要传入方法名和参数的类型

//获得指定的方法
Method method = c1.getMethod("getName",null);
Method method1 = c1.getMethod("setName",String.class);
System.out.println(method);
System.out.println(method1);

image-20220805095328535

 

  1. 获得类的构造器

//获得类的属性
Field[] fields = c1.getConstructor();  
for (Field field : fields) {
    System.out.println(field);
}

image-20220805095958079

获得指定的构造器:需要传入参数的类型

//获得指定的构造器
Constructor declaredConstructor = c1.getDeclaredConstructor(String.class, int.class);
System.out.println("指定构造器"+declaredConstructor);

image-20220805100321365

 

 

动态创建对象执行方法

有了Class对象,能做什么?

  • 创建类的对象:调用Class对象的newInstance()方法

    • 类必须有一个无参数的构造器

    • 类的构造器的访问权限需要足够

package com.reflection;
​
public class Demo08 {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        //获得Class对象
        Class c1 = Class.forName("com.reflection.User");
​
        //构造一个对象
        User user = (User) c1.newInstance();
​
        System.out.println(user);
    }
}

image-20220805142648097

 

调用newInstance()方法,本质上是调用了类的无参构造器 ,如果没有无参构造就会报错

如果没有无参构造并且想要创建对象,应该怎么办呢?

  1. 通过Class类的getDeclaredConstructor(Class...parameterTypes)方法 取得本类的指定参数的构造器

  2. 然后向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数

  3. 通过Constructor 实例化对象

//通过构造器创建对象
Constructor constructor = c1.getDeclaredConstructor(String.class, int.class);
User user2 = (User) constructor.newInstance();
System.out.println(user2);

image-20220805143847112

 

  • 通过反射调用普通方法,通过Method类完成

    • 通过Class类的getMethod(String name,Class...parameterTypes)方法取得一个Method 对象,并设置此方法操作时所需要的参数类型

    • 之后使用Object invoke(Object obj,Object[] args) 进行调用,并向方法中传递要设置的obj对象的参数信息

//通过反射调用普通方法
User user3 = (User) c1.newInstance();
//获取方法
Method setName = c1.getDeclaredMethod("setName", String.class);
//激活执行,要传入两个参数,一个是获取方法所对应的对象,一个是方法本身需要的参数
//setName方法是user3的方法,它需要传入一个字符串的参数即“李四”
setName.invoke(user3,"李四");
System.out.println(user3.getName());

image-20220805144910882

 

  • 通过反射操作属性

//通过反射操作属性
User user4 = (User) c1.newInstance();
//获取属性
Field name = c1.getDeclaredField("name");
//调用set方法,传入对象和需要给属性赋的值
name.set(user4,"王五");
System.out.println(user4.getName());

image-20220805145652191

 

我们执行上面的代码,发现报错了,原因就是name这个属性在User中是private,我们没有权限直接操作,但是我们可以调用setAccessible(true)方法关闭安全检测,然后再去操作私有属性就可以了

//通过反射操作属性
User user4 = (User) c1.newInstance();
//获取属性
Field name = c1.getDeclaredField("name");
//关闭安全检测
name.setAccessible(true);
//调用set方法,传入对象和需要给属性赋的值
name.set(user4,"王五");
System.out.println(user4.getName());

image-20220805150029562

 

调用指定方法:Object invoke(Object obj,Object...args)

  • Object 对应原方法的返回值,若原方法无返回值,此时返回null

  • 若原方法若为静态方法,此时形参Object obj 可为null

  • 若原方法形参列表为空,则Object[] args 为null

  • 若原方法声明为private,则需要在调用此 invoke() 方法之前,显示调用方法对象的setAccessible(true)方法,将可访问private方法

 

setAccessible

  • Method 和 Field、Constructor 对象都有setAccessible()方法

  • setAccessible的作用是启动和禁用访问安全检查的开关

  • 参数值为true则指示反射的对象在使用时应该取消Java语言访问检查

    • 提高反射的效率,如果代码中必须用反射,而该句代码需要频繁的被调用,那么请设置为true

    • 使得原本无法访问的私有成员也可以访问

  • 参数值为false则指示反射的对象应该实施Java语言访问检查

 

性能对比分析

我们分别用 普通方式调用、反射方式调用、关闭安全检测的反射方式调用 User类的getName()方法,然后执行十亿次,计算执行的时间

package com.reflection;
​
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
​
public class Demo09 {
    //普通方式调用
    public static void test01(){
        User user = new User();
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 1000000000; i++) {
            user.getName();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("普通方式调用:"+(endTime-startTime) + "ms");
    }
    //反射方式调用
    public static void test02() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        User user = new User();
        Class c1 = user.getClass();
        Method getName = c1.getDeclaredMethod("getName", null);
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 1000000000; i++) {
            getName.invoke(user,null);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("反射方式调用:"+(endTime-startTime) + "ms");
    }
    //关闭安全检测方式调用
    public static void test03() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        User user = new User();
        Class c1 = user.getClass();
        Method getName = c1.getDeclaredMethod("getName", null);
        getName.setAccessible(true);
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 1000000000; i++) {
            getName.invoke(user,null);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("关闭检测调用:"+(endTime-startTime) + "ms");
    }
​
    public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
        test01();
        test02();
        test03();
    }
}

image-20220805152907574

 

从结果可以看出:反射方式调用的效率很低,但关闭安全检测之后,效率有所提升

posted @ 2022-08-05 15:32  GrowthRoad  阅读(47)  评论(0编辑  收藏  举报