反射机制介绍

Java反射机制原理

  • 反射的原理是什么?
    • 反射是为了能够动态的加载一个类,动态的调用一个方法,动态的访问一个属性等动态要求而设计的。它的出发点就在于JVM会为每个类创建一个java.lang.Class类的实例,通过该对象可以获取这个类的信息,然后通过使用java.lang.reflect包下得API以达到各种动态需求。
    • 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性,这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
    • 使用的前提条件:必须先得到代表的字节码的Class,Class类用于表示.class文件(字节码

Java反射机制的功能

  • Java反射机制的功能有哪些?
    • 1.在运行时判断任意一个对象所属的类。
    • 2.在运行时构造任意一个类的对象。
    • 3.在运行时判断任意一个类所具有的成员变量和方法。
    • 4.在运行时调用任意一个对象的方法。
    • 5.生成动态代理。
  • 反射的组成
    • 由于反射最终也必须有类参与,因此反射的组成一般有下面几个方面组成:
      • 1.java.lang.Class.java:类对象;
      • 2.java.lang.reflect.Constructor.java:类的构造器对象;
      • 3.java.lang.reflect.Method.java:类的方法对象;
      • 4.java.lang.reflect.Field.java:类的属性对象;
    • 反射中类的加载过程
      • 根据虚拟机的工作原理,一般情况下,类需要经过:加载->验证->准备->解析->初始化->使用->卸载这个过程,如果需要反射的类没有在内存中,那么首先会经过加载这个过程,并在在内存中生成一个class对象,有了这个class对象的引用,就可以发挥开发者的想象力,做自己想做的事情了。

Java反射的应用

  • Java反射的应用
    • 1.逆向代码 ,例如反编译
    • 2.与注解相结合的框架 例如Retrofit
    • 3.单纯的反射机制应用框架 例如EventBus
    • 4.动态生成类框架 例如Gson
  • 反射的作用有哪些
    • 前面只是说了反射是一种具有与Java类进行动态交互能力的一种机制,在Java和Android开发中,一般情况下下面几种场景会用到反射机制.
      • 需要访问隐藏属性或者调用方法改变程序原来的逻辑,这个在开发中很常见的,由于一些原因,系统并没有开放一些接口出来,这个时候利用反射是一个有效的解决方法
      • 自定义注解,注解就是在运行时利用反射机制来获取的。
      • 在开发中动态加载类,比如在Android中的动态加载解决65k问题等等,模块化和插件化都离不开反射,离开了反射寸步难行。
  • 反射的用途
    • 官方解释:反射被广泛地用于那些需要在运行时检测或修改程序行为的程序中。这是一个相对高级的特性,只有那些语言基础非常扎实的开发者才应该使用它。如果能把这句警示时刻放在心里,那么反射机制就会成为一项强大的技术,可以让应用程序做一些几乎不可能做到的事情。

Class与.class文档

  • Java 在真正需要某个类时才会加载对应的.class文档,而非在程序启动时就加载所有类,因为大部分时候我们只需要用到应用程序部分资源,有选择地加载可以节省系统资源

  • java.lang.Class 的实例代表 Java 应用程序运行时加载的 .class 文档,类、接口、Enum等编译过后,都会生成 .class 文档,所以 Class可以用来包含类、接口、Enum等信息

  • Class 类没有公开的构造函数,实例是由 JVM 自动产生,每个 .class 文档加载时, JVM 会自动生成对应的 Class 对象

    • 可以通过 Object 的 getClass() 方法或者通过 .class 常量取得每个对象对应的 Class 对象。如果是基本类型,可以使用对象的包装类加载 .TYPE 取得 Class 对象
    • 例如,使用 Integer.TYPE 可取得代表 int 基本类型的 Class,通过 Integer.class 取得代表 Integer.class 文档的 Class
    • 在取得 Class 对象后,就可以操作 Class 对象的公开方法取得类基本信息
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      package com.yc.demo;
      public class Student {
      public static void main(String[] args) {
      Class cl = Student.class;
      System.out.println("类名称:"+cl.getName());
      System.out.println("简单类名称:"+cl.getSimpleName());
      System.out.println("包名:"+cl.getPackage());
      System.out.println("是否为接口:"+cl.isInterface());
      System.out.println("是否为基本类型:"+cl.isPrimitive());
      System.out.println("是否为数组对象:"+cl.isArray());
      System.out.println("父类名称:"+cl.getSuperclass().getName());
      }
      }
    • 输出结果为
      1
      2
      3
      4
      5
      6
      7
      类名称:com.yc.demo.Student
      简单类名称:Student
      包名:package com.yc.demo
      是否为接口:false
      是否为基本类型:false
      是否为数组对象:false
      父类名称:java.lang.Object
  • Java 在真正需要类时才会加载.class文档,即在生成对象时才会加载.class文档。如果只是使用类声明了一个变量,此时并不会加载.class文档,而只是让编译程序检查对应的 .class 文档是否存在。

    • 例如,在 Stduent 类中定义了 static 静态区域块,在首次加载 .class 文档时会被执行(这是默认情况下,也可以指定不执行)
    1
    2
    3
    4
    5
    public class Student {
    static {
    System.out.println("载入了 Student.class 文档");
    }
    }
    • 再来测试加载顺序
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    package com.yc.demo;

    public class Main {
    public static void main(String[] args) {
    Student student;
    System.out.println("声明了 Student 变量");
    student=new Student();
    System.out.println("生成了 Student 实例");
    }
    }
    • 输出结果为
    1
    2
    3
    声明了 Student 变量
    载入了 Student.class 文档
    生成了 Student 实例

反射之动态交互

  • 反射是一种具有与类进行动态交互能力的一种机制,为什么要强调动态交互呢
    • 动态加载,也就是在运行的时候才会加载,而不是在编译的时候,在需要的时候才进行加载获取,或者说你可以在任何时候加载一个不存在的类到内存中,然后进行各种交互,或者获取一个没有公开的类的所有信息,换句话说,开发者可以随时随意的利用反射的这种机制动态进行一些特殊的事情。

使用反射的初衷

  • 使用反射的初衷是什么
    • 反射的初衷不是方便你去创建一个对象,而是让你在写代码的时候可以更加灵活,降低耦合,提高代码的自适应能力。

反射查看类信息

Java反射查看类信息

  • 在取得 Class 对象后,就可以操作 Class 对象的公开方法取得类基本信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    private void method1() {
    Class<?> cl = Student.class;
    LogUtils.i("类名称:"+cl.getName());
    LogUtils.i("类名称:"+cl.getName());
    LogUtils.i("简单类名称:"+cl.getSimpleName());
    LogUtils.i("包名:"+cl.getPackage());
    LogUtils.i("是否为接口:"+cl.isInterface());
    LogUtils.i("是否为基本类型:"+cl.isPrimitive());
    LogUtils.i("是否为数组对象:"+cl.isArray());
    LogUtils.i("父类名称:"+cl.getSuperclass().getName());
    }
  • 输出结果为

    1
    2
    3
    4
    5
    6
    7
    8
    2019-06-11 15:56:59.490 2446-2446/com.ycbjie.other I/yc: 类名称:com.ycbjie.other.ui.activity.Student
    2019-06-11 15:56:59.490 2446-2446/com.ycbjie.other I/yc: 类名称:com.ycbjie.other.ui.activity.Student
    2019-06-11 15:56:59.490 2446-2446/com.ycbjie.other I/yc: 简单类名称:Student
    2019-06-11 15:56:59.490 2446-2446/com.ycbjie.other I/yc: 包名:package com.ycbjie.other.ui.activity
    2019-06-11 15:56:59.490 2446-2446/com.ycbjie.other I/yc: 是否为接口:false
    2019-06-11 15:56:59.490 2446-2446/com.ycbjie.other I/yc: 是否为基本类型:false
    2019-06-11 15:56:59.490 2446-2446/com.ycbjie.other I/yc: 是否为数组对象:false
    2019-06-11 15:56:59.490 2446-2446/com.ycbjie.other I/yc: 父类名称:java.lang.Object

获得Class对象

  • 每个类被加载之后,系统就会为该类生成一个对应的Class对象。通过该Class对象就可以访问到JVM中的这个类。

  • 在Java程序中获得Class对象通常有如下三种方式:

    • 1.使用Class类的forName(String clazzName)静态方法。该方法需要传入字符串参数,该字符串参数的值是某个类的全限定名(必须添加完整包名)。
    • 2.调用某个类的class属性来获取该类对应的Class对象。
    • 3.调用某个对象的getClass()方法。该方法是java.lang.Object类中的一个方法。
    1
    2
    3
    4
    5
    6
    7
    //第一种方式 通过Class类的静态方法——forName()来实现
    class1 = Class.forName("com.lvr.reflection.Person");
    //第二种方式 通过类的class属性
    class1 = Person.class;
    //第三种方式 通过对象getClass方法
    Person person = new Person();
    Class<?> class1 = person.getClass();

Class.forName()

  • 1.通过JVM查找并加载指定的类(上面的代码指定加载了com.fanshe包中的Person类)

  • 2.调用newInstance()方法让加载完的类在内存中创建对应的实例,并把实例赋值给p

    • 注意:如果找不到时,它会抛出 ClassNotFoundException 这个异常,这个很好理解,因为如果查找的类没有在 JVM 中加载的话,自然要告诉开发者。
    1
    2
    Class<?> cls=Class.forName("com.yc.Person"); //forName(包名.类名)
    Person p= (Person) cls.newInstance();

类.class

  • 1.获取指定类型的Class对象,这里是Person

  • 2.调用newInstance()方法在让Class对象在内存中创建对应的实例,并且让p引用实例的内存地址

    1
    2
    Class<?> cls = Person.class;
    Person p=(Person)cls.newInstance();

对象.getClass()

  • 1.在内存中新建一个Person的实例,对象p对这个内存地址进行引用

  • 2.对象p调用getClass()返回对象p所对应的Class对

  • 3.调用newInstance()方法让Class对象在内存中创建对应的实例,并且让p2引用实例的内存地址

    1
    2
    3
    Person p = new Person();
    Class<?> cls= p.getClass();
    Person p2=(Person)cls.newInstance();

获取Class父类对象

  • getSuperclass 返回直接继承的父类(由于编译擦除,没有显示泛型参数

    • 返回表示此 Class 所表示的实体(类、接口、基本类型或 void)的超类的 Class
    • 如果此 Class 表示 Object 类、一个接口、一个基本类型或 void,则返回 null。
    • 如果此对象表示一个数组类,则返回表示该 Object 类的 Class 对象。
  • getGenericSuperclass:返回直接继承的父类(包含泛型参数

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

    • 如果超类是参数化类型,则返回的 Type 对象必须准确反映源代码中所使用的实际类型参数。

    • 如果此 Class 表示 Object 类、接口、基本类型或 void,则返回 null。

    • 如果此对象表示一个数组类,则返回表示 Object 类的 Class 对象。

    • 抛出:

      GenericSignatureFormatError - 如果常规类签名不符合 Java Virtual Machine Specification, 3rd edition 规定的格式

      TypeNotPresentException - 如果常规超类引用不存在的类型声明

      MalformedParameterizedTypeException - 如果常规超类引用的参数化类型由于某种原因无法实例化

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      public class Test {

      public static void main(String[] args) {
      System.out.println("Student.class.getSuperclass()\t"
      + Student.class.getSuperclass());
      System.out.println("Student.class.getGenericSuperclass()\t"
      + Student.class.getGenericSuperclass());

      System.out.println("Test.class.getSuperclass()\t"
      + Test.class.getSuperclass());
      System.out.println("Test.class.getGenericSuperclass()\t"
      + Test.class.getGenericSuperclass());

      System.out.println("Object.class.getGenericSuperclass()\t"
      + Object.class.getGenericSuperclass());
      System.out.println("Object.class.getSuperclass()\t"
      + Object.class.getSuperclass());

      System.out.println("void.class.getSuperclass()\t"
      + void.class.getSuperclass());
      System.out.println("void.class.getGenericSuperclass()\t"
      + void.class.getGenericSuperclass());

      System.out.println("int[].class.getSuperclass()\t"
      + int[].class.getSuperclass());
      System.out.println("int[].class.getGenericSuperclass()\t"
      + int[].class.getGenericSuperclass());
      }

      }

      class Person<T> {

      }

      class Student extends Person<Test> {

      }

      输出结果:
      Student.class.getSuperclass() class cn.test.Person
      Student.class.getGenericSuperclass() cn.test.Person<cn.test.Test>
      Test.class.getSuperclass() class java.lang.Object
      Test.class.getGenericSuperclass() class java.lang.Object
      Object.class.getGenericSuperclass() null
      Object.class.getSuperclass() null
      void.class.getSuperclass() null
      void.class.getGenericSuperclass() null
      int[].class.getSuperclass() class java.lang.Object
      int[].class.getGenericSuperclass() class java.lang.Object
  • 先看一下代码

    1
    2
    3
    4
    //在AppBarLayout类中
    public static class Behavior extends AppBarLayout.BaseBehavior<AppBarLayout>
    //BaseBehavior的父类
    protected static class BaseBehavior<T extends AppBarLayout> extends HeaderBehavior<T>
  • 反射获取父类getSuperclass();

    1
    Class<?> superclass = AppBarLayout.Behavior.class.getSuperclass();
  • 反射获取父类的父类 Class.getSuperclass()

    1
    2
    Class<?> superclass = AppBarLayout.Behavior.class.getSuperclass();
    headerBehaviorType = superclass.getSuperclass();
  • Type

    Type 是 Java 编程语言中所有类型的公共高级接口。它们包括原始类型、参数化类型、数组类型、类型变量和基本类型。

  • ParameterizedType接口

    • ParameterizedType 表示参数化类型,如 Collection<String>

    • 参数化类型在反射方法首次需要时创建(在此包中指定)。当创建参数化类型 p 时,p 实例化的一般类型声明会被解析,并且按递归方式创建 p 的所有类型参数。有关类型变量创建过程的详细信息,请参阅 TypeVariable。

    • 重复创建的参数化类型无效。实现此接口的类的实例必须实现 equals() 方法,该方法用于比较两个共享相同一般类型声明和具有相同类型参数的任何实例。

    • 如果该接口有泛型ParameterizedType<T>,则可以通过getActualTypeArguments()[0]方法得到该参数。

反射查看对象的信息

获取class对象的信息

  • 由于反射最终也必须有类参与,因此反射的组成一般有下面几个方面组成:

    • 1.java.lang.Class.java:类对象;
    • 2.java.lang.reflect.Constructor.java:类的构造器对象;
    • 3.java.lang.reflect.Method.java:类的方法对象;
    • 4.java.lang.reflect.Field.java:类的属性对象;
  • 这个就比较多了……

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    boolean isPrimitive = class1.isPrimitive();//判断是否是基础类型
    boolean isArray = class1.isArray();//判断是否是集合类
    boolean isAnnotation = class1.isAnnotation();//判断是否是注解类
    boolean isInterface = class1.isInterface();//判断是否是接口类
    boolean isEnum = class1.isEnum();//判断是否是枚举类
    boolean isAnonymousClass = class1.isAnonymousClass();//判断是否是匿名内部类
    boolean isAnnotationPresent = class1.isAnnotationPresent(Deprecated.class);//判断是否被某个注解类修饰
    String className = class1.getName();//获取class名字 包含包名路径
    Package aPackage = class1.getPackage();//获取class的包信息
    String simpleName = class1.getSimpleName();//获取class类名
    int modifiers = class1.getModifiers();//获取class访问权限
    Class<?>[] declaredClasses = class1.getDeclaredClasses();//内部类
    Class<?> declaringClass = class1.getDeclaringClass();//外部类
  • A:获取所有构造方法

    • public Constructor<?>[] getConstructors()
    • public Constructor<?>[] getDeclaredConstructors() 获取所有的构造方法
  • B:获取单个构造方法

    • public Constructor<T> getConstructor(Class<?>... parameterTypes)
    • public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
  • C:方法关键字

    • getDeclareMethods() 获取所有的方法
    • getReturnType() 获取方法的返回值类型
    • getParameterTypes() 获取方法的传入参数类型
    • getDeclareMethod("方法名,参数类型.class,....") 获得特定的方法
  • D:成员变量

    • getDeclaredFields 获取所有成员变量
    • getDeclaredField(参数类型.class,....) 获取特定的成员变量
  • E:父类和父接口

    • getSuperclass() 获取某类的父类
    • getInterfaces() 获取某类实现的接口
  • newInstance():获得一个实例

获取对象的变量

  • 获取class对象的成员变量

    1
    2
    3
    4
    Field[] allFields = class1.getDeclaredFields();//获取class对象的所有属性
    Field[] publicFields = class1.getFields();//获取class对象的public属性
    Field ageField = class1.getDeclaredField("age");//获取class指定属性
    Field desField = class1.getField("des");//获取class指定的public属性
  • 常用方法:

    • getName():  名字
    • getType():  类型
    • getModifiers():  访问权限
    • 属性.get(实例),—–实例.属性
    • 实际案例代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    private void method4() {
    Student student = new Student();
    Class<? extends Student> cl = student.getClass();

    Field[] fields = cl.getFields();
    for (int i=0 ; i<fields.length ; i++){
    Field met = fields[i];
    String name = met.getName();
    Annotation[] declaredAnnotations = met.getDeclaredAnnotations();
    int modifiers = met.getModifiers();
    LogUtils.i("获取class对象的public属性:"+name+"----"+declaredAnnotations.length);
    }

    Field[] declaredFields = cl.getDeclaredFields();
    for (int i=0 ; i<declaredFields.length ; i++){
    Field met = declaredFields[i];
    String name = met.getName();
    Annotation[] declaredAnnotations = met.getDeclaredAnnotations();
    int modifiers = met.getModifiers();
    LogUtils.i("获取class对象的所有属性:"+name+"----"+declaredAnnotations.length);
    }
    }


    获取class对象的public属性:NAME----0
    获取class对象的所有属性:age----0
    获取class对象的所有属性:list----0
    获取class对象的所有属性:name----0
    获取class对象的所有属性:sex----0
    获取class对象的所有属性:NAME----0
  • 获取Filed两个方法的区别

    • 两者的区别就是
      • getDeclaredField() 获取的是 Class 中被 private 修饰的属性。
      • getField() 方法获取的是非私有属性,并且 getField() 在当前 Class 获取不到时会向祖先类获取
    1
    2
    3
    4
    5
    //获取所有的属性,但不包括从父类继承下来的属性
    public Field[] getDeclaredFields() throws SecurityException {}

    //获取自身的所有的 public 属性,包括从父类继承下来的。
    public Field[] getFields() throws SecurityException {}

获取class对象的方法

  • 获取class对象的方法

    1
    2
    3
    4
    Method[] methods = class1.getDeclaredMethods();//获取class对象的所有声明方法
    Method[] allMethods = class1.getMethods();//获取class对象的所有public方法 包括父类的方法
    Method method = class1.getMethod("info", String.class);//返回次Class对象对应类的、带指定形参列表的public方法
    Method declaredMethod = class1.getDeclaredMethod("info", String.class);//返回次Class对象对应类的、带指定形参列表的方法
  • 常用方法

    • getReturnType(): 获取返回值得类型
    • getParameterTyoes(): 获得参数列表,数组[]
    • 方法.invoke(实例,[参数])——–实例.方法()
  • 实际案例代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    private void method3() {
    Student student = new Student();
    Class<? extends Student> cl = student.getClass();


    //获取class对象的所有public方法 包括父类的方法
    Method[] methods = cl.getMethods();
    for (int i=0 ; i<methods.length ; i++){
    Method met = methods[i];
    String name = met.getName();
    Annotation[] declaredAnnotations = met.getDeclaredAnnotations();
    int modifiers = met.getModifiers();
    LogUtils.i("获取class对象的所有public方法,包括父类:"+name+"----"+declaredAnnotations.length);
    }

    //获取class对象的所有声明方法
    Method[] declaredMethods = cl.getDeclaredMethods();
    for (int i=0 ; i<declaredMethods.length ; i++){
    Method met = declaredMethods[i];
    String name = met.getName();
    Annotation[] declaredAnnotations = met.getDeclaredAnnotations();
    int modifiers = met.getModifiers();
    LogUtils.i("获取class对象的所有声明方法:"+name+"----"+declaredAnnotations.length);
    }
    }

    2019-06-11 16:07:15.751 3857-3857/com.ycbjie.other I/yc: 获取class对象的所有public方法,包括父类:equals----0
    2019-06-11 16:07:15.752 3857-3857/com.ycbjie.other I/yc: 获取class对象的所有public方法,包括父类:getAge----0
    2019-06-11 16:07:15.752 3857-3857/com.ycbjie.other I/yc: 获取class对象的所有public方法,包括父类:getClass----0
    2019-06-11 16:07:15.752 3857-3857/com.ycbjie.other I/yc: 获取class对象的所有public方法,包括父类:getName----0
    2019-06-11 16:07:15.752 3857-3857/com.ycbjie.other I/yc: 获取class对象的所有public方法,包括父类:hashCode----0
    2019-06-11 16:07:15.752 3857-3857/com.ycbjie.other I/yc: 获取class对象的所有public方法,包括父类:notify----0
    2019-06-11 16:07:15.752 3857-3857/com.ycbjie.other I/yc: 获取class对象的所有public方法,包括父类:notifyAll----0
    2019-06-11 16:07:15.752 3857-3857/com.ycbjie.other I/yc: 获取class对象的所有public方法,包括父类:setAge----0
    2019-06-11 16:07:15.752 3857-3857/com.ycbjie.other I/yc: 获取class对象的所有public方法,包括父类:setName----0
    2019-06-11 16:07:15.752 3857-3857/com.ycbjie.other I/yc: 获取class对象的所有public方法,包括父类:setStudentAge----0
    2019-06-11 16:07:15.752 3857-3857/com.ycbjie.other I/yc: 获取class对象的所有public方法,包括父类:toString----0
    2019-06-11 16:07:15.752 3857-3857/com.ycbjie.other I/yc: 获取class对象的所有public方法,包括父类:wait----0
    2019-06-11 16:07:15.752 3857-3857/com.ycbjie.other I/yc: 获取class对象的所有public方法,包括父类:wait----0
    2019-06-11 16:07:15.752 3857-3857/com.ycbjie.other I/yc: 获取class对象的所有public方法,包括父类:wait----0
    2019-06-11 16:07:15.752 3857-3857/com.ycbjie.other I/yc: 获取class对象的所有声明方法:getStudent----0
    2019-06-11 16:07:15.752 3857-3857/com.ycbjie.other I/yc: 获取class对象的所有声明方法:setList----0
    2019-06-11 16:07:15.752 3857-3857/com.ycbjie.other I/yc: 获取class对象的所有声明方法:getAge----0
    2019-06-11 16:07:15.752 3857-3857/com.ycbjie.other I/yc: 获取class对象的所有声明方法:getName----0
    2019-06-11 16:07:15.752 3857-3857/com.ycbjie.other I/yc: 获取class对象的所有声明方法:setAge----0
    2019-06-11 16:07:15.752 3857-3857/com.ycbjie.other I/yc: 获取class对象的所有声明方法:setName----0
    2019-06-11 16:07:15.752 3857-3857/com.ycbjie.other I/yc: 获取class对象的所有声明方法:setStudentAge----0

获取class对象的构造函数

  • 获取class对象的构造函数

    1
    2
    3
    4
    Constructor<?>[] allConstructors = class1.getDeclaredConstructors();//获取class对象的所有声明构造函数
    Constructor<?>[] publicConstructors = class1.getConstructors();//获取class对象public构造函数
    Constructor<?> constructor = class1.getDeclaredConstructor(String.class);//获取指定声明构造函数
    Constructor publicConstructor = class1.getConstructor(String.class);//获取指定声明的public构造函数
  • 实际案例代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    private void method2() {
    try {
    Class<?> cl = Class.forName("com.ycbjie.other.ui.activity.Student");
    //获取class对象public构造函数
    Constructor<?>[] constructors = cl.getConstructors();
    for (int i=0 ; i<constructors.length ; i++){
    Constructor con = constructors[i];
    String name = con.getName();
    TypeVariable[] typeParameters = con.getTypeParameters();
    Annotation[] declaredAnnotations = con.getDeclaredAnnotations();
    LogUtils.i("获取class对象public构造函数:"+name+"----"+typeParameters.length);
    }
    //获取class对象的所有声明构造函数
    Constructor<?>[] declaredConstructors = cl.getDeclaredConstructors();
    for (int i=0 ; i<declaredConstructors.length ; i++){
    LogUtils.i("获取class对象的所有声明构造函数:"+declaredConstructors[i].getName());
    }
    } catch (ClassNotFoundException e) {
    e.printStackTrace();
    }
    }

获取方法属性

  • 其他方法

    1
    2
    3
    4
    Annotation[] annotations = (Annotation[]) class1.getAnnotations();//获取class对象的所有注解
    Annotation annotation = (Annotation) class1.getAnnotation(Deprecated.class);//获取class对象指定注解
    Type genericSuperclass = class1.getGenericSuperclass();//获取class对象的直接超类的 Type
    Type[] interfaceTypes = class1.getGenericInterfaces();//获取class对象的所有接口的type集合

获取对象信息案例

  • Class对象代表加载的.class文档,取得Class对象后,就可以取得.class文档中记载的信息,如包、构造函数、数据成员、方法成员等

  • 每一种信息都对有对应的类型,如包对应的类型是 java.lang.Package,构造函数对应的类型是 java.lang.reflect.Constructor

  • 例如,先来为Student类增添多种类型的不同信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    public final class Student {

    enum Gender{
    male,female
    }

    private String name;

    public int age;

    protected Gender gender;

    public Student(String name,int age){

    }

    public Student(String name,int age,Gender gender){

    }

    private Student(){

    }

    public String getName() {
    return name;
    }

    private int getAge(){
    return age;
    }

    protected Gender getGender(){
    return gender;
    }
    }
    • 再来获取各种信息
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    public class Main {

    public static void main(String[] args) {
    try {
    Class cl = Class.forName("com.czy.demo.Student");

    // 取得包对象
    Package p = cl.getPackage();
    System.out.println("包名:" + p.getName());
    // 访问修饰符
    int modifier = cl.getModifiers();
    System.out.println("类访问修饰符:" + Modifier.toString(modifier));

    System.out.println();

    //取得构造函数信息
    Constructor[] constructors=cl.getConstructors();
    for(Constructor constructor:constructors){
    System.out.print("访问修饰符:" + Modifier.toString(constructor.getModifiers()));
    System.out.print(" 构造函数名:"+constructor.getName());
    System.out.println();
    }

    System.out.println();

    //取得声明的数据成员
    Field[] fields = cl.getDeclaredFields();
    for (Field field : fields) {
    System.out.print("访问修饰符:" + Modifier.toString(field.getModifiers()));
    System.out.print(" 类型:"+field.getType().getName());
    System.out.print(" 成员名:"+field.getName());
    System.out.println();
    }

    System.out.println();

    //取得成员方法息
    Method[] methods=cl.getDeclaredMethods();
    for(Method method:methods){
    System.out.print("访问修饰符:" + Modifier.toString(method.getModifiers()));
    System.out.print(" 返回值类型:"+method.getReturnType().getName());
    System.out.print(" 方法名:"+method.getName());
    System.out.println();
    }
    } catch (ClassNotFoundException e) {
    e.printStackTrace();
    }
    }
    }
    • 运行结果
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    包名:com.yc.demo
    类访问修饰符:public final

    访问修饰符:public 构造函数名:com.czy.demo.Student
    访问修饰符:public 构造函数名:com.czy.demo.Student

    访问修饰符:private 类型:java.lang.String 成员名:name
    访问修饰符:public 类型:int 成员名:age
    访问修饰符:protected 类型:com.czy.demo.Student$Gender 成员名:gender

    访问修饰符:public 返回值类型:java.lang.String 方法名:getName
    访问修饰符:private 返回值类型:int 方法名:getAge
    访问修饰符:protected 返回值类型:com.czy.demo.Student$Gender 方法名:getGender

反射的应用

反射生成类实例对象

  • 生成类的实例对象

    • 1.使用Class对象的·newInstance()·方法来创建该Class对象对应类的实例。这种方式要求该Class对象的对应类有默认构造器,而执行newInstance()方法时实际上是利用默认构造器来创建该类的实例
    • 2.先使用Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建该Class对象对应类的实例。通过这种方式可以选择使用指定的构造器来创建实例。
    1
    2
    3
    4
    5
    //第一种方式 Class对象调用newInstance()方法生成
    Object obj = class1.newInstance();
    //第二种方式 对象获得对应的Constructor对象,再通过该Constructor对象的newInstance()方法生成
    Constructor<?> constructor = class1.getDeclaredConstructor(String.class);//获取指定声明构造函数
    obj = constructor.newInstance("hello");
  • new对象和反射得到对象的区别

    • 在使用反射的时候,必须确保这个类已经加载并已经连接了。使用new的时候,这个类可以没有被加载,也可以已经被加载。
    • new关键字可以调用任何public构造方法,而反射只能调用无参构造方法。
    • new关键字是强类型的,效率相对较高。 反射是弱类型的,效率低。
    • 反射提供了一种更加灵活的方式创建对象,得到对象的信息。如Spring 中AOP等的使用,动态代理的使用,都是基于反射的。解耦。

反射调用类的方法

  • 调用类的方法

    • 1.通过Class对象的getMethods()方法或者getMethod()方法获得指定方法,返回Method数组或对象。
    • 2.调用Method对象中的Object invoke(Object obj, Object... args)方法。第一个参数对应调用该方法的实例对象,第二个参数对应该方法的参数。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    private void method8() {
    try {
    Class<?> cl = Class.forName("com.ycbjie.other.ui.activity.Student");
    // 生成新的对象:用newInstance()方法
    Student obj = (Student) cl.newInstance();
    String student1 = obj.getStudent();
    LogUtils.i("反射调用类的方法1:"+student1);
    //首先需要获得与该方法对应的Method对象
    Method method = cl.getDeclaredMethod("setAge", int.class);
    //设置权限
    method.setAccessible(true);
    //调用指定的函数并传递参数
    method.invoke(obj, 28);
    String student2 = obj.getStudent();
    LogUtils.i("反射调用类的方法2:"+student2);
    } catch (ClassNotFoundException e) {
    e.printStackTrace();
    } catch (IllegalAccessException e) {
    e.printStackTrace();
    } catch (InstantiationException e) {
    e.printStackTrace();
    } catch (InvocationTargetException e) {
    e.printStackTrace();
    } catch (NoSuchMethodException e) {
    e.printStackTrace();
    }
    }

    //打印值
    2019-06-11 18:24:40.146 23666-23666/com.ycbjie.other I/yc: 反射调用类的方法1:yc---26
    2019-06-11 18:24:40.146 23666-23666/com.ycbjie.other I/yc: 反射调用类的方法2:yc---28
  • 获取方法的参数

  • 当通过Method的invoke()方法来调用对应的方法时,Java会要求程序必须有调用该方法的权限。如果程序确实需要调用某个对象的private方法,则可以先调用Method对象的如下方法。

  • setAccessible(boolean flag):将Method对象的acessible设置为指定的布尔值。值为true,指示该Method在使用时应该取消Java语言的访问权限检查;值为false,则知识该Method在使用时要实施Java语言的访问权限检查。

反射访问成员变量值

  • 反射访问成员变量值

    • 1.通过Class对象的getFields()方法或者getField()方法获得指定方法,返回Field数组或对象。
    • 2.Field提供了两组方法来读取或设置成员变量的值:
      • getXXX(Object obj):获取obj对象的该成员变量的值。此处的XXX对应8种基本类型。如果该成员变量的类型是引用类型,则取消get后面的XXX。
      • setXXX(Object obj,XXX val):将obj对象的该成员变量设置成val值。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    private void method9() {
    try {
    Class<?> cl = Class.forName("com.ycbjie.other.ui.activity.Student");
    // 生成新的对象:用newInstance()方法
    Student obj = (Student) cl.newInstance();
    int age = obj.getAge();
    LogUtils.i("反射访问成员变量值1:"+age);
    //获取age成员变量
    //Field field = cl.getField("age");
    Field field = cl.getDeclaredField("age");
    //设置权限
    field.setAccessible(true);
    //将obj对象的age的值设置为10
    field.setInt(obj, 10);
    //获取obj对象的age的值
    int anInt = field.getInt(obj);
    LogUtils.i("反射访问成员变量值2:"+anInt);

    //反射修改私有变量
    // 获取声明的 code 字段,这里要注意 getField 和 getDeclaredField 的区别
    Field gradeField = cl.getDeclaredField("name");
    // 如果是 private 或者 package 权限的,一定要赋予其访问权限
    gradeField.setAccessible(true);
    // 修改 student 对象中的 Grade 字段值
    gradeField.set(obj, "逗比");
    Object o = gradeField.get(obj);
    LogUtils.i("反射访问成员变量值3:"+o.toString());
    } catch (ClassNotFoundException e) {
    e.printStackTrace();
    } catch (IllegalAccessException e) {
    e.printStackTrace();
    } catch (InstantiationException e) {
    e.printStackTrace();
    } catch (NoSuchFieldException e) {
    e.printStackTrace();
    }
    }



    2019-06-11 19:06:59.380 12313-12313/com.ycbjie.other I/yc: 反射访问成员变量值1:26
    2019-06-11 19:06:59.380 12313-12313/com.ycbjie.other I/yc: 反射访问成员变量值2:10
    2019-06-11 19:06:59.380 12313-12313/com.ycbjie.other I/yc: 反射访问成员变量值3:逗比

使用Class建立对象

情景分析

  • 例如,你需要来控制学生、老师或者家长的唱歌行为,可是学生、老师和家长这些类又是由其他人来设计的,你只是对开始与暂停操作进行控制。那么该如何做呢?
  • 定义接口Sing,接口中有start(),stop()方法,通过实现类的类名获取对象,转为Sing对象,调用方法。

建立实例对象

  • 如果已有确切的类,那么就可以使用new关键字建立实例。如果不知道类名称,那么可以利用Class.forName()动态加载.class文档,取得Class对象之后,利用其newInstance()方法建立实例

    1
    2
    Class cl = Class.forName("ClassName");
    Object object = cl.newInstance();
  • 这种事先不知道类名称,又需要建立类实例的需求,一般情况下都是由于开发者需要得到某个类对象并对其行为进行操纵,可是该类又是由他人开发且还未完工,因此就需要来动态加载.class文档

  • 针对情景1的分析与操作步骤

    • 你可以规定学生类必须实现Sing接口

      1
      2
      3
      public interface Sing {
      void start();
      }
    • 那么,就可以来进行自己的开发了,将动态加载的对象强转为Sing

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      public class Main {
      public static void main(String[] args) {
      try {
      Sing palyer = (Sing) Class.forName("className").newInstance();
      palyer.start();
      } catch (Exception e) {
      e.printStackTrace();
      }

      }
      }
    • 然后规定他人设计的学生类必须实现Sing接口

      1
      2
      3
      4
      5
      6
      7
      public class Student implements Sing {

      @Override
      public void start() {
      System.out.println("学生唱歌");
      }
      }
    • 这样,等到得到确切的类名称后,修改main方法的className即可

      1
      2
      3
      4
      5
      6
      7
      8
      9
      public static void main(String[] args) {
      try {
      Sing palyer = (Sing) Class.forName("com.czy.demo.Student").newInstance();
      palyer.start();
      } catch (Exception e) {
      e.printStackTrace();
      }

      }

操作成员方法

指定一个student类

  • 修改Student类,将get方法都指定为公有的,将set方法指定为私有的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    public class Student {

    private String name;

    private int age;

    public Student() {

    }

    public Student(String name, int age) {
    this.name = name;
    this.age = age;
    }

    public String getName() {
    System.out.println("调用了getName方法,Name:" + name);
    return name;
    }

    public int getAge() {
    System.out.println("调用了getAge方法,Age:" + age);
    return age;
    }

    private void setName(String name) {
    this.name = name;
    System.out.println("调用了setName方法,name:" + name);
    }

    private void setAge(int age) {
    this.age = age;
    System.out.println("调用了setAge方法,age:" + age);
    }
    }

反射调用公有方法

  • java.lang.reflect.Method 实例是方法的代表对象,可以使用 invoke() 方法来动态调用指定的方法

  • 首先来调用公有方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public class Main {

    public static void main(String[] args) throws Exception {
    Class cl = Class.forName("com.czy.demo.Student");
    // 指定构造函数
    Constructor constructor = cl.getConstructor(String.class, Integer.TYPE);
    // 根据指定的构造函数来获取对象
    Object object = constructor.newInstance("杨充逗比", 25);

    // 指定方法名称来获取对应的公开的Method实例
    Method getName = cl.getMethod("getName");
    // 调用对象object的方法
    getName.invoke(object);

    // 指定方法名称来获取对应的公开的Method实例
    Method getAge = cl.getMethod("getAge");
    // 调用对象object的方法
    getAge.invoke(object);

    }
    }
  • 输出结果如下所示,可以知道Student对象的两个get方法成功被调用了。

    1
    2
    调用了getName方法,Name:杨充逗比
    调用了getAge方法,Age:25

反射调用私有方法

  • 一般情况下,类的私有方法只有在其内部才可以被调用,通过反射我们可以来突破这一限制

  • 受保护或私有方法的调用步骤略有不同

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class Main {
    public static void main(String[] args) throws Exception {
    Class cl = Class.forName("com.czy.demo.Student");
    // 指定构造函数
    Constructor constructor = cl.getConstructor(String.class, Integer.TYPE);
    // 根据指定的构造函数来获取对象
    Object object = constructor.newInstance("杨充逗比", 25);

    // 指定方法名称来获取对应的私有的Method实例
    Method setName = cl.getDeclaredMethod("setName", String.class);
    setName.setAccessible(true);
    setName.invoke(object, "潇湘剑雨");

    // 指定方法名称来获取对应的私有的Method实例
    Method setAge = cl.getDeclaredMethod("setAge", Integer.TYPE);
    setAge.setAccessible(true);
    setAge.invoke(object, 100);
    }
    }
  • 输出结果如下所示,可以看到私有方法一样在外部被调用了

    1
    2
    调用了setName方法,name:潇湘剑雨
    调用了setAge方法,age:100

invoke()方法执行

  • Method 调用 invoke() 的时候,存在许多细节:
    • invoke() 方法中第一个参数 Object 实质上是 Method 所依附的 Class 对应的类的实例,如果这个方法是一个静态方法,那么 ojb 为 null,后面的可变参数 Object 对应的自然就是参数。
    • invoke() 返回的对象是 Object,所以实际上执行的时候要进行强制转换。
    • 在对Method调用invoke()的时候,如果方法本身会抛出异常,那么这个异常就会经过包装,由Method统一抛InvocationTargetException。而通过InvocationTargetException.getCause() 可以获取真正的异常。

setAccessible暴力访问

  • 设置.setAccessible(true)暴力访问权限

    • 一般情况下,我们并不能对类的私有字段进行操作,利用反射也不例外,但有的时候,例如要序列化的时候,我们又必须有能力去处理这些字段,这时候,我们就需要调用AccessibleObject上的setAccessible()方法来允许这种访问,而由于反射类中的Field,Method和Constructor继承自AccessibleObject,因此,通过在这些类上调用setAccessible()方法,我们可以实现对这些字段的操作。
    • 值为true,指示该Method在使用时应该取消Java语言的访问权限检查;
    • 值为false,则知识该Method在使用时要实施Java语言的访问权限检查。
    1
    2
    3
    4
    5
    6
    7
    Field gradeField = clazz.getDeclaredField("code");
    // 如果是 private 或者 package 权限的,一定要赋予其访问权限
    gradeField.setAccessible(true);

    Method goMethod = clazz.getDeclaredMethod("getMethod");
    // 赋予访问权限
    goMethod.setAccessible(true);

泛型和反射

泛型和Class类

  • 从JDK 1.5 后,Java中引入泛型机制,Class类也增加了泛型功能,从而允许使用泛型来限制Class类,例如:String.class的类型实际上是Class<String>。如果Class对应的类暂时未知,则使用Class<?>(?是通配符)。通过反射中使用泛型,可以避免使用反射生成的对象需要强制类型转换

  • 泛型的好处众多,最主要的一点就是避免类型转换,防止出现ClassCastException,即类型转换异常。以下面程序为例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class ObjectFactory {
    public static Object getInstance(String name){
    try {
    //创建指定类对应的Class对象
    Class cls = Class.forName(name);
    //返回使用该Class对象创建的实例
    return cls.newInstance();
    } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
    e.printStackTrace();
    return null;
    }
    }
    }
    • 上面程序是个工厂类,通过指定的字符串创建Class对象并创建一个类的实例对象返回。但是这个对象的类型是Object对象,取出实例后需要强制类型转换。如下例:
    1
    Date date = (Date) ObjectFactory.getInstance("java.util.Date");
    • 或者如下:
    1
    String string = (String) ObjectFactory.getInstance("java.util.Date");
    • 上面代码在编译时不会有任何问题,但是运行时将抛出ClassCastException异常,因为程序试图将一个Date对象转换成String对象。
  • 但是泛型的出现后,就可以避免这种情况。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class ObjectFactory {
    public static <T> T getInstance(Class<T> cls) {
    try {
    // 返回使用该Class对象创建的实例
    return cls.newInstance();
    } catch (InstantiationException | IllegalAccessException e) {
    e.printStackTrace();
    return null;
    }
    }

    }
    • 在上面程序的getInstance()方法中传入一个Class<T>参数,这是一个泛型化的Class对象,调用该Class对象的newInstance()方法将返回一个T对象。
    1
    String instance = ObjectFactory.getInstance(String.class);
    • 通过传入String.class便知道T代表String,所以返回的对象是String类型的,避免强制类型转换。当然Class类引入泛型的好处不止这一点,在以后的实际应用中会更加能体会到。

使用反射来获取泛型信息

  • 通过指定类对应的 Class 对象,可以获得该类里包含的所有 Field,不管该 Field 是使用 private 修饰,还是使用 public 修饰。

  • 获得了 Field 对象后,就可以很容易地获得该 Field 的数据类型,即使用如下代码即可获得指定 Field 的类型。

    1
    2
    // 获取 Field 对象 f 的类型
    Class<?> a = f.getType();
  • 但这种方式只对普通类型的 Field 有效。如果该 Field 的类型是有泛型限制的类型,如 Map<String, Integer> 类型,则不能准确地得到该 Field 的泛型参数。

    • 为了获得指定 Field 的泛型类型,应先使用如下方法来获取指定 Field 的类型。
    1
    2
    // 获得 Field 实例的泛型类型
    Type type = f.getGenericType();
  • 然后将 Type 对象强制类型转换为 ParameterizedType 对象,ParameterizedType 代表被参数化的类型,也就是增加了泛型限制的类型。ParameterizedType 类提供了如下两个方法。

    • getRawType():返回没有泛型信息的原始类型。
    • getActualTypeArguments():返回泛型参数的类型。
  • 下面是一个获取泛型类型的完整程序。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    public class GenericTest{
    private Map<String , Integer> score;
    public static void main(String[] args)
    throws Exception{
    Class<GenericTest> clazz = GenericTest.class;
    Field f = clazz.getDeclaredField("score");
    // 直接使用getType()取出Field类型只对普通类型的Field有效
    Class<?> a = f.getType();
    // 下面将看到仅输出java.util.Map
    System.out.println("score的类型是:" + a);
    // 获得Field实例f的泛型类型
    Type gType = f.getGenericType();
    // 如果gType类型是ParameterizedType对象
    if(gType instanceof ParameterizedType){
    // 强制类型转换
    ParameterizedType pType = (ParameterizedType)gType;
    // 获取原始类型
    Type rType = pType.getRawType();
    System.out.println("原始类型是:" + rType);
    // 取得泛型类型的泛型参数
    Type[] tArgs = pType.getActualTypeArguments();
    System.out.println("泛型类型是:");
    for (int i = 0; i < tArgs.length; i++)
    {
    System.out.println("第" + i + "个泛型类型是:" + tArgs[i]);
    }
    } else{
    System.out.println("获取泛型类型出错!");
    }
    }
    }
    • 输出结果:

      1
      2
      3
      4
      5
      score 的类型是: interface java.util.Map
      原始类型是: interface java.util.Map
      泛型类型是:
      第 0 个泛型类型是: class java.lang.String
      第 1 个泛型类型是:class java.lang.Integer
  • 从上面的运行结果可以看出,直接使用Field的getType()方法只能获取普通类型的Field的数据类型:对于增加了泛型参数的类型的 Field,应该使用 getGenericType() 方法来取得其类型。

  • Type 也是 java.lang.reflect 包下的一个接口,该接口代表所有类型的公共高级接口,Class 是 Type 接口的实现类。Type 包括原始类型、参数化类型、数组类型、类型变量和基本类型等。

泛型和反射案例

  • 通过反射获得泛型的实际类型参数

    • 把泛型变量当成方法的参数,利用Method类的getGenericParameterTypes方法来获取泛型的实际类型参数
    • 例子:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    public class GenericTest {

    public static void main(String[] args) throws Exception {
    getParamType();
    }

    /*利用反射获取方法参数的实际参数类型*/
    public static void getParamType() throws NoSuchMethodException{
    Method method = GenericTest.class.getMethod("applyMap",Map.class);
    //获取方法的泛型参数的类型
    Type[] types = method.getGenericParameterTypes();
    System.out.println(types[0]);
    //参数化的类型
    ParameterizedType pType = (ParameterizedType)types[0];
    //原始类型
    System.out.println(pType.getRawType());
    //实际类型参数
    System.out.println(pType.getActualTypeArguments()[0]);
    System.out.println(pType.getActualTypeArguments()[1]);
    }

    /*供测试参数类型的方法*/
    public static void applyMap(Map<Integer,String> map){

    }


    public static void applyMap(ArrayList<? extends Student> list){

    }
    }
  • 输出结果:

    1
    2
    3
    4
    java.util.Map<java.lang.Integer, java.lang.String>
    interface java.util.Map
    class java.lang.Integer
    class java.lang.String
  • 注意问题

    • applyMap(ArrayList<? extends Student> list)该泛型方法,无法使用反射获取参数类型

防止反射序列化攻击单例

  • 枚举单例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public enum Singleton {
    INSTANCE {
    @Override
    protected void read() {
    System.out.println("read");
    }
    @Override
    protected void write() {
    System.out.println("write");
    }
    };
    protected abstract void read();
    protected abstract void write();
    }
  • class文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    public abstract class Singleton extends Enum{
    private Singleton(String s, int i){
    super(s, i);
    }

    protected abstract void read();
    protected abstract void write();
    public static Singleton[] values(){
    Singleton asingleton[];
    int i;
    Singleton asingleton1[];
    System.arraycopy(asingleton = ENUM$VALUES, 0, asingleton1 = new Singleton[i = asingleton.length], 0, i);
    return asingleton1;
    }

    public static Singleton valueOf(String s) {
    return (Singleton)Enum.valueOf(singleton/Singleton, s);
    }

    Singleton(String s, int i, Singleton singleton){
    this(s, i);
    }

    public static final Singleton INSTANCE;
    private static final Singleton ENUM$VALUES[];

    static {
    INSTANCE = new Singleton("INSTANCE", 0){

    protected void read(){
    System.out.println("read");
    }

    protected void write(){
    System.out.println("write");
    }

    };
    ENUM$VALUES = (new Singleton[] {
    INSTANCE
    });
    }
    }
  • 类的修饰abstract,所以没法实例化,反射也无能为力。关于线程安全的保证,其实是通过类加载机制来保证的,我们看看INSTANCE的实例化时机,是在static块中,JVM加载类的过程显然是线程安全的。对于防止反序列化生成新实例的问题还不是很明白,一般的方法我们会在该类中添加上如下方法,不过枚举中也没有显示的写明该方法。

    1
    2
    3
    4
    //readResolve to prevent another instance of Singleton
    private Object readResolve(){
    return INSTANCE;
    }

反射性能

第一个案例

使用静态语言做开发时,方法/函数通常是直接调用的,而不常用反射调用. 因为前者能被编译器检查出各种不符合调用规则的问题,且运行时直接调用的开销最小.

  • 先看一下下面的代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    import java.lang.reflect.Method;
    /**
    * <pre>
    * @author yangchong
    * blog : https://github.com/yangchong211
    * time : 2017/09/16
    * desc :
    * revise:
    * </pre>
    */
    public class Test1 {

    public static void test1() {
    Student student = new Student();
    long startTime = System.currentTimeMillis();
    for (int i = 0; i < 100000000; i++) {
    student.setName("张三");
    }
    long endTime = System.currentTimeMillis();
    System.out.printf("调用普通方法,执行1亿次,耗时%dms\n", endTime - startTime);
    }

    public static void test2() {
    try {
    Student student = new Student();
    Method m = student.getClass().getMethod("setName", String.class);
    long startTime = System.currentTimeMillis();
    for (int i = 0; i < 100000000; i++) {
    m.invoke(student, "张三");
    }
    long endTime = System.currentTimeMillis();
    System.out.printf("调用反射方法,执行1亿次,耗时%dms\n", endTime - startTime);
    } catch (Exception e) {
    e.printStackTrace();
    }
    }

    public static void main(String[] args) {
    test1();
    test2();
    }
    }
  • 然后看一下直接结果

    1
    2
    调用普通方法,执行1亿次,耗时16ms
    调用反射方法,执行1亿次,耗时172ms
  • 思考一下

    • 难道反射和普通setName相差N倍的性能吗?!!
    • 其实并不是,反射是慢,但慢多少这个例子并不能测试出来,这个例子第二个方法由于反射的存在,导致JVM无法优化。
    • 第一个例子结果出入并不大,因为JVM直接把这段代码优化了,JVM直接判定循环中的代码没有对外界造成影响,所以直接忽略调用了。

第二个例子

  • 看下面的代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    import java.lang.reflect.Method;

    public class Bench1 {
    long v;

    public void func0() { v++; }
    public void func1() { v--; }
    public void func2() { v++; }
    public void func3() { v--; }

    public void testInterface() {
    long t = System.nanoTime();
    Runnable[] rs = {
    this::func0,
    this::func1,
    this::func2,
    this::func3,
    };
    for(int i = 0; i < 1_0000_0000; i++)
    rs[i & 3].run(); // 关键调用
    t = (System.nanoTime() - t) / 1_000_000;
    System.out.format("testInterface: %d %dms\n", v, t);
    }

    public void testReflect() throws Exception {
    long t = System.nanoTime();
    Method[] ms = {
    Bench1.class.getMethod("func0"),
    Bench1.class.getMethod("func1"),
    Bench1.class.getMethod("func2"),
    Bench1.class.getMethod("func3"),
    };
    for(int i = 0; i < 1_0000_0000; i++)
    ms[i & 3].invoke(this); // 关键调用
    t = (System.nanoTime() - t) / 1_000_000;
    System.out.format("testReflect : %d %dms\n", v, t);
    }

    public static void main(String[] args) throws Exception {
    Bench1 b;
    b = new Bench1(); // 预热部分
    b.testInterface();
    b = new Bench1();
    b.testReflect();

    b = new Bench1(); // 实测部分
    b.testInterface();
    b = new Bench1();
    b.testReflect();
    }
    }
  • 然后看一下执行时间

    1
    2
    3
    4
    然后为了避免JIT预热影响, 只看后面实测的部分的结果, 并运行5次取最小时间的结果, 如下:

    testInterface: 0 595ms
    testReflect : 0 809ms

    参考

反射实用场景

  • 反射的适用场景是什么?
    • 1).Java的反射机制在做基础框架的时候非常有用,有一句话这么说来着:反射机制是很多Java框架的基石。而一般应用层面很少用,不过这种东西,现在很多开源框架基本都已经给你封装好了,自己基本用不着写。典型的除了之外,还有Spring也用到很多反射机制。经典的就是在xml文件或者properties里面写好了配置,然后在Java类里面解析xml或properties里面的内容,得到一个字符串,然后用反射机制,根据这个字符串获得某个类的Class实例,这样就可以动态配置一些东西,不用每一次都要在代码里面去new或者做其他的事情,以后要改的话直接改配置文件,代码维护起来就很方便了,同时有时候要适应某些需求,Java类里面不一定能直接调用另外的方法,这时候也可以通过反射机制来实现。
    • 2)当你做一个软件可以安装插件的功能,你连插件的类型名称都不知道,你怎么实例化这个对象呢?因为程序是支持插件的(第三方的),在开发的时候并不知道 。所以无法在代码中 New出来 ,但反射可以,通过反射,动态加载程序集,然后读出类,检查标记之后再实例化对象,就可以获得正确的类实例。
    • 3)在编码阶段不知道那个类名,要在运行期从配置文件读取类名, 这时候就没有办法硬编码new ClassName(),而必须用到反射才能创建这个对象.反射的目的就是为了扩展未知的应用。比如你写了一个程序,这个程序定义了一些接口,只要实现了这些接口的dll都可以作为插件来插入到这个程序中。那么怎么实现呢?就可以通过反射来实现。就是把dll加载进内存,然后通过反射的方式来调用dll中的方法。很多工厂模式就是使用的反射。

尽量远离反射

  • 反射:在流行的库如Spring和Gson,Retrofit中,反射自然有其用武之地。不过内省业务代码在很多时候都不是一件好事,原因有很多,一般情况下我总是建议大家不要使用反射。

  • 首先是代码可读性与工具支持。打开熟悉的IDE,寻找你的Java代码的内部依赖,很容易吧。现在,使用反射来替换掉你的代码然后再试一下,结果如何呢?如果通过反射来修改已经封装好的对象状态,那么结果将会变得更加不可控。请看看如下示例代码:博客

    • 比如下面Student为第三方库代码,这个时候如果别人更新setName方法名称,改为setStudentName,那么之前写的反射代码就会出现问题,而且不太好发现。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class Student {
    private String name;
    public Student() {}
    public void setName(String name) {
    this.name = name;
    }
    }


    Student student = new Student();
    Method m = student.getClass().getMethod("setName", String.class);
    m.invoke(student, "张三");
  • 如果这样做就无法得到编译期的安全保证。就像上面这个示例一样,你会发现如果getDeclaredField()方法调用的参数输错了,那么只有在运行期才能发现。要知道的是,寻找运行期Bug的难度要远远超过编译期的Bug。

反射的弊端

  • 反射机制是一种程序自我分析的能力。用于获取一个类的类变量,构造函数,方法,修饰符。
    • 优点:运行期类型的判断,动态类加载,动态代理使用反射。
    • 缺点:性能是一个问题,反射相当于一系列解释操作,通知jvm要做的事情,性能比直接的java代码要慢很多。
  • 反射的弊端有哪些?
    • 反射包括了一些动态类型,所以JVM无法对这些代码进行优化。因此,反射操作的效率要比那些非反射操作低得多。我们应该避免在经常被 执行的代码或对性能要求很高的程序中使用反射。
    • 使用反射技术要求程序必须在一个没有安全限制的环境中运行。如果一个程序必须在有安全限制的环境中运行。
    • 由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方法),所以使用反射可能会导致意料之外的副作用--代码有功能上的错误,降低可移植性。反射代码破坏了抽象性,因此当平台发生改变的时候,代码的行为就有可能也随着变化。

代理

代理分类说明

  • 代理的实现分为:
    • 静态代理:
    • 代理类是在编译时就实现好的。也就是说 Java 编译完成后代理类是一个实际的 class 文件。
    • 动态代理:
    • 代理类是在运行时生成的。也就是说 Java 编译完之后并没有实际的 class 文件,而是在运行时动态生成的类字节码,并加载到JVM中。

什么是动态代理

动态代理定义

  • 给某个对象提供一个代理对象,并由代理对象控制对于原对象的访问,即客户不直接操控原对象,而是通过代理对象间接地操控原对象。
  • 第一个类比:代理对象是中介,原对象是房东的房子,客户就是我们需要找房的人。
    • 房东的房子让中介代理出租,中介可以直接控制房子出租,用户是不能找房东直接租房子的,而是要间接通过中介租房子。
  • 第二个类比:代理对象是售票点,原对象是火车站,客户就是需要买票的乘客。
    • 我们购买火车票可以去火车站买,但是也可以去火车票代售处买,此处的火车票代售处就是火车站购票的代理,即我们在代售点发出买票请求,代售点会把请求发给火车站,火车站把购买成功响应发给代售点,代售点再告诉你。
    • 但是代售点只能买票,不能退票,而火车站能买票也能退票,因此代理对象支持的操作可能和委托对象的操作有所不同。
  • 第三个类比:代理对象是vpn,原对象是国外网站,客户就是需要翻墙上网的人。
    • (1)用户把HTTP请求发给代理;(2)代理把HTTP请求发给web服务器;(3)web服务器把HTTP响应发给代理;(4)代理把HTTP响应发回给用户

动态代理参与者

  • 代理模式的角色分四种:

  • 如下所示

    • 主题接口:Subject 是委托对象和代理对象都共同实现的接口,即代理类的所实现的行为接口。Request() 是委托对象和代理对象共同拥有的方法。
    • 目标对象:ReaSubject 是原对象,也就是被代理的对象。
    • 代理对象:Proxy 是代理对象,用来封装真是主题类的代理类。
    • 客户端 :使用代理类和主题接口完成一些工作。

什么是静态代理

静态代理定义

  • 静态代理:代理类是在编译时就实现好的。也就是说 Java 编译完成后代理类是一个实际的 class 文件。
  • 若代理类在程序运行前就已经存在,那么这种代理方式被成为 静态代理 ,这种情况下的代理类通常都是我们在Java代码中定义的。 通常情况下,静态代理中的代理类和委托类会实现同一接口或是派生自相同的父类。

静态代理伪代码

  • 代码如下所示

    • 可以了解到,静态代理可以通过聚合来实现,让代理类持有一个委托类的引用即可。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    /**
    * 静态代理伪代码
    */
    private void test6() {
    //1.创建委托对象
    RealSubject subject = new RealSubject();
    //2.创建调用处理器对象
    MyProxy p = new MyProxy(subject);
    //3.通过代理对象调用方法
    p.request();
    }

    /**
    * 代理类和委托类会实现接口
    */
    interface Subject{
    void request();
    }

    /**
    * 委托类
    */
    class RealSubject implements Subject{
    @Override
    public void request(){
    System.out.println("request");
    }
    }

    /**
    * 代理
    */
    class Proxy implements Subject{
    private Subject subject;
    public Proxy(Subject subject){
    this.subject = subject;
    }
    @Override
    public void request(){
    System.out.println("PreProcess");
    subject.request();
    System.out.println("PostProcess");
    }
    }

静态代理拓展

  • 考虑一下这个需求:

    • 给委托类增加一个过滤功能,只租房给我们这类逗比程序员。通过静态代理,我们无需修改委托类的代码就可以实现,只需在代理类中的方法中添加一个判断即可如下所示:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class MyProxy implements Subject{
    private Subject subject;
    public MyProxy(Subject subject){
    this.subject = subject;
    }
    @Override
    public void request(){
    //判断是否是逗比程序员
    if (isDouBi){
    System.out.println("PreProcess");
    subject.request();
    System.out.println("PostProcess");
    }
    }
    }
  • 使用代理的第二个优点:

    • 可以实现客户与委托类间的解耦,在不修改委托类代码的情况下能够做一些额外的处理。静态代理的局限在于运行前必须编写好代理类

动态代理

为何需要动态代理

  • 动态代理:代理类是在运行时生成的。也就是说Java编译完之后并没有实际的class文件,而是在运行时动态生成的类字节码,并加载到JVM中。
  • 动态代理是指在运行时动态生成代理类。代理类的字节码将在运行时生成并载入当前代理的ClassLoader。与静态处理类相比,动态类有诸多好处。
    • ①不需要为(RealSubject )写一个形式上完全一样的封装类,假如主题接口(Subject)中的方法很多,为每一个接口写一个代理方法也很麻烦。如果接口有变动,则目标对象和代理类都要修改,不利于系统维护;
    • ②使用一些动态代理的生成方法甚至可以在运行时制定代理类的执行逻辑,从而大大提升系统的灵活性。

先看一个需求

  • 在类比动态代理的时候,假如根据租房的条件,将客户需求分类:比如需要公寓,单间,隔断房,床位,整租房等等,

动态代理组成元素

  • 为其他对象提供一种代理以控制对这个对象的访问。某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以再两者之间起到中介作用。运行阶段才指定代理哪个对象。
  • 组成元素:
    • 抽象类接口
    • 被代理类(具体实现抽象类接口的类)
    • 动态代理类,实际调用被代理类的方法和属性

为何用到反射

  • 先来了解下反射
    • Java 反射机制在程序运行时,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。这种 动态的获取信息 以及 动态调用对象的方法 的功能称为 java 的反射机制。
    • 反射机制很重要的一点就是“运行时”,其使得我们可以在程序运行时加载、探索以及使用编译期间完全未知的 .class 文件。换句话说,Java 程序可以加载一个运行时才得知名称的 .class 文件,然后获悉其完整构造,并生成其对象实体、或对其 fields(变量)设值、或调用其 methods(方法)。

动态代理原理

实现动态代理步骤

  • Java实现动态代理的大致步骤如下:
    • 定义一个委托类和公共接口。
    • 自己定义一个类(调用处理器类,即实现 InvocationHandler 接口),这个类的目的是指定运行时将生成的代理类需要完成的具体任务(包括Preprocess和Postprocess),即代理类调用任何方法都会经过这个调用处理器类。
      • 生成代理对象(当然也会生成代理类),需要为他指定(1)委托对象(2)实现的一系列接口(3)调用处理器类的实例。因此可以看出一个代理对象对应一个委托对象,对应一个调用处理器实例。

重要类介绍

  • Java 实现动态代理主要涉及以下几个类:
    • java.lang.reflect.Proxy: 这是生成代理类的主类,通过 Proxy 类生成的代理类都继承了 Proxy 类,即 DynamicProxyClass extends Proxy。Proxy提供了用户创建动态代理类和代理对象的静态方法,它是所有动态代理类的父类。
    • java.lang.reflect.InvocationHandler: 这里称他为”调用处理器”,他是一个接口,当调用动态代理类中的方法时,将会直接转接到执行自定义的InvocationHandler中的invoke()方法。即我们动态生成的代理类需要完成的具体内容需要自己定义一个类,而这个类必须实现 InvocationHandler 接口,通过重写invoke()方法来执行具体内容。

Proxy类

创建动态代理类

Proxy提供了如下两个方法来创建动态代理类和动态代理实例。

  • static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces) 返回代理类的java.lang.Class对象。

    • 第一个参数是类加载器对象(即哪个类加载器来加载这个代理类到 JVM 的方法区),
    • 第二个参数是接口(表明你这个代理类需要实现哪些接口),
    • 第三个参数是调用处理器类实例(指定代理类中具体要干什么),该代理类将实现interfaces所指定的所有接口,执行代理对象的每个方法时都会被替换执行InvocationHandler对象的invoke方法。
  • static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 返回代理类实例。参数与上述方法一致。

  • 对应上述两种方法创建动态代理对象的方式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    //创建一个InvocationHandler对象
    InvocationHandler handler = new MyInvocationHandler(.args..);
    //使用Proxy生成一个动态代理类
    Class proxyClass = Proxy.getProxyClass(RealSubject.class.getClassLoader(),RealSubject.class.getInterfaces(), handler);
    //获取proxyClass类中一个带InvocationHandler参数的构造器
    Constructor constructor = proxyClass.getConstructor(InvocationHandler.class);
    //调用constructor的newInstance方法来创建动态实例
    RealSubject real = (RealSubject)constructor.newInstance(handler);



    //创建一个InvocationHandler对象
    InvocationHandler handler = new MyInvocationHandler(.args..);
    //使用Proxy直接生成一个动态代理对象
    RealSubject real =Proxy.newProxyInstance(RealSubject.class.getClassLoader(),RealSubject.class.getInterfaces(), handler);
  • newProxyInstance这个方法实际上做了两件事:第一,创建了一个新的类【代理类】,这个类实现了Class[] interfaces中的所有接口,并通过你指定的ClassLoader将生成的类的字节码加载到JVM中,创建Class对象;第二,以你传入的InvocationHandler作为参数创建一个代理类的实例并返回。

静态方法

  • Proxy 类还有一些静态方法,比如:
    • InvocationHandler getInvocationHandler(Object proxy):获得代理对象对应的调用处理器对象。
    • Class getProxyClass(ClassLoader loader, Class[] interfaces):根据类加载器和实现的接口获得代理类。
  • InvocationHandler 接口中有方法:
    • invoke(Object proxy, Method method, Object[] args)
      这个函数是在代理对象调用任何一个方法时都会调用的,方法不同会导致第二个参数method不同,第一个参数是代理对象(表示哪个代理对象调用了method方法),第二个参数是 Method 对象(表示哪个方法被调用了),第三个参数是指定调用方法的参数。

动态代理实现

  • 代码如下所示

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    public class DynamicProxyDemo {
    public static void main(String[] args) {
    //1.创建目标对象
    RealSubject realSubject = new RealSubject();
    //2.创建调用处理器对象
    ProxyHandler handler = new ProxyHandler(realSubject);
    //3.动态生成代理对象
    Subject proxySubject = (Subject)Proxy.newProxyInstance(RealSubject.class.getClassLoader(),
    RealSubject.class.getInterfaces(), handler);
    //4.通过代理对象调用方法
    proxySubject.request();
    }
    }

    /**
    * 主题接口
    */
    interface Subject{
    void request();
    }

    /**
    * 目标对象类
    */
    class RealSubject implements Subject{
    public void request(){
    System.out.println("====RealSubject Request====");
    }
    }
    /**
    * 代理类的调用处理器
    */
    class ProxyHandler implements InvocationHandler{
    private Subject subject;
    public ProxyHandler(Subject subject){
    this.subject = subject;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
    throws Throwable {
    //定义预处理的工作,当然你也可以根据 method 的不同进行不同的预处理工作
    System.out.println("====before====");
    //调用RealSubject中的方法
    Object result = method.invoke(subject, args);
    System.out.println("====after====");
    return result;
    }
    }
  • 可以看到,我们通过newProxyInstance就产生了一个Subject 的实例,即代理类的实例,然后就可以通过Subject .request(),就会调用InvocationHandler中的invoke()方法,传入方法Method对象,以及调用方法的参数,通过Method.invoke调用RealSubject中的方法的request()方法。

  • 同时可以在InvocationHandler中的invoke()方法加入其他执行逻辑。