目录介绍

  • 8.0.0.1 反射的原理是什么?有哪些途径获取到Class对象,Class类的含义和作用是什么?什么是class类?
  • 8.0.0.2 反射的组成包含哪些方面?反射中类的加载过程大概是怎样的?Java反射的应用场景有哪些?
  • 8.0.0.3 java反射机制提供了什么功能?反射具有暴力访问权限,如何防止反射序列化攻击单例?
  • 8.0.0.4 通过反射获得泛型的实际类型参数?反射获取构造方法,变量,方法的方法是哪些?
  • 8.0.0.5 getGenericParameterTypes 与 getParameterTypes区别?
  • 8.0.0.6 反射有什么作用和应用?反射和注解相比,为何反射消耗性能,有什么优缺点?
  • 8.0.0.7 反射的适用场景是什么?为什么建议尽量远离反射?反射的弊端有哪些?
  • 8.0.0.9 泛型和反射有何联系?使用反射来获取泛型信息?getType和getGenericType有何区别?
  • 8.0.1.1 使用反射和不使用反射有何区别,执行方法的速度是怎样的?两者本质有何区别?
  • 8.0.1.2 有哪些方式可以提高反射效率?为何反射消耗性能?究竟是怎么影响的,举例说明?

8.0.0.1 反射的原理是什么?有哪些途径获取到Class对象,Class类的含义和作用是什么?什么是class类?

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

    • 每一个Class类的对象就代表了一种被加载进入JVM的类,它代表了该类的一种信息映射。开发者可以通过3种途径获取到Class对象。
    • 技术博客大总结
    • 第一种方式:Class的forName()方法的返回值就是Class类型,也就是动态导入类的Class对象的引用。
    • public static Class<?> forName(String className) throws ClassNotFoundException
    • 第二种方式:每个类都会有一个名称为Class的静态属性,通过它也是可以获取到Class对象的,示例代码如下:

      1. Class<Student> clazz = Student.class; // 访问Student类的class属性
    • 第三种方式:Object类中有一个名为getClass的成员方法,它返回的是对象的运行时类的Class对象。因为Object类是所有类的父类,所以,所有的对象都可以使用该方法得到它运行时类的Class对象,示例代码如下:

      1. Student stu = new Student();
      2. Class<Student> clazz = stu.getClass(); // 调用Student对象的getName方法
  • 什么是class类?博客

    • .class文件:是由一个.java文件通过编译过程生成的.class文件。Class对象:当Java虚拟机加载一个.class文件到内存时,都会生成一个对应的Class对象,注意的是这个Class对象有且只有一个存在于内存当中。

8.0.0.2 反射的组成包含哪些方面?反射中类的加载过程大概是怎样的?Java反射的应用场景有哪些?

  • 反射的组成包含哪些方面?
    • 由于反射最终也必须有类参与,因此反射的组成一般有下面几个方面组成:
      • 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反射的应用场景有哪些?博客
    • 1.逆向代码 ,例如反编译
    • 2.与注解相结合的框架 例如Retrofit
    • 3.单纯的反射机制应用框架 例如EventBus
    • 4.动态生成类框架,例如Gson
  • 反射的作用有哪些?
    • 前面只是说了反射是一种具有与Java类进行动态交互能力的一种机制,在Java和Android开发中,一般情况下下面几种场景会用到反射机制.
      • 需要访问隐藏属性或者调用方法改变程序原来的逻辑,这个在开发中很常见的,由于一些原因,系统并没有开放一些接口出来,这个时候利用反射是一个有效的解决方法
      • 自定义注解,注解就是在运行时利用反射机制来获取的。
      • 在开发中动态加载类,比如在Android中的动态加载解决65k问题等等,模块化和插件化都离不开反射,离开了反射寸步难行。博客

8.0.0.3 java反射机制提供了什么功能?反射具有暴力访问权限,如何避免反射攻击?

  • java反射机制提供了什么功能?
    • 在运行时能够判断任意一个对象所属的类
    • 在运行时构造任意一个类的对象
    • 在运行时判断任意一个类所具有的成员变量和方法
    • 在运行时调用任一对象的方法技术博客大总结
    • 在运行时创建新类对象
  • 发射具有暴力访问权限,如何避免反射攻击?
  • 如何防止反射序列化攻击单例

    • 枚举单例

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

      1. public abstract class Singleton extends Enum{
      2. private Singleton(String s, int i){
      3. super(s, i);
      4. }
      5. protected abstract void read();
      6. protected abstract void write();
      7. public static Singleton[] values(){
      8. Singleton asingleton[];
      9. int i;
      10. Singleton asingleton1[];
      11. System.arraycopy(asingleton = ENUM$VALUES, 0, asingleton1 = new Singleton[i = asingleton.length], 0, i);
      12. return asingleton1;
      13. }
      14. public static Singleton valueOf(String s) {
      15. return (Singleton)Enum.valueOf(singleton/Singleton, s);
      16. }
      17. Singleton(String s, int i, Singleton singleton){
      18. this(s, i);
      19. }
      20. public static final Singleton INSTANCE;
      21. private static final Singleton ENUM$VALUES[];
      22. static {
      23. INSTANCE = new Singleton("INSTANCE", 0){
      24. protected void read(){
      25. System.out.println("read");
      26. }
      27. protected void write(){
      28. System.out.println("write");
      29. }
      30. };
      31. ENUM$VALUES = (new Singleton[] {
      32. INSTANCE
      33. });
      34. }
      35. }
      • 类的修饰abstract,所以没法实例化,反射也无能为力。关于线程安全的保证,其实是通过类加载机制来保证的,我们看看INSTANCE的实例化时机,是在static块中,JVM加载类的过程显然是线程安全的。对于防止反序列化生成新实例的问题还不是很明白,一般的方法我们会在该类中添加上如下方法,不过枚举中也没有显示的写明该方法。技术博客大总结
        1. //readResolve to prevent another instance of Singleton
        2. private Object readResolve(){
        3. return INSTANCE;
        4. }

8.0.0.4 通过反射获得泛型的实际类型参数?反射获取构造方法,变量,方法的方法是哪些?

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

    • 把泛型变量当成方法的参数,利用Method类的getGenericParameterTypes方法来获取泛型的实际类型参数
    • 例子: ``` 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 map){

      }

}

  1. - 输出结果:[博客](https://github.com/yangchong211/YCBlogs)

java.util.Map interface java.util.Map class java.lang.Integer class java.lang.String

  1. - 反射获取构造方法,变量,方法的方法是哪些?[技术博客大总结](https://github.com/yangchong211/YCBlogs)
  2. - 在反射包中,我们常用的类主要有Constructor类表示的是Class 对象所表示的类的构造方法,利用它可以在运行时动态创建对象、Field表示Class对象所表示的类的成员变量,通过它可以在运行时动态修改成员变量的属性值(包含private)、Method表示Class对象所表示的类的成员方法,通过它可以动态调用对象的方法(包含private)。
  3. <a name="bf17c9b6"></a>
  4. #### 8.0.0.5 getGenericParameterTypes 与 getParameterTypes区别?
  5. - getGenericParameterTypes getParameterTypes区别?
  6. - getGenericParameterTypes getParameterTypes 都是获取构成函数的参数类型
  7. - 前者返回的是Type类型,后者返回的是Class类型,由于Type顶级接口,Class也实现了该接口,因此Class类是Type的子类,Type 表示的全部类型而每个Class对象表示一个具体类型的实例,如String.class仅代表String类型。由此看来Type Class 表示类型几乎是相同的,只不过Type表示的范围比Class要广得多而已。当然Type还有其他子类,如:
  8. - TypeVariable:表示类型参数,可以有上界,比如:T extends Number
  9. - ParameterizedType:表示参数化的类型,有原始类型和具体的类型参数,比如:List
  10. - WildcardType:表示通配符类型,比如:?, ? extends Number, ? super Integer
  11. <a name="34a67e7a"></a>
  12. #### 8.0.0.6 反射有什么作用和应用?反射和注解相比,为何反射消耗性能,有什么优缺点?
  13. - 反射有什么作用和应用?
  14. - 含义:在运行状态中,对于任意一个类都能知道它的所有属性和方法,对于任何一个对象都能够调用它的任何一个方法和属性。
  15. - 功能:动态性,体现在:在运行时判断任意一个类所具有的属性和方法; 在运行时判断任意一个对象所属的类;在运行时构造任意一个类的对象;在运行时调用任意一个对象的方法;生成动态代理
  16. - 为何反射消耗性能
  17. - [技术博客大总结](https://github.com/yangchong211/YCBlogs)
  18. - 有什么优缺点
  19. - 优点
  20. - 动态编译:运行时确定类型,绑定对象。动态编译最大限度的发挥了java的灵活性,体现了多态的应用,有利于降低类之间的耦合性。
  21. - 反射机制的优点就是可以实现动态创建对象和编译,体现出很大的灵活性,特别是在开发中。它的灵活性就表现的十分明显。比如,一个大型的软件,不可能一次就把把它设计的很完美,当这个程序编译后,发布了,当发现需要更新某些功能时,我们不可能要用户把以前的卸载,再重新安装新的版本,假如这样的话,这个软件肯定是没有多少人用的。采用静态的话,需要把整个程序重新编译一次才可以实现功能的更新,而采用反射机制的话,它就可以不用卸载,只需要在运行时才动态的创建和编译,就可以实现该功能。
  22. <a name="242d23dc"></a>
  23. #### 8.0.0.7 反射的适用场景是什么?为什么建议尽量远离反射?反射的弊端有哪些?
  24. - 反射的适用场景是什么?
  25. - 1).Java的反射机制在做基础框架的时候非常有用,有一句话这么说来着:反射机制是很多Java框架的基石。而一般应用层面很少用,不过这种东西,现在很多开源框架基本都已经给你封装好了,自己基本用不着写。典型的除了之外,还有Spring也用到很多反射机制。经典的就是在xml文件或者properties里面写好了配置,然后在Java类里面解析xmlproperties里面的内容,得到一个字符串,然后用反射机制,根据这个字符串获得某个类的Class实例,这样就可以动态配置一些东西,不用每一次都要在代码里面去new或者做其他的事情,以后要改的话直接改配置文件,代码维护起来就很方便了,同时有时候要适应某些需求,Java类里面不一定能直接调用另外的方法,这时候也可以通过反射机制来实现。
  26. - 2)当你做一个软件可以安装插件的功能,你连插件的类型名称都不知道,你怎么实例化这个对象呢?因为程序是支持插件的(第三方的),在开发的时候并不知道 。所以无法在代码中 New出来 ,但反射可以,通过反射,动态加载程序集,然后读出类,检查标记之后再实例化对象,就可以获得正确的类实例。
  27. - 3)在编码阶段不知道那个类名,要在运行期从配置文件读取类名, 这时候就没有办法硬编码new ClassName(),而必须用到反射才能创建这个对象.反射的目的就是为了扩展未知的应用。比如你写了一个程序,这个程序定义了一些接口,只要实现了这些接口的dll都可以作为插件来插入到这个程序中。那么怎么实现呢?就可以通过反射来实现。就是把dll加载进内存,然后通过反射的方式来调用dll中的方法。很多工厂模式就是使用的反射。 [博客](https://github.com/yangchong211/YCBlogs)
  28. - 为什么建议尽量远离反射
  29. - 反射:在流行的库如SpringGsonRetrofit中,反射自然有其用武之地。不过内省业务代码在很多时候都不是一件好事,原因有很多,一般情况下我总是建议大家不要使用反射。
  30. - 首先是代码可读性与工具支持。打开熟悉的IDE,寻找你的Java代码的内部依赖,很容易吧。现在,使用反射来替换掉你的代码然后再试一下,结果如何呢?如果通过反射来修改已经封装好的对象状态,那么结果将会变得更加不可控。请看看如下示例代码:
  31. - 比如下面Student为第三方库代码,这个时候如果别人更新setName方法名称,改为setStudentName,那么之前写的反射代码就会出现问题,而且不太好发现。

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, “张三”);

  1. - 如果这样做就无法得到编译期的安全保证。就像上面这个示例一样,你会发现如果getDeclaredField()方法调用的参数输错了,那么只有在运行期才能发现。要知道的是,寻找运行期Bug的难度要远远超过编译期的Bug
  2. - 反射的弊端有哪些?
  3. - 反射包括了一些动态类型,所以JVM无法对这些代码进行优化。因此,反射操作的效率要比那些非反射操作低得多。我们应该避免在经常被 执行的代码或对性能要求很高的程序中使用反射。
  4. - 使用反射技术要求程序必须在一个没有安全限制的环境中运行。如果一个程序必须在有安全限制的环境中运行。
  5. - 由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方法),所以使用反射可能会导致意料之外的副作用--代码有功能上的错误,降低可移植性。反射代码破坏了抽象性,因此当平台发生改变的时候,代码的行为就有可能也随着变化。[博客](https://github.com/yangchong211/YCBlogs)
  6. <a name="82006ea5"></a>
  7. #### 8.0.0.9 泛型和反射有何联系?使用反射来获取泛型信息?getType和getGenericType有何区别?
  8. - 泛型和反射有何联系
  9. - JDK 1.5 后,Java中引入泛型机制,Class类也增加了泛型功能,从而允许使用泛型来限制Class类,例如:String.class的类型实际上是Class<String>。如果Class对应的类暂时未知,则使用Class<?>(?是通配符)。通过反射中使用泛型,可以避免使用反射生成的对象需要强制类型转换。
  10. - 泛型的好处众多,最主要的一点就是避免类型转换,防止出现ClassCastException,即类型转换异常。

public class ObjectFactory { public static T getInstance(Class cls) { try { // 返回使用该Class对象创建的实例 return cls.newInstance(); } catch (InstantiationException | IllegalAccessException e) { e.printStackTrace(); return null; } }

}

  1. - 在上面程序的getInstance()方法中传入一个Class<T>参数,这是一个泛型化的Class对象,调用该Class对象的newInstance()方法将返回一个T对象。

String instance = ObjectFactory.getInstance(String.class);

  1. - 通过传入`String.class`便知道T代表String,所以返回的对象是String类型的,避免强制类型转换。当然Class类引入泛型的好处不止这一点,在以后的实际应用中会更加能体会到。
  2. - 使用反射来获取泛型信息
  3. - 通过指定类对应的 Class 对象,可以获得该类里包含的所有 Field,不管该 Field 是使用 private 修饰,还是使用 public 修饰。
  4. - 获得了 Field 对象后,就可以很容易地获得该 Field 的数据类型,即使用如下代码即可获得指定 Field 的类型。[博客](https://github.com/yangchong211/YCBlogs)

// 获取 Field 对象 f 的类型 Class<?> a = f.getType();

  1. - 但这种方式只对普通类型的 Field 有效。如果该 Field 的类型是有泛型限制的类型,如 Map<String, Integer> 类型,则不能准确地得到该 Field 的泛型参数。
  2. - 为了获得指定 Field 的泛型类型,应先使用如下方法来获取指定 Field 的类型。
  3. ```java
  4. // 获得 Field 实例的泛型类型
  5. Type type = f.getGenericType();
  • 然后将 Type 对象强制类型转换为 ParameterizedType 对象,ParameterizedType 代表被参数化的类型,也就是增加了泛型限制的类型。ParameterizedType 类提供了如下两个方法。
    • getRawType():返回没有泛型信息的原始类型。
    • getActualTypeArguments():返回泛型参数的类型。博客
  • 下面是一个获取泛型类型的完整程序。

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

      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 包括原始类型、参数化类型、数组类型、类型变量和基本类型等。

8.0.1.0 如何通过反射获得泛型的实际类型参数?如何获取

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

    • 把泛型变量当成方法的参数,利用Method类的getGenericParameterTypes方法来获取泛型的实际类型参数
    • 例子:博客 ``` 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 map){

      }

}

  1. - 输出结果:

java.util.Map interface java.util.Map class java.lang.Integer class java.lang.String

  1. <a name="8bbc1ed6"></a>
  2. #### 8.0.1.1 使用反射和不使用反射有何区别,执行方法的速度是怎样的?两者本质有何区别?
  3. - 先看一下下面的代码

import java.lang.reflect.Method; /*

  1. */<br />public class Test1 {
  1. public static void test1() {
  2. Student student = new Student();
  3. long startTime = System.currentTimeMillis();
  4. for (int i = 0; i < 1000000000; i++) {
  5. student.setName("张三");
  6. }
  7. long endTime = System.currentTimeMillis();
  8. System.out.printf("调用普通方法,执行1亿次,耗时%dms\n", endTime - startTime);
  9. }
  10. public static void test2() {
  11. try {
  12. Student student = new Student();
  13. Method m = student.getClass().getMethod("setName", String.class);
  14. long startTime = System.currentTimeMillis();
  15. for (int i = 0; i < 1000000000; i++) {
  16. m.invoke(student, "张三");
  17. }
  18. long endTime = System.currentTimeMillis();
  19. System.out.printf("调用反射方法,执行1亿次,耗时%dms\n", endTime - startTime);
  20. } catch (Exception e) {
  21. e.printStackTrace();
  22. }
  23. }
  24. public static void main(String[] args) {
  25. test1();
  26. test2();
  27. }

}

  1. ```
  2. - 然后看一下直接结果

调用普通方法,执行1亿次,耗时35ms 调用反射方法,执行1亿次,耗时1584ms

  1. <a name="febe9b4e"></a>
  2. #### 8.0.1.2 有哪些方式可以提高反射效率?为何反射消耗性能?究竟是怎么影响的,举例说明?
  3. - 有哪些方式可以提高反射效率?
  4. - 善用API:比如,尽量不要getMethods()后再遍历筛选,而直接用getMethod(methodName)来根据方法名获取方法。
  5. - [技术博客大总结](https://github.com/yangchong211/YCBlogs)
  6. - 适当使用缓存:比如,需要多次动态创建一个类的实例的时候,有缓存的写法会比没有缓存要快很多:

// 1. 没有缓存 void createInstance(String className){ return Class.forName(className).newInstance(); }

// 2. 缓存forName的结果 void createInstance(String className){ cachedClass = cache.get(className); if (cachedClass == null){ cachedClass = Class.forName(className); cache.set(className, cachedClass); } return cachedClass.newInstance(); } ```

  • 为何反射消耗性能?