java反射详解

Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。

反射机制的优点与缺点

为什么要用反射机制?直接创建对象不就可以了吗,这就涉及到了动态与静态的概念

  • 静态编译:在编译时确定类型,绑定对象,即通过。
  • 动态编译:运行时确定类型,绑定对象。动态编译最大限度发挥了java的灵活性,体现了多态的应用,可以降低类之间的藕合性。

反射机制的优点:

可以实现动态创建对象和编译,体现出很大的灵活性,特别是在J2EE的开发中
它的灵活性就表现的十分明显。比如,一个大型的软件,不可能一次就把把它设计的很完美,当这个程序编 译后,发布了,当发现需要更新某些功能时,我们不可能要用户把以前的卸载,再重新安装新的版本,假如 这样的话,这个软件肯定是没有多少人用的。采用静态的话,需要把整个程序重新编译一次才可以实现功能 的更新,而采用反射机制的话,它就可以不用卸载,只需要在运行时才动态的创建和编译,就可以实现该功 能。

反射的缺点

它的缺点是对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于只直接执行相同的操作。

关于Class

说反射我们就不得不说一下Class

  • 1、Class是一个类,一个描述类的类(也就是描述类本身),封装了描述方法的Method,描述字段的Filed,描述构造器的Constructor等属性
  • 2、对象照镜子后(反射)可以得到的信息:某个类的数据成员名、方法和构造器、某个类到底实现了哪些接口。
  • 3、对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含了特定某个类的有关信息。
  • 4、Class 对象只能由系统建立对象
  • 5、一个类在 JVM 中只会有一个Class实例

获取Class的三种方法

  • 1、Class cl=A.class;
    JVM将使用类A的类装载器, 将类A装入内存(前提是:类A还没有装入内存),不对类A做类的初始化工作.返回类A的Class的对象。

  • 2、Class clazz=对象引用obj.getClass();
    返回引用obj运行时真正所指的对象(因为:子对象的引用可能会赋给父对象的引用变量中)所属的类的Class的对象 。

  • 3、Class.forName(类名字符串);
    (注:类名字符串是包名+类名)装入类,并做类的静态初始化,返回Class的对象

.getClass()是动态的,其余是静态的。

.class和Class.forName()只能返回类内field的默认值,getClass可以返回当前对象中field的最新值

Class.forName() 返回的是一个类,.newInstance() 后才创建一个对象,Class.forName()的作用是要求JVM查找并加载指定的类,也就是说JVM会执行该类的加载

举例:

package anno;

public class GetClass {

    public static void main(String[] args) {
        Class clazz = null;

        // 1 直接通过类名.Class的方式得到
        clazz = GetClass.class;
        System.out.println("通过类名: " + clazz);

        // 2 通过对象的getClass()方法获取,这个使用的少(一般是传的是Object,不知道是什么类型的时候才用)
        Object obj = new GetClass();
        clazz = obj.getClass();
        System.out.println("通过getClass(): " + clazz);

        // 3 通过全类名获取,用的比较多,但可能抛出ClassNotFoundException异常
        try {
            clazz = Class.forName("anno.GetClass");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        System.out.println("通过全类名获取: " + clazz);
    }

}

输出

通过类名: class anno.GetClass
通过getClass(): class anno.GetClass
通过全类名获取: class anno.GetClass

1、利用newInstance创建对象:调用的类必须有无参的构造器

package reflect;

public class Instance {
    public static void main(String[] args) throws InstantiationException, IllegalAccessException {
         Class clazz;
        try {

            //使用Class类的newInstance()方法创建类的一个对象  
            //实际调用的类的那个 无参数的构造器(这就是为什么写的类的时候,要写一个无参数的构造器,就是给反射用的)  
            //一般的,一个类若声明了带参数的构造器,也要声明一个无参数的构造器  
            clazz = Class.forName("reflect.Instance");
             Instance obj = (Instance) clazz.newInstance();  
                System.out.println(obj);  
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }    

    }
}


2、获取构造器方法Constructor及参数Parameter

代码

package reflect;

import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;

public class Constructors {
    public Constructors() {
        System.out.println("无参构造函数");
    }

    public Constructors( String s) {
        System.out.println("有1个参构造函数");
        System.out.println(s);
    }

    public Constructors(final String s, int num) {
        System.out.println("有2个参构造函数");
        System.out.println(s);
    }

    public static void main(String[] args) {
        try {
            // 1、获取指定构造方法
            Class clazz = Class.forName("reflect.Constructors");
            // 无参构造函数生成对象
            clazz.newInstance();
            //Constructor con = clazz.getConstructor(String.class, int.class);
            //Constructors cons = (Constructors) con.newInstance("获取构造器实例化对象", 4);
            // 2、 获取全部构造方法
            Constructor[] conarr = clazz.getConstructors();
            for (Constructor constructor : conarr) {
                // 获取每个构造函数的参数字节码对象
                Class[] parameterTypes = constructor.getParameterTypes();
                for (Class pclass : parameterTypes) {
                    System.out.print(pclass.getName() + ", ");
                }
                System.out.println();
                // 获取构造参数对象
                Parameter[] parameters = constructor.getParameters();
                for (Parameter parameter : parameters) {
                     System.out.println("通过parameter.getModifiers()获取参数修饰符:" + Modifier.toString(parameter.getModifiers()));
                     System.out.println("通过parameter.getName()获取参数名:" + parameter.getName()); 
                     System.out.println("通过parameter.getParameterizedType()获取参数化类型(泛型):" + parameter.getParameterizedType()); 
                     System.out.println("通过parameter.toString()获取参数的字符串描述:" + parameter.toString());
                     System.out.println("通过parameter.isSynthetic()判断参数是否是合成的:" + parameter.isSynthetic()); 
                     System.out.println("通过parameter.isImplicit()判断参数是否是隐式的:" + parameter.isImplicit()); 
                     System.out.println("通过parameter.isNamePresent()判断参数是否存在可用:" + parameter.isNamePresent()); 
                     System.out.println("通过parameter.isVarArgs()判断参数是否是可变的:" + parameter.isVarArgs() + "\n");
                }

            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

输出

无参构造函数
有2个参构造函数
获取构造器实例化对象
java.lang.String, int, 
通过parameter.getModifiers()获取参数修饰符:final
通过parameter.getName()获取参数名:s
通过parameter.getParameterizedType()获取参数化类型(泛型):class java.lang.String
通过parameter.toString()获取参数的字符串描述:final java.lang.String s
通过parameter.isSynthetic()判断参数是否是合成的:false
通过parameter.isImplicit()判断参数是否是隐式的:false
通过parameter.isNamePresent()判断参数是否存在可用:true
通过parameter.isVarArgs()判断参数是否是参数列表:false

通过parameter.getModifiers()获取参数修饰符:
通过parameter.getName()获取参数名:num
通过parameter.getParameterizedType()获取参数化类型(泛型):int
通过parameter.toString()获取参数的字符串描述:int num
通过parameter.isSynthetic()判断参数是否是合成的:false
通过parameter.isImplicit()判断参数是否是隐式的:false
通过parameter.isNamePresent()判断参数是否存在可用:true
通过parameter.isVarArgs()判断参数是否是参数列表:false

java.lang.String, 
通过parameter.getModifiers()获取参数修饰符:
通过parameter.getName()获取参数名:s
通过parameter.getParameterizedType()获取参数化类型(泛型):class java.lang.String
通过parameter.toString()获取参数的字符串描述:java.lang.String s
通过parameter.isSynthetic()判断参数是否是合成的:false
通过parameter.isImplicit()判断参数是否是隐式的:false
通过parameter.isNamePresent()判断参数是否存在可用:true
通过parameter.isVarArgs()判断参数是否是参数列表:false

4、获取Method

java中get与getDeclared方法的区别,get包括自身public + 继承public的字段,方法或者自身public构造函数或者继承注解,getDeclared只包括自身所有字段,方法,构造函数,注解

package reflect;

import java.lang.reflect.Method;

public class TMethod {
    private void test1(){
        System.out.println("私有测试方法1");
    }
    private void test2(String s){
        System.out.println("私有测试方法2,参数String="+s);
    }
    public void test3(String s,int num){
        System.out.println("公有测试方法3,参数String="+s+":num="+num);
    }
    public static void main(String[] args) {
        try {
            Class clazz = Class.forName("reflect.TMethod");
              //1、得到clazz 对应的类中有哪些方法,不能获取private方法  
            Method[] methods =clazz.getMethods();  
            System.out.print("自身及继承Object类的公有方法: ");  
            for (Method method : methods){  
                System.out.print(method.getName() + ", ");  
            }  

            //2、获取所有的方法(且只获取当着类声明的方法,包括private方法)  
            Method[] methods2 = clazz.getDeclaredMethods();  
            System.out.print("\n自身所有方法(公有,私有): ");  
            for (Method method : methods2){  
                System.out.print(method.getName() + ", "); 

            }  

            //3、获取指定的方法  
            Method method = clazz.getDeclaredMethod("test2",String.class);//第一个参数是方法名,后面的是方法里的参数  
            System.out.println("\nmethod2 : " + method);  

            Method method2 = clazz.getDeclaredMethod("test3",String.class ,int.class);//第一个参数是方法名,后面的是方法里的参数  
            System.out.println("method3: " + method2);  

            //4、执行方法!  
            Object obj = clazz.newInstance();  
            method2.invoke(obj, "changwen", 22); 
            //这是本类中调用,不必设置 setAccessible(true);如果是其他类中必须设置,不然method是私有的,无法在其他类中执行
             method.setAccessible(true);
            method.invoke(obj,"dddd");
        } catch (Exception e) {
            e.printStackTrace();
        }  
    }
}

输出

自身及Object类的公有方法: main, test3, wait, wait, wait, equals, toString, hashCode, getClass, notify, notifyAll, 
自身所有方法(公有,私有): main, test1, test2, test3, 
method2 : private void reflect.TMethod.test2(java.lang.String)
method3: public void reflect.TMethod.test3(java.lang.String,int)
公有测试方法3,参数String=changwen:num=22
私有测试方法2,参数String=dddd

5、Invoke巧妙使用

从上一个例子中,我们看到可以使用meithod对象直接通过对象和参数来执行方法
下面我们定义2个方法来通过直接传入类方法名和参数以及类的Class对象或者类对象来直接运行类的方法

 package reflect;

import java.lang.reflect.Method;

public class TInvoke {
     /** 
     * 获取clazz 的methodName 方法, 该方法可能是私有方法 
     */  
    public Method getMethod(Class clazz, String methodName, Class ... parameterTypes) {  
        //注意这个循环里的内容!!! 
        for (; clazz != Object.class; clazz = clazz.getSuperclass()){  
            try {  
                return clazz.getDeclaredMethod(methodName, parameterTypes);  
            } catch (Exception e) { 
                e.printStackTrace();
            }  
        }  
        return null;  
    }  
   /** 
     * @param className  某个类的全类名 
     * @param methodName 类的一个方法的方法名,该方法也可能是私有方法 
     * @param args  调用该方法需要传入的参数 ...可变参数的意思 
     * @return 调用方法后的返回值 
     */  
    public Object invoke(String className, String methodName,Class<?>[] parameterTypes,Object ... args) {  
        Object obj = null;  
        try {  
            obj = Class.forName(className).newInstance();  
            return invoke(obj, methodName, parameterTypes,args);  

        } catch (InstantiationException e) {  
            e.printStackTrace();  
        } catch (IllegalAccessException e) {  
            e.printStackTrace();  
        } catch (ClassNotFoundException e) {  
            e.printStackTrace();  
        }  
        return invoke(null, methodName,parameterTypes, args);  
    }  
    /** 
     * @param obj  方法执行的那个对象 
     * @param methodName 类的一个方法的方法名,该方法也可能是私有方法,还可能是该方法在父类中定义的私有方法 
     * @param args  调用该方法需要传入的参数 ...可变参数的意思 
     * @return 调用方法后的返回值 
     */  
    public Object invoke(Object obj, String methodName,Class<?>[] parameterTypes, Object ... args) {  
        try {  
            //2、执行Method方法  

            Method method = getMethod(obj.getClass(), methodName,parameterTypes);  

            //通过反射执行private方法  
            method.setAccessible(true);  

            //3、返回方法的返回值  
            return method.invoke(obj,args);  

        } catch (Exception e) {  

        }  

        return null;  
    }  
    public void  testInvoke(){
        Object obj = new TMethod();

           invoke(obj, "test3",new Class<?>[] {String.class,int.class},"test3",10);  

            Object result = invoke(obj, "test2",new Class<?>[] {String.class},"test2");  
            System.out.println(result); 
    }
     public static void main(String[] args) {
         try {
             TInvoke obj = new TInvoke();  
               obj.testInvoke();
        } catch (Exception e) {
            e.printStackTrace();
        }  
    }

}

输出

公有测试方法3,参数String=test3:num=10
私有测试方法2,参数String=test2
null

6、获取Field

package reflect;

import java.lang.reflect.Field;

class Person {  
    public String name;  
    private Integer age;  

    public Person() {  
    }  

    public Person(String name, Integer age) {  
        this.name = name;  
        this.age = age;  
    }  
    public Integer getAge() {
        return age;
    }  
} 

class Student extends Person{  

} 
public class TField {

/** 
 * Field: 封装了字段的信息 
 */  
public void testField() throws  
        ClassNotFoundException, NoSuchFieldException, IllegalAccessException {  

    Class clazz = Class.forName("reflect.Person");  

    //1、获取字段  
    //1.1 获取Field的数组,私有字段也能获取  
    Field[] fields = clazz.getDeclaredFields();  
    for (Field field: fields) {  
        System.out.print(field.getName() + ", ");  
    }  

    //1.2 获取指定名字的Field(如果是私有的,见下面的4)  
    Field field = clazz.getDeclaredField("name");  
    System.out.println("\n获取指定Field名=: " + field.getName());  

    Person person = new Person("ABC", 12);  
    //2、获取指定对象的Field的值  
    Object val = field.get(person);  
    System.out.println("获取指定对象字段'name'的Field的值=: " + val);  

    //3、设置指定对象的Field的值  
    field.set(person, "changwen2");  
    System.out.println("设置指定对象字段'name'的Field的值=: " + person.name);  

    //4、若该字段是私有的,需要调用setAccessible(true)方法  
    Field field2 = clazz.getDeclaredField("age");  
    field2.setAccessible(true); 
    System.out.println("获取指定私有字段名=: " + field2.getName());  
}  


/** 
 * 一个实例: 
 * 反射获取一个继承Person2的Student类 
 * 设置字段"age"=20(该字段可能为私有,可能在其父类中) 
 */  
public void testClassField() throws ClassNotFoundException, IllegalAccessException, InstantiationException {  
    String className = "reflect.Student";  
    String fieldName = "age";  //可能为私有,可能在其父类中  
    Object val = 20;  

    //创建className 对应类的对象,并为其fieldName赋值为val  
    Class clazz = Class.forName(className);  
    Field field = null;  
    for (Class clazz2 = clazz; clazz2 != Object.class; clazz2 = clazz2.getSuperclass()){  
        try {  
            field = clazz2.getDeclaredField(fieldName);  
        } catch (Exception e) {  

        }  
    }  

    Object obj = clazz.newInstance();  
    assert field != null;  
    field.setAccessible(true);  
    field.set(obj, val);  

    Student stu = (Student) obj;  
    System.out.println("age = " + stu.getAge());  
}  

    public static void main(String[] args) {
        TField field=new TField();
        try {
            field.testField();

            System.out.println("===============================s");
            field.testClassField();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

输出

name, age, 
获取指定Field名=: name
获取指定对象字段'name'的Field的值=: ABC
设置指定对象字段'name'的Field的值=: changwen2
获取指定私有字段名=: age
===============================
age = 20

7、反射获取注解

JDK5.0 在 java.lang.reflect包下新增了 AnnotatedElement接口,该接口代表程序中可以接受注释的程序元素

  • 当一个 Annotation类型被定义为运行时Annotation后,该注释才是运行时可见,当 class文件被载入时保存在 class文件中的 Annotation才会被虚拟机读取
  • 程序可以调用AnnotationElement对象的如下方法来访问 Annotation信息
  • 获取 Annotation实例:
    • getAnnotation(Class annotationClass)
    • getDeclaredAnnotations()
    • getParameterAnnotations()

关于注解详解,请看我之前的文章 https://www.shadowwu.club/2018/10/21/java_annotation/index.html

参考

java反射之包装类和基础数据类型的坑(分享个反射工具方法) https://blog.csdn.net/qq_20641565/article/details/78797975
Java中反射机制详解 https://www.cnblogs.com/whgk/p/6122036.html
Java反射机制详解 https://www.cnblogs.com/bojuetech/p/5896551.html

点赞

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注