:::info JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。Java反射机制在框架设计中极为广泛,需要深入理解。 :::

反射基础

反射就是把java类中的各种成分映射成一个个的Java对象
例如:一个类有:成员变量、方法、构造方法、包等等信息,利用反射技术可以对一个类进行解剖,把个个组成部分映射成一个个对象。

class类

  1. Class类也是类的一种,与class关键字是不一样的。
  2. 手动编写的类被编译后会产生一个Class对象,其表示的是创建的类的类型信息,而且这个Class对象保存在同名.class的文件中(字节码文件)
  3. 每个通过关键字class标识的类,在内存中有且只有一个与之对应的Class对象来描述其类型信息,无论创建多少个实例对象,其依据的都是用一个Class对象。
  4. Class类只存私有构造函数,因此对应Class对象只能有JVM创建和加载
  5. Class类的对象作用是运行时提供或获得某个对象的类型信息,这点对于反射技术很重要。

    反射的使用

    在Java中,Class类与java.lang.reflect类库一起对反射技术进行了全力的支持。在反射包中,常用的类主要:

  6. Constructor类表示的是Class 对象所表示的类的构造方法,利用它可以在运行时动态创建对象、

  7. Field表示Class对象所表示的类的成员变量,通过它可以在运行时动态修改成员变量的属性值(包含private)、
  8. Method表示Class对象所表示的类的成员方法,通过它可以动态调用对象的方法(包含private),下面将对这几个重要类进行分别说明。

    class类对象的获取

    在类加载的时候,jvm会创建一个class对象
    class对象是可以说是反射中最常用的,获取class对象的方式的主要有三种
  • 根据类名:类名.class
  • 根据对象:对象.getClass()
  • 根据全限定类名:Class.forName(全限定类名) ```java
    1. // 获取Class对象的三种方式
    2. System.out.println("根据类名: \t" + User.class);
    3. System.out.println("根据对象: \t" + new User().getClass());
    4. System.out.println("根据全限定类名:\t" + Class.forName("com.test.User"));
    5. // 常用的方法
    6. System.out.println("获取全限定类名:\t" + userClass.getName());
    7. System.out.println("获取类名:\t" + userClass.getSimpleName());
    8. System.out.println("实例化:\t" + userClass.newInstance());
package com.test;
public class User {
    private String name = "init";
    private int age;
    public User() {}
    public User(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }
}
```java
根据类名:      class com.test.User
根据对象:      class com.test.User
根据全限定类名:    class com.test.User
获取全限定类名:    com.test.User
获取类名:    User
实例化:    User [name=init, age=0]
  • ReflectDemo.class.getClassLoader().getResource(“.”).toURI()
  • 这里的当前目录表示的是当前Demo这个类所在最外层包的上一级目录
  • ReflectDemo.class.getResource(“.”).toURI()
  • 这里的当前目录就是当前类所在的目录
    package reflect.demo;
    public class Demo {
      public static void main(String[] args){
          File dir = new File(Demo.class.getClassLoader().getResource(".").toURI());
          System.out.println(dir.getName());//CGB2202_SE(src的上一级)
          File dir1 = new File(Demo.class.getResource(".").toURI());
          System.out.println(dir1.getName());//demo
      }
    }
    
    | 方法名 | 说明 | | —- | —- | | forName() | (1)获取Class对象的一个引用,但引用的类还没有加载(该类的第一个对象没有生成)就加载了这个类。 | | Object-getClass() | 获取Class对象的一个引用,返回表示该对象的实际类型的Class引用。 | | getName() | 取全限定的类名(包括包名),即类的完整名字。 | | getSimpleName() | 获取类名(不包括包名) | | getCanonicalName() | 获取全限定的类名(包括包名) | | isInterface() | 判断Class对象是否是表示一个接口 | | getInterfaces() | 返回Class对象数组,表示Class对象所引用的类所实现的所有接口。 | | getSupercalss() | 返回Class对象,表示Class对象所引用的类所继承的直接基类。应用该方法可在运行时发现一个对象完整的继承结构。 | | newInstance() | 类对象直接提供了可以通过公开的无参构造器实例化的功能。
    使用该方法创建的类,必须带有无参的构造器。 | | newInstance(参数) | 实例化有参数的构造函数时,要传入构造器要求的实际参数, | | getPackage() | 获取当前类对象所表示的类的包,返回包名 | | getConstructor() | 返回无参类型、具有public访问权限的构造函数对象 | | getConstructor(Class<?>… parameterTypes) | 返回指定参数类型、具有public访问权限的构造函数对象 | | getFields() | 获得某个类的所有的公共(public)的字段,包括继承自父类的所有公共字段。 类似的还有getMethods和getConstructors。 | | getDeclaredFields | 获得某个类的自己声明的字段,即包括public、private和proteced,默认但是不包括父类声明的任何字段。类似的还有getDeclaredMethods和getDeclaredConstructors。 |

getName、getCanonicalName与getSimpleName的区别

  • getSimpleName:只获取类名
  • getName:类的全限定名,jvm中Class的表示,可以用于动态加载Class对象,例如Class.forName。
  • getCanonicalName:返回更容易理解的表示,主要用于输出(toString)或log打印,大多数情况下和getName一样,但是在内部类、数组等类型的表示形式就不同了

    package com.cry;
    public class Test {
      private  class inner{
      }
      public static void main(String[] args) throws ClassNotFoundException {
          //普通类
          System.out.println(Test.class.getSimpleName()); //Test
          System.out.println(Test.class.getName()); //com.cry.Test
          System.out.println(Test.class.getCanonicalName()); //com.cry.Test
          //内部类
          System.out.println(inner.class.getSimpleName()); //inner
          System.out.println(inner.class.getName()); //com.cry.Test$inner
          System.out.println(inner.class.getCanonicalName()); //com.cry.Test.inner
          //数组
          System.out.println(args.getClass().getSimpleName()); //String[]
          System.out.println(args.getClass().getName()); //[Ljava.lang.String;
          System.out.println(args.getClass().getCanonicalName()); //java.lang.String[]
          //我们不能用getCanonicalName去加载类对象,必须用getName
          //Class.forName(inner.class.getCanonicalName()); 报错
          Class.forName(inner.class.getName());
      }
    }
    

    Constructor类及其用法

    :::info Constructor类存在于反射包(java.lang.reflect)中,反映的是Class 对象所表示的类的构造方法。 :::

  • 获取Constructor对象是通过Class类中的方法获取的,Class类与Constructor相关的主要方法如下: | 方法返回值 | 方法名称 | 方法说明 | | —- | —- | —- | | static Class<?> | forName(String className) | 返回与带有给定字符串名的类或接口相关联的 Class 对象。 | | Constructor
    | getConstructor(Class<?>… parameterTypes) | 返回指定参数类型、具有public访问权限的构造函数对象 | | Constructor<?>[] | getConstructors() | 返回所有具有public访问权限的构造函数的Constructor对象数组 | | Constructor | getDeclaredConstructor(Class<?>… parameterTypes) | 返回指定参数类型、所有声明的(包括private)构造函数对象 | | Constructor<?>[] | getDeclaredConstructor() | 返回所有声明的(包括private)构造函数对象 | | T | newInstance() | 调用无参构造器创建此 Class 对象所表示的类的一个新实例。 |

  • Constructor对象的使用: ```java public class ConstructionTest implements Serializable { public static void main(String[] args) throws Exception {

      Class<?> clazz = null;
    
      //获取Class对象的引用
      clazz = Class.forName("com.example.javabase.User");
    
      //第一种方法,实例化默认构造方法,User必须无参构造函数,否则将抛异常
      User user = (User) clazz.newInstance();
      user.setAge(20);
      user.setName("Jack");
      System.out.println(user);
    
      System.out.println("--------------------------------------------");
    
      //获取带String参数的public构造函数
      Constructor cs1 =clazz.getConstructor(String.class);
      //创建User
      User user1= (User) cs1.newInstance("hiway");
      user1.setAge(22);
      System.out.println("user1:"+user1.toString());
    
      System.out.println("--------------------------------------------");
    
      //取得指定带int和String参数构造函数,该方法是私有构造private
      Constructor cs2=clazz.getDeclaredConstructor(int.class,String.class);
      //由于是private必须设置可访问
      cs2.setAccessible(true);
      //创建user对象
      User user2= (User) cs2.newInstance(25,"hiway2");
      System.out.println("user2:"+user2.toString());
    
      System.out.println("--------------------------------------------");
    
      //获取所有构造包含private
      Constructor<?> cons[] = clazz.getDeclaredConstructors();
      // 查看每个构造方法需要的参数
      for (int i = 0; i < cons.length; i++) {
          //获取构造函数参数类型
          Class<?> clazzs[] = cons[i].getParameterTypes();
          System.out.println("构造函数["+i+"]:"+cons[i].toString() );
          System.out.print("参数类型["+i+"]:(");
          for (int j = 0; j < clazzs.length; j++) {
              if (j == clazzs.length - 1)
                  System.out.print(clazzs[j].getName());
              else
                  System.out.print(clazzs[j].getName() + ",");
          }
          System.out.println(")");
      }
    

    } }

class User { private int age; private String name; public User() { super(); } public User(String name) { super(); this.name = name; }

/**
 * 私有构造
 * @param age
 * @param name
 */
private User(int age, String name) {
    super();
    this.age = age;
    this.name = name;
}

public int getAge() {
    return age;
}

public void setAge(int age) {
    this.age = age;
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

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

}


- 输出结果
```java
/* output 
User{age=20, name='Jack'}
--------------------------------------------
user1:User{age=22, name='hiway'}
--------------------------------------------
user2:User{age=25, name='hiway2'}
--------------------------------------------
构造函数[0]:private com.example.javabase.User(int,java.lang.String)
参数类型[0]:(int,java.lang.String)
构造函数[1]:public com.example.javabase.User(java.lang.String)
参数类型[1]:(java.lang.String)
构造函数[2]:public com.example.javabase.User()
参数类型[2]:()
  • 关于Constructor类本身一些常用方法如下(仅部分,其他可查API) | 方法返回值 | 方法名称 | 方法说明 | | —- | —- | —- | | Class | getDeclaringClass() | 返回 Class 对象,该对象表示声明由此 Constructor 对象表示的构造方法的类,其实就是返回真实类型(不包含参数) | | Type[] | getGenericParameterTypes() | 按照声明顺序返回一组 Type 对象,返回的就是 Constructor对象构造函数的形参类型。 | | String | getName() | 以字符串形式返回此构造方法的名称。 | | Class<?>[] | getParameterTypes() | 按照声明顺序返回一组 Class 对象,即返回Constructor 对象所表示构造方法的形参类型 | | T | newInstance(Object… initargs) | 使用此 Constructor对象表示的构造函数来创建新实例 | | String | toGenericString() | 返回描述此 Constructor 的字符串,其中包括类型参数。 |

  • 代码演示如下: ```java Constructor cs3 = clazz.getDeclaredConstructor(int.class,String.class); System.out.println(“——-getDeclaringClass——-“); Class uclazz=cs3.getDeclaringClass(); //Constructor对象表示的构造方法的类 System.out.println(“构造方法的类:”+uclazz.getName());

System.out.println(“——-getGenericParameterTypes——-“); //对象表示此 Constructor 对象所表示的方法的形参类型 Type[] tps=cs3.getGenericParameterTypes(); for (Type tp:tps) { System.out.println(“参数名称tp:”+tp); } System.out.println(“——-getParameterTypes——-“); //获取构造函数参数类型 Class<?> clazzs[] = cs3.getParameterTypes(); for (Class claz:clazzs) { System.out.println(“参数名称:”+claz.getName()); } System.out.println(“——-getName——-“); //以字符串形式返回此构造方法的名称 System.out.println(“getName:”+cs3.getName());

System.out.println(“——-getoGenericString——-“); //返回描述此 Constructor 的字符串,其中包括类型参数。 System.out.println(“getoGenericString():”+cs3.toGenericString());


- 输出结果
```java
-----getDeclaringClass-----
构造方法的类:com.example.javabase.User
-----getGenericParameterTypes-----
参数名称tp:int
参数名称tp:class java.lang.String
-----getParameterTypes-----
参数名称:int
参数名称:java.lang.String
-----getName-----
getName:com.example.javabase.User
-----getoGenericString-----
getoGenericString():private com.example.javabase.User(int,java.lang.String)

Field类及其用法

:::info Field 提供有关类或接口的单个字段的信息,以及对它的动态访问权限。反射的字段可能是一个类(静态)字段或实例字段。 :::

  • 通过Class类的提供的方法来获取代表字段信息的Field对象,Class类与Field对象相关方法如下: | 方法返回值 | 方法名称 | 方法说明 | | —- | —- | —- | | Field | getDeclaredField(String name) | 获取指定name名称的(包含private修饰的)字段,不包括继承的字段 | | Field[] | getDeclaredFields() | 获取Class对象所表示的类或接口的所有(包含private修饰的)字段,不包括继承的字段 | | Field | getField(String name) | 获取指定name名称、具有public修饰的字段,包含继承字段 | | Field[] | getFields() | 获取修饰符为public的字段,包含继承字段 |

  • 下面的代码演示了上述方法的使用过程 ```java public class ReflectField {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {

      Class<?> clazz = Class.forName("reflect.Student");
      //获取指定字段名称的Field类,注意字段修饰符必须为public而且存在该字段,
      // 否则抛NoSuchFieldException
      Field field = clazz.getField("age");
      System.out.println("field:"+field);
    
      //获取所有修饰符为public的字段,包含父类字段,注意修饰符为public才会获取
      Field fields[] = clazz.getFields();
      for (Field f:fields) {
          System.out.println("f:"+f.getDeclaringClass());
      }
    
      System.out.println("================getDeclaredFields====================");
      //获取当前类所字段(包含private字段),注意不包含父类的字段
      Field fields2[] = clazz.getDeclaredFields();
      for (Field f:fields2) {
          System.out.println("f2:"+f.getDeclaringClass());
      }
      //获取指定字段名称的Field类,可以是任意修饰符的自动,注意不包含父类的字段
      Field field2 = clazz.getDeclaredField("desc");
      System.out.println("field2:"+field2);
    

    } /**

    输出结果: 
    

    field:public int reflect.Person.age f:public java.lang.String reflect.Student.desc f:public int reflect.Person.age f:public java.lang.String reflect.Person.name

    ================getDeclaredFields==================== f2:public java.lang.String reflect.Student.desc f2:private int reflect.Student.score field2:public java.lang.String reflect.Student.desc */ }

class Person{ public int age; public String name; //省略set和get方法 }

class Student extends Person{ public String desc; private int score; //省略set和get方法 }


- 上述方法需要注意的是,如果我们不期望获取其父类的字段,则需使用Class类的getDeclaredField/getDeclaredFields方法来获取字段即可,倘若需要连带获取到父类的字段,那么请使用Class类的getField/getFields,但是也只能获取到public修饰的的字段,无法获取父类的私有字段。下面将通过Field类本身的方法对指定类属性赋值,代码演示如下
```java
//获取Class对象引用
Class<?> clazz = Class.forName("reflect.Student");

Student st= (Student) clazz.newInstance();
//获取父类public字段并赋值
Field ageField = clazz.getField("age");
ageField.set(st,18);
Field nameField = clazz.getField("name");
nameField.set(st,"Lily");

//只获取当前类的字段,不获取父类的字段
Field descField = clazz.getDeclaredField("desc");
descField.set(st,"I am student");
Field scoreField = clazz.getDeclaredField("score");
//设置可访问,score是private的
scoreField.setAccessible(true);
scoreField.set(st,88);
System.out.println(st.toString());

//输出结果:Student{age=18, name='Lily ,desc='I am student', score=88} 

//获取字段值
System.out.println(scoreField.get(st));
// 88
  • 其中的set(Object obj, Object value)方法是Field类本身的方法,用于设置字段的值,而get(Object obj)则是获取字段的值,当然关于Field类还有其他常用的方法如下: | 方法返回值 | 方法名称 | 方法说明 | | —- | —- | —- | | void | set(Object obj, Object value) | 将指定对象变量上此 Field 对象表示的字段设置为指定的新值。 | | Object | get(Object obj) | 返回指定对象上此 Field 表示的字段的值 | | Class<?> | getType() | 返回一个 Class 对象,它标识了此Field 对象所表示字段的声明类型。 | | boolean | isEnumConstant() | 如果此字段表示枚举类型的元素则返回 true;否则返回 false | | String | toGenericString() | 返回一个描述此 Field(包括其一般类型)的字符串 | | String | getName() | 返回此 Field 对象表示的字段的名称 | | Class<?> | getDeclaringClass() | 返回表示类或接口的 Class 对象,该类或接口声明由此 Field 对象表示的字段 | | void | setAccessible(boolean flag) | 将此对象的 accessible 标志设置为指示的布尔值,即设置其可访问性 |

  • 上述方法可能是较为常用的,事实上在设置值的方法上,Field类还提供了专门针对基本数据类型的方法,如setInt()/getInt()、setBoolean()/getBoolean、setChar()/getChar()等等方法,这里就不全部列出了,需要时查API文档即可。需要特别注意的是被final关键字修饰的Field字段是安全的,在运行时可以接收任何修改,但最终其实际值是不会发生改变的。

    Method类及其用法

    :::info Method 提供关于类或接口上单独某个方法(以及如何访问该方法)的信息,所反映的方法可能是类方法或实例方法(包括抽象方法)。 :::

  1. 下面是Class类获取Method对象相关的方法: | 方法返回值 | 方法名称 | 方法说明 | | —- | —- | —- | | Method[] | getMethods() | 获取当前类对象所表示的类中定义的所有公开方法,包含从超类继承下来的方法 | | Method[] | getDeclaredMethods() | 返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的所有方法,不包括父类的 | | Method | getMethod(String name, Class<?>… parameterTypes) | 返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。(父类里面的也会有)
    Class<?>… parameterTypes,表示需要的参数,没有可不传 | | Method | getDeclaredMethod(String name, Class<?>… parameterTypes) | 返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。
    Class<?>… parameterTypes,表示需要的参数,没有可不传 | | void | method.setAccessible(true) | 强行打开method指代的方法的访问权限 | | Object | invoke(Object obj) | 类似于obj.方法名 |

同样通过案例演示上述方法:

import java.lang.reflect.Method;

public class ReflectMethod  {


    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {

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

        //根据参数获取public的Method,包含继承自父类的方法
        Method method = clazz.getMethod("draw",int.class,String.class);

        System.out.println("method:"+method);

        //获取所有public的方法:
        Method[] methods =clazz.getMethods();
        for (Method m:methods){
            System.out.println("m::"+m);
        }

        System.out.println("=========================================");

        //获取当前类的方法包含private,该方法无法获取继承自父类的method
        Method method1 = clazz.getDeclaredMethod("drawCircle");
        System.out.println("method1::"+method1);
        //获取当前类的所有方法包含private,该方法无法获取继承自父类的method
        Method[] methods1=clazz.getDeclaredMethods();
        for (Method m:methods1){
            System.out.println("m1::"+m);
        }
    }
}

class Shape {
    public void draw(){
        System.out.println("draw");
    }

    public void draw(int count , String name){
        System.out.println("draw "+ name +",count="+count);
    }

}
class Circle extends Shape{

    private void drawCircle(){
        System.out.println("drawCircle");
    }
    public int getAllCount(){
        return 100;
    }
}

输出结果:

method:public void reflect.Shape.draw(int,java.lang.String)

m::public int reflect.Circle.getAllCount()
m::public void reflect.Shape.draw()
m::public void reflect.Shape.draw(int,java.lang.String)
m::public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
m::public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
m::public final void java.lang.Object.wait() throws java.lang.InterruptedException
m::public boolean java.lang.Object.equals(java.lang.Object)
m::public java.lang.String java.lang.Object.toString()
m::public native int java.lang.Object.hashCode()
m::public final native java.lang.Class java.lang.Object.getClass()
m::public final native void java.lang.Object.notify()
m::public final native void java.lang.Object.notifyAll()

=========================================
method1::private void reflect.Circle.drawCircle()

m1::public int reflect.Circle.getAllCount()
m1::private void reflect.Circle.drawCircle()
  • 在通过getMethods方法获取Method对象时,会把父类的方法也获取到,如上的输出结果,把Object类的方法都打印出来了。而getDeclaredMethod/getDeclaredMethods方法都只能获取当前类的方法。我们在使用时根据情况选择即可。下面将演示通过Method对象调用指定类的方法: ```java Class clazz = Class.forName(“reflect.Circle”); //创建对象 Circle circle = (Circle) clazz.newInstance();

//获取指定参数的方法对象Method Method method = clazz.getMethod(“draw”,int.class,String.class);

//通过Method对象的invoke(Object obj,Object… args)方法调用 method.invoke(circle,15,”圈圈”);

//对私有无参方法的操作 Method method1 = clazz.getDeclaredMethod(“drawCircle”); //修改私有方法的访问标识 method1.setAccessible(true); method1.invoke(circle);

//对有返回值得方法操作 Method method2 =clazz.getDeclaredMethod(“getAllCount”); Integer count = (Integer) method2.invoke(circle); System.out.println(“count:”+count);


- 输出结果
```java
draw 圈圈,count=15
drawCircle
count:100