Java基础----反射 (一) 反射机制、Class类、类的加载和类加载器ClassLoader、反射的使用
阅读原文时间:2021年04月20日阅读:1

1. 反射机制概述

Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,我们形象的称之为:反射

注:
动态语言:在运行时代码可以根据某些条件改变自身结构。 主要动态语言有Object-C、C#、JavaScript、PHP、Python、Erlang;
静态语言:运行时结构不可变的语言。如Java、C、 C++。

Java反射机制主要提供如下功能:

  • 在运行时判断任意一个对象所属的类
  • 在运行时构造任意一个类的对象
  • 在运行时判断任意一个类所具有的成员变量和方法
  • 在运行时获取泛型信息
  • 在运行时调用任意一个对象的成员变量和方法
  • 在运行时处理注解
  • 生成动态代理

反射相关的主要API:

  • java.lang.Class:代表一个类
  • java.lang.reflect.Method:代表类的方法
  • java.lang.reflect.Field:代表类的成员变量
  • java.lang.reflect.Constructor:代表类的构造器

2. Class 类

对象照镜子后可以得到的信息:某个类的属性、方法和构造器、某个类到底实现了哪些接口。对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含了特定某个结构(class/interface/enum/annotation/primitive type/void/[])的有关信息。

在Object类中定义了**public final Class getClass()**方法,方法返回值的类型是一个Class类。
Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的Class对象。

  • Class本身也是一个类
  • Class 对象只能由系统建立对象
  • 一个加载的类在 JVM 中只会有一个Class实例
  • 一个Class对象对应的是一个加载到JVM中的一个.class文件
  • 每个类的实例都会记得自己是由哪个 Class 实例所生成
  • 通过Class可以完整地得到一个类中的所有被加载的结构

Class类常用方法:

获取Class类实例的方法:(四种,重要了解前三种)

/*
    关于java.lang.Class类的理解
    1.类的加载过程:
    程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾)。
    接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件
    加载到内存中。此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此
    运行时类,就作为Class的一个实例。

    2.换句话说,Class的实例就对应着一个运行时类。
    3.加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式
    来获取此运行时类。

     */
    //获取Class的实例的方式
    @Test
    public void test3() throws ClassNotFoundException {
        //方式一:调用运行时类的属性:.class
        //前提:已知具体的类,通过类的class属性获取,该方法最为安全可靠, 程序性能最高 
        Class<Person> clazz = Person.class;
        System.out.println(clazz);

        //方式二:通过运行时类的对象,调用getClass()
        //前提:已知某个类的实例,调用该实例的getClass()方法获取Class对象 
        Person p1 = new Person();
        Class<? extends Person> class2 = p1.getClass();
        System.out.println(class2);

        //方式三:调用Class的静态方法:forName(String classPath)
        //已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException 
        Class<?> class3 = Class.forName("reflection_mechanism.Person");
        System.out.println(class3);

        //判断是不是同一个类
        System.out.println(clazz == class2);
        System.out.println(class2 == class3);

        //方式四:使用类的加载器:ClassLoader  (了解)
        ClassLoader classLoader = ReflectionTest.class.getClassLoader();
        Class<?> clazz4 = classLoader.loadClass("reflection_mechanism.Person");
        System.out.println(clazz4);
        System.out.println(clazz4 == clazz);
    }

注意,只有以下类型才有Class类对象:
(1)class: 外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
(2)interface:接口
(3)[]:数组
(4)enum:枚举
(5)annotation:注解@interface
(6)primitive type:基本数据类型
(7)void

3. 类的加载和类加载器ClassLoader

类的加载过程:
当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过 三个步骤来对该类进行初始化。
当类主动引用时,一定会发生类的初始化。
主动引用包括: new一个类的对象;调用类的静态成员(除了final常量)和静态方法 ;使用java.lang.reflect包的方法对类进行反射调用 ;当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类 。

类的被动引用,不会发生类的初始化。
被动引用包括:当访问一个静态域时,只有真正声明这个域的类才会被初始化,当通过子类引用父类的静态变量,不会导致子类初始化;通过数组定义类引用,不会触发此类的初始化;引用常量不会触发此类的初始(常量在链接阶段就存入调用类的常 量池中了)。

类加载器的作用:

  1. 类加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。
  2. 类缓存:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象。

类加载器的分类:

获取类加载器方法:

    @Test
    public void test1(){
        //对于自定义类,使用系统类加载器进行加载
        ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
        System.out.println(classLoader);

        //调用系统类加载器的getParent():获取扩展类加载器(系统类加载器的父类加载器)
        ClassLoader classLoader1 = classLoader.getParent();
        System.out.println(classLoader1);

        //调用扩展类加载器的getParent():无法获取引导类加载器(扩展类加载器的父类加载器)
        //引导类加载器主要负责加载java的核心类库,无法加载自定义类的。
        ClassLoader classLoader2 = classLoader1.getParent();
        System.out.println(classLoader2);

        ClassLoader classLoader3 = String.class.getClassLoader();
        System.out.println(classLoader3);

    }

类加载器的一个主要方法:
getResourceAsStream(String str): 获取类路 径下的指定文件的输入流

调用类加载器读取文件:

    @Test
    public void test2() throws Exception {

        Properties pros =  new Properties();
        //此时的文件默认在当前的module下。
        //读取配置文件的方式一:
//        FileInputStream fis = new FileInputStream("jdbc.properties");
//        FileInputStream fis = new FileInputStream("src\\jdbc1.properties");
//        pros.load(fis);

        //读取配置文件的方式二:使用ClassLoader
        //配置文件默认识别为:当前module的src下
        ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
        InputStream is = classLoader.getResourceAsStream("jdbc1.properties");
        pros.load(is);


        String user = pros.getProperty("user");
        String password = pros.getProperty("password");
        System.out.println("user = " + user + ",password = " + password);

    }

4. 通过反射创建对应的运行时类的对象

newInstance(): 调用此方法,创建对应的运行时类的对象。内部调用了运行时类的空参的构造器。

    要想此方法正常的创建运行时类的对象,要求:
    1.运行时类必须提供空参的构造器
    2.空参的构造器的访问权限得够。通常,设置为public。

    在javabean中要求提供一个public的空参构造器。原因:
    1.便于通过反射,创建运行时类的对象
    2.便于子类继承此运行时类时,默认调用super()时,保证父类有此构造器


        Class<Person> clazz = Person.class;
//        Person obj = clazz.newInstance(); //方法过时了
        Person obj = clazz.getDeclaredConstructor().newInstance();
        System.out.println(obj);

注意: clazz.newInstance() 方法在Java9之后过时了,使用clazz.getDeclaredConstructor().newInstance() 代替。

5. 获取运行时类的完整结构

运行时类的完整结构包括:属性Field、方法Method、构造器Constructor、继承的父类Superclass、实现的全部接口Interface、注解Annotation.

1. 实现的全部接口
public Class[] getInterfaces() :确定此对象所表示的类或接口实现的接口。

2. 继承的父类
public Class getSuperclass() :返回表示此 Class 所表示的实体(类、接口、基本类型)的父类的Class。

3.全部的构造器
public Constructor[] getConstructors() :返回此 Class 对象所表示的类的所有public构造方法
public Constructor[] getDeclaredConstructors() :返回此 Class 对象表示的类声明的所有构造方法。

Constructor类中
取得修饰符: public int getModifiers();
取得方法名称: public String getName();
取得参数的类型:public Class[] getParameterTypes();

4.全部的方法
public Method[] getDeclaredMethods() :返回此Class对象所表示的类或接口的全部方法
public Method[] getMethods() :返回此Class对象所表示的类或接口的public的方法

Method类中:
public Class getReturnType():取得全部的返回值
public Class[] getParameterTypes():取得全部的参数
public int getModifiers():取得修饰符
public Class[] getExceptionTypes():取得异常信息

5.全部的Field
public Field[] getFields() :返回此Class对象所表示的类或接口的public的Field。
public Field[] getDeclaredFields() :返回此Class对象所表示的类或接口的全部Field。

Field方法中:
public int getModifiers(): 以整数形式返回此Field的修饰符
public Class getType(): 得到Field的属性类型
public String getName() 返回Field的名称。

6. Annotation相关
get Annotation(Class annotationClass)
getDeclaredAnnotations()

7.泛型相关
获取父类泛型类型:Type getGenericSuperclass()
泛型类型:ParameterizedType
获取实际的泛型类型参数数组:getActualTypeArguments()

8.类所在的包
Package getPackage()

具体操作代码:

/*
获取当前运行时类的属性结构
*/
public class FieldTest {

    @Test
    public void test1(){

        Class clazz = Person.class;

        //获取属性结构
        //getFields():获取当前运行时类及其父类中声明为public访问权限的属性
        Field[] fields = clazz.getFields();
        for (Field f: fields){
            System.out.println(f);
        }
        System.out.println();

        //getDeclaredFields():获取当前运行时类中声明的所有属性。(不包含父类中声明的属性)
        Field[] declaredFields = clazz.getDeclaredFields();
        for(Field f : declaredFields){
            System.out.println(f);
        }
    }

    //权限修饰符  数据类型 变量名
    @Test
    public void test2(){
        Class<Person> clazz = Person.class;
        Field[] declaredFields = clazz.getDeclaredFields();
        for(Field f : declaredFields){
            //1.权限修饰符
            int modifier = f.getModifiers();
            System.out.print(Modifier.toString(modifier) + "\t");

            //2.数据类型
            Class<?> type = f.getType();
            System.out.print(type.getName() + "\t");

            //3.变量名
            String fName = f.getName();
            System.out.print(fName);

            System.out.println();
        }
    }
}


/*
获取运行时类的方法结构
*/
public class MethodTest {

    @Test
    public void test1(){

        Class clazz = Person.class;

        //getMethods():获取当前运行时类及其所有父类中声明为public权限的方法
        Method[] methods = clazz.getMethods();
        for(Method m : methods){
            System.out.println(m);
        }
        System.out.println();
        //getDeclaredMethods():获取当前运行时类中声明的所有方法。(不包含父类中声明的方法)
        Method[] declaredMethods = clazz.getDeclaredMethods();
        for(Method m : declaredMethods){
            System.out.println(m);
        }
    }

    /*
    @Xxxx
    权限修饰符  返回值类型  方法名(参数类型1 形参名1,...) throws XxxException{}
     */
    @Test
    public void test2(){
        Class clazz = Person.class;
        Method[] declaredMethods = clazz.getDeclaredMethods();
        for(Method m : declaredMethods){
            //1.获取方法声明的注解
            Annotation[] annos = m.getAnnotations();
            for (Annotation anno : annos) {
                System.out.println(anno);
            }

            //2.权限修饰符
            System.out.print(Modifier.toString(m.getModifiers()) + "\t");

            //3.返回值类型
            System.out.print(m.getReturnType().getName() + "\t");

            //4.方法名
            System.out.print(m.getName());
            System.out.print("(");
            //5.形参列表
            Class[] parameterTypes = m.getParameterTypes();
            if(!(parameterTypes == null && parameterTypes.length == 0)){
                for(int i = 0;i < parameterTypes.length;i++){

                    if(i == parameterTypes.length - 1){
                        System.out.print(parameterTypes[i].getName() + " args_" + i);
                        break;
                    }

                    System.out.print(parameterTypes[i].getName() + " args_" + i + ",");
                }
            }

            System.out.print(")");

            //6.抛出的异常
            Class[] exceptionTypes = m.getExceptionTypes();
            if(exceptionTypes.length > 0){
                System.out.print("throws ");
                for(int i = 0;i < exceptionTypes.length;i++){
                    if(i == exceptionTypes.length - 1){
                        System.out.print(exceptionTypes[i].getName());
                        break;
                    }

                    System.out.print(exceptionTypes[i].getName() + ",");
                }
            }


            System.out.println();
        }

    }
}


public class OtherTest{

    /*
    获取构造器结构

     */
    @Test
    public void test1(){

        Class clazz = Person.class;
        //getConstructors():获取当前运行时类中声明为public的构造器
        Constructor[] constructors = clazz.getConstructors();
        for(Constructor c : constructors){
            System.out.println(c);
        }

        System.out.println();
        //getDeclaredConstructors():获取当前运行时类中声明的所有的构造器
        Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
        for(Constructor c : declaredConstructors){
            System.out.println(c);
        }

    }

    /*
    获取运行时类的父类

     */
    @Test
    public void test2(){
        Class clazz = Person.class;

        Class superclass = clazz.getSuperclass();
        System.out.println(superclass);
    }

    /*
    获取运行时类的带泛型的父类

     */
    @Test
    public void test3(){
        Class clazz = Person.class;

        Type genericSuperclass = clazz.getGenericSuperclass();
        System.out.println(genericSuperclass);
    }

    /*
    获取运行时类的带泛型的父类的泛型


    代码:逻辑性代码  vs 功能性代码
     */
    @Test
    public void test4(){
        Class clazz = Person.class;

        Type genericSuperclass = clazz.getGenericSuperclass();
        ParameterizedType paramType = (ParameterizedType) genericSuperclass;
        //获取泛型类型
        Type[] actualTypeArguments = paramType.getActualTypeArguments();
//        System.out.println(actualTypeArguments[0].getTypeName());
        System.out.println(((Class)actualTypeArguments[0]).getName());
    }

    /*
    获取运行时类实现的接口
     */
    @Test
    public void test5(){
        Class clazz = Person.class;

        Class[] interfaces = clazz.getInterfaces();
        for(Class c : interfaces){
            System.out.println(c);
        }

        System.out.println();
        //获取运行时类的父类实现的接口
        Class[] interfaces1 = clazz.getSuperclass().getInterfaces();
        for(Class c : interfaces1){
            System.out.println(c);
        }

    }
    /*
        获取运行时类所在的包

     */
    @Test
    public void test6(){
        Class clazz = Person.class;

        Package pack = clazz.getPackage();
        System.out.println(pack);
    }

    /*
        获取运行时类声明的注解

     */
    @Test
    public void test7(){
        Class clazz = Person.class;

        Annotation[] annotations = clazz.getAnnotations();
        for(Annotation annos : annotations){
            System.out.println(annos);
        }
    }

}

6. 调用运行时类的指定结构

1.调用指定方法
通过反射,调用类中的方法,通过Method类完成。步骤:
1.通过Class类的getMethod(String name,Class…parameterTypes) 方法取得一个Method对象,并设置此方法操作时所需要的参数类型。
2.之后使用Object invoke(Object obj, Object[] args) 进行调用,并向方法中传递要设置的obj对象的参数信息。


invoke方法说明:
1.Object 对应原方法的返回值,若原方法无返回值,此时返回null
2.若原方法若为静态方法,此时形参Object obj可为null
3.若原方法形参列表为空,则Object[] args为null
4.若原方法声明为private,则需要在调用此invoke()方法前,显式调用方法对象的setAccessible(true)方法,将可访问private的方法。

2.调用指定属性
在反射机制中,可以直接通过Field类操作类中的属性,通过Field类提供的set()和get()方法就可以完成设置和取得属性内容的操作。
public Field getField(String name): 返回此Class对象表示的类或接口的指定的 public的Field。
public Field getDeclaredField(String name): 返回此Class对象表示的类或接口的 指定的Field。

在Field中:
public Object get(Object obj): 取得指定对象obj上此Field的属性内容
public void set(Object obj,Object value): 设置指定对象obj上此Field的属性内容

setAccessible方法的使用:

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

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

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

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

    /**

    • 调用运行时类中指定的结构:属性、方法、构造器
      */
      public class InvokeTest {

      /*
      不需要掌握
      */
      @Test
      public void testField() throws Exception {
      Class clazz = Person.class;

      //创建运行时类的对象

      // Person p = (Person) clazz.newInstance();
      Person p = (Person) clazz.getConstructor().newInstance();

      //获取指定的属性:要求运行时类中属性声明为public
      //通常不采用此方法
      Field id = clazz.getField("id");
      
      /*
      设置当前属性的值
      set():参数1:指明设置哪个对象的属性   参数2:将此属性值设置为多少
       */
      
      id.set(p,1001);
      
      /*
      获取当前属性的值
      get():参数1:获取哪个对象的当前属性值
       */
      int pId = (int) id.get(p);
      System.out.println(pId);

      }
      /*
      如何操作运行时类中的指定的属性 -- 需要掌握
      */
      @Test
      public void testField1() throws Exception {
      Class clazz = Person.class;

      //创建运行时类的对象

      // Person p = (Person) clazz.newInstance();
      Person p = (Person) clazz.getConstructor().newInstance();

      //1. getDeclaredField(String fieldName):获取运行时类中指定变量名的属性
      Field name = clazz.getDeclaredField("name");
      
      //2.保证当前属性是可访问的
      name.setAccessible(true);
      //3.获取、设置指定对象的此属性值
      name.set(p,"Tom");
      
      System.out.println(name.get(p));

      }

      /*
      如何操作运行时类中的指定的方法 -- 需要掌握
      */
      @Test
      public void testMethod() throws Exception {

    // Class clazz = Person.class;
    Class clazz = Class.forName("reflection_get_structure.Person");
    //创建运行时类的对象
    // Person p = (Person) clazz.newInstance();
    Person p = (Person) clazz.getConstructor().newInstance();

        /*
        1.获取指定的某个方法
        getDeclaredMethod():参数1 :指明获取的方法的名称  参数2:指明获取的方法的形参列表
         */
        Method show = clazz.getDeclaredMethod("show", String.class);
        //2.保证当前方法是可访问的
        show.setAccessible(true);
    /*
    3. 调用方法的invoke():参数1:方法的调用者  参数2:给方法形参赋值的实参
    invoke()的返回值即为对应类中调用的方法的返回值。
     */
    Object returnValue = show.invoke(p,"CHN"); //String nation = p.show("CHN");
    System.out.println(returnValue);
    
    System.out.println("*************如何调用静态方法*****************");
    
    // private static void showDesc()
    
    Method showDesc = clazz.getDeclaredMethod("showDesc");
    showDesc.setAccessible(true);
    //如果调用的运行时类中的方法没有返回值,则此invoke()返回null</code></pre>

    // Object returnVal = showDesc.invoke(null); //这种写法也行
    Object returnVal = showDesc.invoke(Person.class);
    System.out.println(returnVal);//null

    }
    
    /*
    如何调用运行时类中的指定的构造器
     */
    @Test
    public void testConstructor() throws Exception {
        Class clazz = Person.class;
    //private Person(String name)
    /*
    1.获取指定的构造器
    getDeclaredConstructor():参数:指明构造器的参数列表
     */
    
    Constructor constructor = clazz.getDeclaredConstructor(String.class);
    
    //2.保证此构造器是可访问的
    constructor.setAccessible(true);
    
    //3.调用此构造器创建运行时类的对象
    Person per = (Person) constructor.newInstance("Tom");
    System.out.println(per);
    }

    }