Java反射

Posted littleskinny

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java反射相关的知识,希望对你有一定的参考价值。

Java反射

1、动态语言和静态语言

  • 动态语言

    在运行时可以改变其结构的语言,例如新的函数、对象、已有的函数可以被删除或是其他结构上的变化。

  • 静态语言

    运行时不可改变结构的语言

  • Java不是动态语言,但Java可以被称为“准动态语言”,Java有一定的动态性,我们可以利用反射机制获得类似动态语言的特性

2、反射概述

  • Java Reflection

  • 反射机制允许程序在执行期间借助于Reflection API取得任何类的内部信息(类名、类的接口、类的方法、类的属性等等),并能直接操作任何对象的内部属性及方法。

  • 加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个class对象),这个对象就包含了完整的类的结构信息,我们可以通过这个对象看到类的结构

    • 正常方式:

      引入需要的包类名称 ---> new实例化 ---> 取得实例化对象

    • 反射:

      实例化对象 ---> getClass()方法 ---> 取得完整的包类名称

3、创建Class类的方式

  • 方式一:通过getClass()获取

    Class aClass = student.getClass();
  • 方式二:通过forName获取

    Class bClass = Class.forName("com.hmx.pojo.Student");
  • 方式三:通过类名.class获取

    Class cClass = Student.class;
  • 方式四:通过子类的Class获取

    Class personClass = student.getClass().getSuperclass();
  • 方式五:基本内置类型的包装类通过Type属性可以获取Class类

    Class typeClass = Integer.TYPE;

4、所有类型的Class对象

  • Class

    Class c1 = Class.class;
    //输出结果:class java.lang.Class
    System.out.println(c1);
  • Class c2 = Object.class;
    //输出结果:class java.lang.Object
    System.out.println(c2);
  • 接口

    Class c3 = Comparable.class;
    //输出结果:interface java.lang.Comparable
    System.out.println(c3);
  • 注解

    Class c4 = Override.class;
    //输出结果:interface java.lang.Override
    System.out.println(c4);
  • 基本数据类型

    Class c5 = Integer.class;
    //输出结果:class java.lang.Integer
    System.out.println(c5);
  • 一维数组

    Class c6 = String[].class;
    //输出结果:class [Ljava.lang.String;
    System.out.println(c6);
  • 二维数组

    Class c7 = String[][].class;
    //输出结果:class [[Ljava.lang.String;
    System.out.println(c7);
  • 枚举

    Class c8 = ElementType.class;
    //输出结果:class java.lang.annotation.ElementType
    System.out.println(c8);
  • void

    Class c9 = void.class;
    //输出结果:void
    System.out.println(c9);

5、类加载器

  • 类加载器的作用

    • 将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的 java.lang.Class 对象,作为方法区中类数据的访问入口

  • 分类

    • 系统类加载器:最常用的加载器,主要负责加载classpath所指定的位置的类或者是jar文档

      ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
      //输出结果为:sun.misc.Launcher$AppClassLoader@18b4aac2
      System.out.println(systemClassLoader);
    • 扩展类加载器:主要加载%JAVA_HOME%/jre/lib/ext,此路径下的所有classes目录以及java.ext.dirs系统变量指定的路径中类库

      ClassLoader extClassLoader = systemClassLoader.getParent();
      //输出结果为:sun.misc.Launcher$ExtClassLoader@1540e19d
      System.out.println(extClassLoader);
    • 引导类加载器:C++编写的,JVM自带的类加载器,负责Java平台核心库,用来装载核心类库,该加载器无法直接获取

      ClassLoader parent = extClassLoader.getParent();
      //输出结果为:null
      System.out.println(parent);

6、类加载内存分析

以下面这段代码为例讲解

public class Test03 {
   public static void main(String[] args) {
       AAA aaa = new AAA();
       System.out.println(AAA.num);
  }
}
?
class AAA{
   static {
       System.out.println("类AAA的静态代码代码块");
  }
?
   static int num = 100;
?
   public AAA() {
       System.out.println("类AAA的构造方法");
  }
}
  • 第一步:加载

    • 将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象

      技术图片

  • 第二步:链接

    • 将Java类的二进制代码合并到JVM的运行状态之中的过程

      • 验证:确保加载的信息符合JVM规范,没有安全方面的问题

      • 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配

      • 解析:虚拟机常量池内的符号引用(常量名)替换成直接引用(地址)的过程

      技术图片

  • 第三步:初始化

    • 执行类构造器方法<clinit>()的过程,类构造器方法<clinit>()是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的

    • 当初始化一个类时,如果发现其父类还没有初始化,则需要先触发其父类的初始化

    • 虚拟机会保证一个类的<clinit>()方法在多线程中会被正确加载和同步

      技术图片

7、类的初始化

  • 类的主动引用一定会引起类的初始化

    • 当虚拟机启动时,先初始main方法所在的类

    • new一个类的对象

    • 调用类的静态成员(final除外)和静态方法

    • 使用java.lang.Reflection包下的方法对类进行反射调用

    • 子类的父类没有被初始化,那么初始化子类时,父类也会被初始化

  • 类的被动引用不会引起类的初始化

    • 当访问一个静态域时,只有真正声明这个域的类才会被初始化,例如通过子类访问父类的静态属性,子类不会被初始化

    • 通过数组定义类引用,不会触发此类的初始化

    • 引用常量不会触发此类的初始化,因为常量在链接阶段就存入调用类的常量池中了

8、获得类的结构

  • 1、获得类名

    • 获得类名

      Class c1 = new User().getClass();
      ?
      c1.getSimpleName();
    • 获得包名 + 类名

      Class c1 = new User().getClass();
      ?
      c1.getName();
  • 2、获得类的属性

    • 获得类公共的属性

      Class c1 = new User().getClass();
      ?
      c1.getFields();//Field[]
    • 获得类所有的属性

      Class c1 = new User().getClass();
      ?
      c1.getDeclaredFields();////Field[]
    • 获得类指定的公共的属性

      Class c1 = new User().getClass();
      ?
      c1.getField("id");
    • 获得类指定的属性

      Class c1 = new User().getClass();
      ?
      c1.getDeclaredField("name");
  • 3、获得类的方法

    • 获得类和父类的公有方法

      Class c1 = new User().getClass();
      ?
      c1.getMethods();//Method[]
    • 获得类的所有方法

      Class c1 = new User().getClass();
      ?
      c1.getDeclaredMethods();//Method[]
    • 获得类的指定的公有方法

      Class c1 = new User().getClass();
      ?
      //无参方法
      c1.getMethod("getName",null);
      //有参方法
      c1.getMethod("setName", String.class);
    • 获得类的指定的方法

      Class c1 = new User().getClass();
      ?
      c1.getDeclaredMethod("test", null);
  • 4、获得类的构造器

    • 获得共有的构造器

      Class c1 = new User().getClass();
      ?
      c1.getConstructors();//Constructor[]
    • 获得所有的构造器

      Class c1 = new User().getClass();
      ?
      c1.getDeclaredConstructors();//Constructor[]
    • 获得指定的构造器

      Class c1 = new User().getClass();
      ?
      c1.getConstructor(int.class,String.class, int.class);
      c1.getDeclaredConstructor(int.class,String.class,int.class);
  • 5、获得泛型信息

    • 参数为泛型

      //参数为泛型的方法
      public void test01(Map<String,User> userMap, List<User> userList){}
      ?
      //获得泛型信息
      ?
      Method test01 = Test09.class.getMethod("test01", Map.class, List.class);
      //获得方法的泛型参数类型
      Type[] gPTypes = test01.getGenericParameterTypes();
      for (Type gPType : gPTypes) {
         /*
         输出结果:
        java.util.Map<java.lang.String, com.hmx.pojo.User>
      java.util.List<com.hmx.pojo.User>
         */
         System.out.println(gPType);
         //判断gPType是否属于ParameterizedType的类型
         if (gPType instanceof ParameterizedType){
             //获得泛型参数的实际类型
             Type[] aTArgus = ((ParameterizedType) gPType).getActualTypeArguments();
             for (Type tArgus : aTArgus) {
                 System.out.println(tArgus);
            }
        }
      }
    • 返回结果为泛型

      //返回结果为泛型
      public List<User> test02(){
         return null;
      }
      ?
      //获得泛型信息
      ?
      //获得方法
      Method test02 = Test09.class.getMethod("test02",null);
      ?
      //获得泛型返回值类型
      Type genericRType = test02.getGenericReturnType();
      //判断genericRType是否属于ParameterizedType的类型
      if (genericRType instanceof ParameterizedType){
         //获得泛型返回值的实际类型
         Type[] actualTA = ((ParameterizedType) genericRType).getActualTypeArguments();
         for (Type aTA : actualTA) {
             System.out.println(aTA);
        }
      }
  • 6、获取注解信息

    //自定义注解
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @interface Table{
       String tableName();
    }
    ?
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    @interface Filed{
       String columnName();
       String type();
       int length();
    }
    ?
    //实体类
    @Table(tableName = "用户表")
    public class User {
    ?
       @Filed(columnName = "姓名",type = "varchar",length = 11)
       private String name;
       @Filed(columnName = "年龄",type = "int",length = 3)
       private int age;
    }
    ?
    //获得注解信息
    public class Test10 {
    ?
       public static void main(String[] args) throws Exception {
    ?
           //获得Class对象
           Class c1 = Class.forName("com.hmx.pojo.User");
    ?
           //获得类的注解信息
           Annotation[] annotations = c1.getAnnotations();
           for (Annotation annotation : annotations) {
               //输出结果为:@com.hmx.pojo.Table(tableName=用户表)
               System.out.println(annotation);
          }
    ?
           //获得类注解信息里的参数列表
           Table tableAnnotation = (Table)c1.getAnnotation(Table.class);
           //输出结果为:用户表
           System.out.println(tableAnnotation.tableName());
    ?
           //获得属性的注解信息
           Field name = c1.getDeclaredField("name");
           Annotation[] annotations1 = name.getAnnotations();
           for (Annotation annotation : annotations1) {
               //输出结果为:@com.hmx.pojo.Filed(columnName=姓名, type=varchar, length=11)
               System.out.println(annotation);
          }
    ?
           //获得属性注解信息的参数列表
           Filed annotation = name.getAnnotation(Filed.class);
           //输出结果为:姓名
           System.out.println(annotation.columnName());
           //输出结果为:varchar
           System.out.println(annotation.type());
           //输出结果为:11
           System.out.println(annotation.length());
      }
    }

9、通过反射创建对象以及操作对象的属性和方法

  • 创建对象:newInstance()方法

    • 默认调用的是类的无参构造方法

      Class c1 = Class.forName("com.hmx.pojo.User");
      ?
      User user = (User)c1.newInstance();
    • 显示指定调用有参构造方法

      Class c1 = Class.forName("com.hmx.pojo.User");
      ?
      Constructor con = c1.getDeclaredConstructor(int.class, String.class, int.class);
      User user = (User)con.newInstance(1, "hmx", 23);
  • 操作对象的属性

    Class c1 = Class.forName("com.hmx.pojo.User");
    ?
    //创建user对象
    User user = (User)c1.newInstance();
    ?
    //获得对象的属性
    Field name = c1.getDeclaredField("name");
    //不能直接操作私有属性,我们需要关闭程序的安全检测
    name.setAccessible(true);
    //给属性赋值
    name.set(user,"洪梦霞");
  • 操作对象的方法

    Class c1 = Class.forName("com.hmx.pojo.User");
    User user = (User)c1.newInstance();
    ?
    //操作无参私有方法
    Method test = c1.getDeclaredMethod("test",null);
    test.setAccessible(true);
    //激活方法
    test.invoke(user);
    ?
    //操作有参公有方法
    Method setName = c1.getDeclaredMethod("setName", String.class);
    setName.invoke(user,"hmx");

10、性能分析

经过以下代码可知:(正常创建对象的效率) 高于 (使用反射创建对象,关闭Java安全检测的效率 )高于 (使用反射创建对象,不关闭Java安全检测的效率)

public class Test08 {
?
   public static void main(String[] args) throws Exception {
       test01();
       test02();
       test03();
  }
?
   //正常创建对象,访问10亿次对象中方法所用时间:4ms
   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");
  }
?
   //使用反射创建对象,访问10亿次对象中方法所用时间:1390ms
   public static void test02() throws Exception {
       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");
  }
?
   //使用反射创建对象,关闭Java安全检测,访问10亿次对象中方法所用时间:1017ms
   public static void test03() throws Exception {
       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");
  }
}

以上是关于Java反射的主要内容,如果未能解决你的问题,请参考以下文章

反射机制入门

反射机制入门

反射机制入门

使用反射在外部JAR / CLASS上调用包含Hibernate事务的方法(Java EE)

为啥我的 Ray March 片段着色器反射纹理查找会减慢我的帧速率?

OpenGL片段着色器不照亮场景