美文网首页
反射机制

反射机制

作者: 安仔夏天勤奋 | 来源:发表于2019-09-27 17:38 被阅读0次

    一般情况下,我们用到某个类时都是直接实例化出一个类对象,而这个类我们都知道的,然后对这个类对象进行一系列操作。如果想在运行期间获得某个类的结构、成员变量、方法等信息,那么就要用到反射了,也用来实例化出一个对象,对其进行一系列对象。

    反射基本概念

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

    反射机制主要的功能

    • 在运行时构造任意类对象
    • 在运行时,获取包信息、属性和方法等
    • 在运行时,调用类的方法或修改成员变量值等

    在面向对象的世界里,万事万物皆对象。在java语言中,静态的成员、普通数据类型是不是对象呢?类是谁的对象呢?类是对象,类是java.lang.Class类的实例对象。在Java 中使用 Class 这个类来表示所有的类。反射的入口的第一步是首先获取类的 Class 对象。

    Class类

    Class 类用于描述一个类的所有信息,例如构造方法,成员变量,成员方法等,类中每一个元素都有一个对应的类来表示。只列举一些函数表达

    • 使用Constructor表示构造函数
    • 使用Method表示成员方法
    • 使用Field表示成员属性

    Class类的常用方法

    • getName()

      一个Class对象描述了一个特定类的属性,Class类中最常用的方法getName以String的形式返回此Class对象所表示的实体(类、接口、数组类、基本类型或void名称。

    • getSimpleName()

      获取不包含包名的类的名称。

    • newInstance()

      Class还有一个有用的方法可以为类创建一个实例对象,这个方法叫做newInstance()。例如:x.getClass.newInstance(),创建了一个同x一样类型的新实例。newInstance()方法调用默认构造器(无参数构造器)初始化新建实例对象。注意:前提是需要一个无参的构造方法

    • getClassLoader()

      返回该类的类加载器。

    • getComponentType()

      返回表示数组组件类型的Class。

    • getSuperclass()

      返回表示此 Class 所表示的实体(类、接口、基本类型或 void)的超类的Class。

    • isArray()

      判定此 Class 对象是否表示一个数组类。

    • getModifiers()

      使用Modifier 表示解码 Class 修饰符。使用 Class.getModifiers() 获得调用类的修饰符的二进制值,然后使用 Modifier.toString(int modifiers) 将获取到的二进制值转换为字符串。

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

    • 通过类名.class获取

      当执行类名.class时,JVM会先检查Class对象是否装入内存,如果没有装入内存,则将Class对象装入内存,然后返回Class对象,如果装入内存,则直接返回Class对象。在加载Class对象后,不会对Class对象进行初始化。

    • 通过对象.getClass()获取

      getClass()方法的方法是在通过的类的实例调用的,即已经创建了类的实例。

    • 通过Class.forName(全类名)获取

      当执行Class.forName()时,JVM也会先检查Class对象是否装入内存,如果没有装入内存,则将Class对象装入内存,然后返回Class对象,如果装入内存,则直接返回Class对象。在加载Class对象后,会对类进行初始化,即执行类的静态代码块。forName()方法中的参数是类名字符串,类名字符串 = 包名 + 类名。Class.forName()的一个很常见的用法是在加载数据库驱动的时候。不仅表示了类的类类型,还代表了动态加载类。注意:编译时刻加载类是静态加载类、运行时刻加载类是动态加载类。

        //获取对象  一般直接new出一个对象
        Person p = new Person();
        //Class类的实例对象,不能直接new出来。任何一个类都是Class的实例对象,这个实例对象有三种表达方        式。
        //每个类型,包括基本和引用,都会赋予这个类型的一个静态的属性,属性的名字就叫class。
        //实际在告诉我们任何一个类都是有一个隐含的静态成员变量class。
        Class  c= Person.class;
        //调用person类的父类的方法getclass,即知道该类的对象通过getClass方法获得
        Class c = p.getClass();
        //forName 还不知道具体的类
          //此处需要注意的是引号里面需要填写完整的类名包括包名
        Class c= Class.forName("com.example.reflect.Person");
        //通过类的类类型创建该类的对象实例
        //Person person = (Person) c.newInstance();//(提前是需要有无参数的构造方法)
      

    Class类的基本的数据类型

     Class c1 = int.class;//int的类类型
     Class c2 = String.class;
     Class c3 = double.class;
     Class c4 = Double.class;
     Class c5 = void.class;
    
     System.out.println("c1 getName "+c1.getName());//打印类类型的名称
     System.out.println("c2 getName "+c2.getName());//打印类的全称
     System.out.println("c2 getSimpleName "+c2.getSimpleName());//不包含包名的类的名称
     System.out.println("c3 getName "+c3.getName());//打印类类型的名称
     System.out.println("c4 getName "+c4.getName());//打印类的全称
     System.out.println("c5 getName "+c5.getName());//打印类的全称
     //Class.getModifiers() 使用Modifier 表示解码 Class 修饰符。使用 Class.getModifiers() 获得调用类的修饰符的二进制值,然后使用 Modifier.toString(int modifiers) 将获取到的二进制值转换为字符串。
    
     //打印结果
     c1 getName int
     c2 getName java.lang.String
     c2 getSimpleName String
     c3 getName double
     c4 getName java.lang.Double
     c5 getName void
    
    

    注意:void关键字都存在类类型

    Constructor构造器

    获取Constructor构造器对象:

    • getConstructor(Class... parameterType) 获取具有指定参数的公共构造器对象

    • getConstructors() 获取所有公共构造器对象

    • getDeclaredConstructor(Class... parameterType)获取具有指定参数的构造器对象

    • getDdclaredConstructors() 获取所有构造器对象

      Class c = Person.class;
      Constructor cons = c.getConstructor();
      Person p = (Person) cons.newInstance();//无参构造(提前是需要有无参数的构造方法)
      p.print();
      
      Constructor cons1 = c.getConstructor(int.class,String.class);
      Person p1 = (Person) cons1.newInstance(18,"张三");//有参构造
      p1.print();
      
      Constructor cons2 = c.getConstructor(int.class,String.class,String.class);
      Person p2 = (Person) cons2.newInstance(20,"李四","男");//有参构造
      p2.print();
      
      //打印结果
      {姓名:null,性别:null,年龄:0}
      {姓名:张三,性别:null,年龄:18}
      {姓名:李四,性别:男,年龄:20}
      
      

    Field字段

    获取Field字段对象:

    • getField(String name) 获取具有指定名称的公共字段对象
    • getFields()获取具有所有公共字段对象
    • getDeclaredField(String name)获取具有指定名称的字段对象
    • getDeclaredFields()获取所有字段对象

    Field类主要方法

    • Object get(Object obj) 获取obj对象的此Field对象代表的字段的值

    • void set(Object obj , Object value)设置obj对象的此Field对象代表的字段的值

      Constructor cons2 = c.getConstructor(int.class,String.class,String.class);
      Person p2 = (Person) cons2.newInstance(20,"李四","男");
      p2.print();//打印
      
      Field field = c.getDeclaredField("name");//获取name字段
      field.setAccessible(true); //开启访问权限
      field.set(p2,"小二");////给Person对象的该字段设置值
      p2.print();//打印
      
      //打印出的结果
      {姓名:李四,性别:男,年龄:20}
      {姓名:小二,性别:男,年龄:20}
      

    Method对象

    获取Method对象:

    • getMethod(String name,Class...parameterType)获取具有指定名称和参数的公共方法对象
    • getMethods()获取所有公共方法对象,包括父类继承而来的
    • getDeclaredMethod(String name,Class...parameterType)获取的是所有该 类自己声明的方法对象
    • getDeclaredMethods()获取的是所有该 类自己声明的方法对象,不问访问权限
    Person p = (Person) c.newInstance();
    Method method = c.getDeclaredMethod("printMethod",String.class);//获取名为"printMethod",参数为string的method对象
    method.invoke(p,"我通过反射调用printMethod方法");//调用Person的printMethod方法,并打印
                
    //打印结果
    我通过反射调用printMethod方法
    

    在JDK1.4 和 JDk1.5 版本中 Method#invoke 方法的区别

    JDK1.5 public Object invoke(Object obj,Object...args)
    JDK1.4 public Object invoke(Object obj,Object[] args)

    如果是以一个字符串数组传入给 invoke 方法,那么 javac 会按照哪种语法给处理呢?
    因为 JDK1.5 需要兼容 JDk1.4 因此会按照 JDK1.4 的语法来执行。由于invoke方法的实现是在native中就不过多的详实了。

    @RecentlyNullable
    public native Object invoke(@RecentlyNullable Object var1, @RecentlyNullable Object... var2) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException;
    
    

    通过Class、Method来认清泛型的本质

    ArrayList list1 = new ArrayList();//指任意类型
    ArrayList<String> list2 = new ArrayList<>();//指定String类型
    list2.add("hello");
    //list2.add(100);//错误
    
    Class c1 = list1.getClass();
    Class c2 = list2.getClass();
    System.out.println(c1==c2);//打印结果为true
    

    c1==c2结果为true,说明编译之后的集合泛型是去泛型化的。java中集合的泛型,是防止错误输入的(list2.add(100)会提示这样添加是错误的),只在编译阶段有效,绕过了编译就无效了。怎么证明只在编译阶段有效,绕过了编译就无效了是正确的呢?我们可以通过方法的反射来操作,绕过编译验证。代码如下:

    Method m = c2.getDeclaredMethod("add",Object.class);
    m.invoke(list2,100);//绕过编译操作就绕过了泛型
    //看看是否添加一个int值进入list2集合中
    System.out.println(list2);//打印结果为[hello, 100]
    

    打印结果为[hello, 100],说明这个int 100能添加到ArrayList<String>集合中,所以就证明了java中集合的泛型,是防止错误输入的,只在编译阶段有效,绕过了编译就无效了。所以需要区分编译和运行。
    注意: 反射后就不能for(String s : list2)这样遍历了,因为list集合中100是int类型,则会报类异常。

    反射中为什么调用方法Method.invoke(Object obj,Object arr),既然传了对象给invoke方法,为什么不直接调用对应的方法?
    Method对象是在运行期通过字节码对象获取的,对于编码期间,并不知道具体的对象是什么类型,所以需要用invoke方法调用。Method.invoke(...)中传入了具体对象和方法参数,以及方法对象。在JVM中会获取具体对象的class字节码对象,又有了方法对象和方法参数,就可以动态调用Method.invoke()方法。

    动态加载与静态加载

    此处所说的加载是针对编译的,将xxx.java转化成xxx.class,而不是运行的加载字节码。Java创建对象的常用方式是使用new 关键字,如 Person p = new Person(); 这种是静态加载,即在编译期就已经获取Person类的有关信息,如果Person类不存在或有错误,编译会报错。动态加载就是用上面的Class.forName("包名.Person");来加载Person类,如果Person类不存在或是有错误,在编译时是不会报错的,因为根本没去加载Person类。只有当程序运行到该处,JVM才会去加载Person,而动态加载则是依赖反射实现的。

    相关文章

      网友评论

          本文标题:反射机制

          本文链接:https://www.haomeiwen.com/subject/sddbyctx.html