反射和invoke

发布时间:2023年12月20日

反射机制是什么

1、Java反射机制的核心是在程序运行时动态加载类并获取类的详细信息,从而操作类或对象的属性和方法。本质是JVM得到class对象之后,再通过class对象进行反编译,从而获取对象的各种信息。
2、Java属于先编译再运行的语言,程序中对象的类型在编译期就确定下来了,而当程序在运行时可能需要动态加载某些类,这些类因为之前用不到,所以没有被加载到JVM。通过反射,可以在运行时动态地创建对象并调用其属性,不需要提前在编译期知道运行的对象是谁。

反射的应用场合

  • 在编译时根本无法知道该对象或类可能属于哪些类,程序只依靠运行时信息来发现该对象和类的真实信息

反射的原理

Class对象的由来是将.class文件读入内存,并为之创建一个Class对象。
image.png

反射机制常用的类文件:

  • Java.lang.Class;
  • Java.lang.reflect.Constructor;
  • Java.lang.reflect.Field;
  • Java.lang.reflect.Method;
  • Java.lang.reflect.Modifier;

反射的作用

  • 动态创建对象
  • 动态操作属性
  • 动态调用方法

在JDK中,主要由以下类来实现Java反射机制,这些类都位于java.lang.reflect包中

  • Class类:代表一个类
  • Constructor 类:代表类的构造方法
  • Field 类:代表类的成员变量(属性)
  • Method类:代表类的成员方法

参考代码实现

public class TestClass1 {
    public static void main(String[] args) throws Exception {
        //1.获取一个类的结构信息(类对象 Class对象)
        Class clazz = Class.forName("com.bjsxt.why.Dog");
        //2.从类对象中获取类的各种结构信息
        //2.1 获取基本结构信息
        System.out.println(clazz.getName());
        System.out.println(clazz.getSimpleName());
        System.out.println(clazz.getSuperclass());
        System.out.println(Arrays.toString(clazz.getInterfaces()));
        //2.2 获取构造方法
        //只能得到public修饰的构造方法
        //Constructor[] constructors = clazz.getConstructors();
        //可以得到所有的构造方法
        Constructor[] constructors = clazz.getDeclaredConstructors(); 
        System.out.println(constructors.length);
        for(Constructor con :constructors){
            //System.out.println(con.toString());
            System.out.println(con.getName() + "||" + 
                    Modifier.toString(con.getModifiers())+"  ||"
                    + Arrays.toString(con.getParameterTypes()));
        }
        //Constructor con = clazz.getConstructor();//获取无参数构造方法
      //Constructor con = clazz.getConstructor(String.class,String.class);
        Constructor con = 
clazz.getDeclaredConstructor(String.class,String.class);
        System.out.println(con);
        //2.3 获取属性
       //Field[] fields = clazz.getFields();
        Field [] fields = clazz.getDeclaredFields();
        System.out.println(fields.length);
        for(Field f :fields){
            System.out.println(f);
        }
        //Field f = clazz.getField("color");
        //private 默认 protecte public都可以获取,但不包括父类的
        Field f = clazz.getDeclaredField("age");
        System.out.println(f);
        //2.3 获取方法
       //Method[] methods = clazz.getMethods();
        Method [] methods = clazz.getDeclaredMethods();
        for(Method m : methods){
            System.out.println(m);
        }
        //Method m = clazz.getMethod("shout",String.class);
        //Method m = clazz.getMethod("run");//public
        Method m = clazz.getDeclaredMethod("run");
        System.out.println(m);
    }
}

Class类的常用方法:

getFields()—— 获得类的public类型的属性。
getDeclaredFields()—— 获得类的所有属性
getField(String name)—— 获得类的指定属性
getMethods()—— 获得类的public类型的方法
getMethod (String name,Class [] args)—— 获得类的指定方法
getConstrutors()—— 获得类的public类型的构造方法
getConstrutor(Class[] args)—— 获得类的特定构造方法
newInstance()—— 通过类的无参构造方法创建对象
getName()—— 获得类的完整名字
getPackage()—— 获取此类所属的包
getSuperclass()—— 获得此类的父类对应的Class对象
**setAccessible(true)——**突破权限的控制
field.getDeclaringClass()—— 获得此属性的class类型

获取一个类的类对象的三种方式:

public class TestClass2 {
    public static void main(String[] args) throws  Exception {
        //1.获取一个类的结构信息(类对象 Class对象)
        // 1.1Class.forName(类的完整路径字符串);
        Class clazz = Class.forName("java.lang.String");
        //1.2 类名.class
        Class clazz = String.class;
        //1.3 对象名.getClass()
        String str = "bjsxt";
        Class clazz = str.getClass();
        //Integer in = new Integer(20);
        //2.从类对象中获取类的各种结构信息
        System.out.println(clazz.getName());
        System.out.println(clazz.getSimpleName());
        System.out.println(clazz.getSuperclass());
        System.out.println(Arrays.toString(clazz.getInterfaces()));
    }
}

使用反射创建对象

调用无参数构造方法创建对象
方法1:通过Class的newInstance()方法

  • 该方法要求该Class对象的对应类有无参构造方法
  • 执行newInstance()实际上就是执行无参构造方法来创建该类的实例

方法2:通过Constructor的newInstance()方法

  • 先使用Class对象获取指定的Constructor对象
  • 再调用Constructor对象的newInstance()创建Class对象对应类的对象
  • 通过该方法可选择使用指定构造方法来创建对象

代码示例:通过Class的newInstance()方法

public class TestConstructor1 {
    public static void main(String[] args) throws Exception{
        //不使用反射创建对象
        //Dog dog = new Dog();
        //使用反射创建对象
        //1.获取类的完整路径字符串
        String className = "com.bjsxt.why.Dog";
        //2.根据完整路径字符串获取Class对象信息
        Class clazz = Class.forName(className);
        //3.直接使用Class的方法创建对象
        Object obj = clazz.newInstance();
        System.out.println(obj.toString());
    }
}

代码示例:通过Constructor的newInstance()方法创建对象

public class TestConstructor2  {
    public static void main(String[] args) throws Exception{
        //不使用反射创建对象
        //Dog dog = new Dog();
        //使用反射创建对象
        //1.获取类的完整路径字符串
        String className = "com.bjsxt.why.Dog";
        //2.根据完整路径字符串获取Class对象信息
        Class clazz = Class.forName(className);
        //3.获取无参数构造方法
        Constructor con = clazz.getConstructor();
        //4.使用无参数构造方法来创建对象
        Object obj = con.newInstance();
        System.out.println(obj);
    }
}

使用反射操作属性

通过Class对象的getFields()或者getField()方法可以获得该类所包括的全部Field属性或指定Field属性。Field类提供了以下方法来访问属性

  • getXxx(Object obj):获取obj对象该Field的属性值。此处的Xxx对应8个基本数据类型,如果该属性类型是引用类型则直接使用get(Object obj)
  • setXxx(Object obj,Xxx val):将obj对象的该Field赋值val。此处的Xxx对应8个基本数据类型,如果该属性类型是引用类型则直接使用set(Object obj, Object val)
  • setAccessible(Boolean flag):若flag为true,则取消属性的访问权限控制,即使private属性也可以进行访问

代码示例:使用反射操作属性

public class TestField {
    public static void main(String[] args) throws Exception{
        //不使用反射操作属
        //        Dog dog = new Dog();
        //        dog.nickName = "旺财";
        //        dog.age ="黑色";
        //        System.out.println(dog.nickName);
        //        System.out.println(dog.color);
        //使用反射操作属性  实际操作中使用反射直接操作属性也不多
        //1.获取类的完整路径字符串
        String className = "com.bjsxt.why.Dog";
        //2.得到类对象
        Class clazz = Class.forName(className);
        //3.使用反射创建对象
        //Object dog = clazz.newInstance();
        Object dog = clazz.getConstructor().newInstance();
        //4.获取属性
        Field f1 =  clazz.getField("color");
        //Field f2 = clazz.getField("age");
        Field f2 = clazz.getDeclaredField("age");
        //5.给属性赋值
        f1.set(dog,"黑色1"); //  dog.color ="黑色";
        f2.setAccessible(true);//突破权限的控制
        f2.set(dog,10);
        //6.输出给属性
        System.out.println(f1.get(dog)); //dog.color
        System.out.println(f2.get(dog)); //dog.age
        System.out.println(dog);
    }
}

使用反射执行方法

  • 通过Class对象的getMethods() 方法可以获得该类所包括的全部方法, 返回值是Method[]
  • 通过Class对象的getMethod()方法可以获得该类所包括的指定方法, 返回值是Method
  • 每个Method对象对应一个方法,获得Method对象后,可以调用其invoke() 来调用对应方法
  • Object invoke(Object obj,Object [] args):obj代表当前方法所属的对象的名字,args代表当前方法的参数列表,返回值Object是当前方法的返回值,即执行当前方法的结果。

代码示例:使用反射执行方法

public class TestMethod {
    public static void main(String[] args) throws Exception{
        //不使用反射执行方法
        //        Dog dog = new Dog();
        //        dog.shout();
        //        int result = dog.add(10,20);
        //        System.out.println(result);
        //使用反射执行方法
        //1.获取类的完整路径字符串
        String className = "com.bjsxt.why.Dog";
        //2.得到类对象
        Class clazz = Class.forName(className);
        //3.使用反射创建对象
        //Object dog = clazz.newInstance();
        Object dog = clazz.getConstructor().newInstance();
        //4.获取方法
        Method m1 = clazz.getMethod("shout");
        Method m2 = clazz.getMethod("add",int.class,int.class);
        //5.使用反射执行方法
        m1.invoke(dog);//dog.shout();
        Object result = m2.invoke(dog,10,20);   
        System.out.println(result);
    }
}

invoke方法

  • 在Java中很多方法都会调用invoke方法,很多异常的抛出多会定位到invoke方法:
java.lang.NullPointerException
  at ......
  at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
  at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  at java.lang.reflect.Method.invoke(Method.java:497)

invoke0:jvm实现的 invoke方法

invoke执行过程

  • invoke方法用来在运行时动态地调用某个实例的方法,实现如下:
  • invoke() 就是调用类中的方法,
    • 第一个参数是obj(对象),在我们平常使用过程中用到的是类,类是对象的一个集合,
    • 第二个参数是args(参数),是调用invoke这个方法所使用的参数,我们使用是一般是类中的方法(method),因此invoke方法我们也可以这样理解:invoke(class,method),相当于把类中的方法参数化了。
@CallSensitive
public Object invoke(Object obj, Object ... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
    if (!override) {
        if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
            Class<?> caller = Refelection.getCallerClass();
            checkAccess(caller, clazz, obj, modifiers);
        }
    }
    MethodAccessor ma = methodAccessor;
    if (ma == null) {
        ma = acquireMethodAccessor();
    }
    return ma.invoke(obj, args);
}

访问私有方法(TestClass为普通的对象、privateMethod私有调用方法)

/**
 * 访问对象的私有方法
 * 为简洁代码,在方法上抛出总的异常,实际开发别这样
 */
private static void getPrivateMethod() throws Exception{
    //1. 获取 Class 类实例
    TestClass testClass = new TestClass();
    Class mClass = testClass.getClass();
    
    //2. 获取私有方法
    //第一个参数为要获取的私有方法的名称
    //第二个为要获取方法的参数的类型,参数为 Class...,没有参数就是null
    //方法参数也可这么写 :new Class[]{String.class , int.class}
    Method privateMethod =
            mClass.getDeclaredMethod("privateMethod", String.class, int.class);
            
    //3. 开始操作方法
    if (privateMethod != null) {
        //获取私有方法的访问权
        //只是获取访问权,并不是修改实际权限
        privateMethod.setAccessible(true);
        
        //使用 invoke 反射调用私有方法
        //privateMethod 是获取到的私有方法
        //testClass 要操作的对象
        //后面两个参数传实参
        privateMethod.invoke(testClass, "Java Reflect ", 666);
    }
}

需要注意的是,第3步中的 setAccessible(true) 方法,是获取私有方法的访问权限,如果不加会报异常 IllegalAccessException,因为当前方法访问权限是“private”的
常量是不可以修改的

反射机制的优缺点

1、优点:
在运行时获得类的各种内容,进行反编译,对于Java这种先编译再运行的语言,能够让我们很方便的创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象。
2、缺点:
(1)反射会消耗一定的系统资源,因此,如果不需要动态地创建一个对象,那么就不需要用反射;
(2)反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。

参考地址:
知乎
csdn

文章来源:https://blog.csdn.net/weixin_44824381/article/details/135076592
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。