1. 反射是什么

Java 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性。Java 语言编译之后会生成一个 .class 文件,反射就是通过字节码文件找到某一个类、类中的方法以及属性等。

反射的作用:对于在编译期无法确定使用哪个数据类的场景,通过反射可以在运行期构造出不同的数据类实例

2. 获得 Class 对象的三种方法

对象.getClass()类名.classClass.forName("路径") 是三种获得Class对象的方法。

  • getClass() 接在对象的后面。getClass() 是 Object 类中的方法,而 Object 类是所有 Java 类的父类。
  • .class 接在任意一个 Class 类的后面,在编译期加载(静态加载)。
  • Class.forName 是 Class 类中的一个静态方法,从指定的 classloader 中装载类,返回与给定字符串对应类或接口的 Class 对象,在运行期加载(动态加载)。

关于静态加载和动态加载:

  1. 静态加载的类的源程序在编译时期加载(必须存在),而动态加载的类在编译时期可以缺席(源程序不存在时编译器也不会报错)。

  2. 当类找不到的时候,静态加载方式会抛出异常”NoClassDefFoundError”,而动态加载方式则抛出”ClassNotFoundException”异常。

这三种方法获得的 Class 对象都是一模一样的。在得到 Class 对象之后,我们可以调用一些方法来获取该类的各种信息,包括类名、父类以及它所实现接口的名字等。

Class.forName() 和 ClassLoader 的 loadClass() 之间的区别:

  • Class.forName() 加载类是将类进了初始化,而 ClassLoader 的 loadClass() 方法并没有对类进行初始化,只是把类加载到了虚拟机中。
  • Spring 框架中 IOC 容器的实现就是使用的 ClassLoader 的 loadClass() 方法。
  • 而在我们使用 JDBC 时通常是使用 Class.forName() 方法来加载数据库连接驱动。这是因为在 JDBC 规范中明确要求 Driver (数据库驱动)类必须向 DriverManager 注册自己。

3. 获取一个类的所有信息

获取类中的变量(Field)

  • Field[] getFields():获取类中所有被public修饰的所有变量
  • Field getField(String name):根据变量名获取类中的一个变量,该变量必须被public修饰
  • Field[] getDeclaredFields():获取类中所有的变量,但无法获取继承下来的变量
  • Field getDeclaredField(String name):根据姓名获取类中的某个变量,无法获取继承下来的变量

    获取类中的方法(Method)

  • Method[] getMethods():获取类中被public修饰的所有方法

  • Method getMethod(String name, Class… paramTypes):根据名字和参数类型获取对应方法,该方法必须被public修饰
  • Method[] getDeclaredMethods():获取所有方法,但无法获取继承下来的方法
  • Method getDeclaredMethod(String name, Class… paramTypes):根据名字和参数类型获取对应方法,无法获取继承下来的方法

    获取类的构造器(Constructor)

  • Constuctor[] getConstructors():获取类中所有被public修饰的构造器

  • Constructor getConstructor(Class… paramTypes):根据参数类型获取类中某个构造器,该构造器必须被public修饰
  • Constructor[] getDeclaredConstructors():获取类中所有构造器
  • Constructor getDeclaredConstructor(class… paramTypes):根据参数类型获取对应的构造器

    获取注解

  • Annotation[] getAnnotations():获取该对象上的所有注解

  • Annotation getAnnotation(Class annotaionClass):传入注解类型,获取该对象上的特定一个注解
  • Annotation[] getDeclaredAnnotations():获取该对象上的显式标注的所有注解,无法获取继承下来的注解
  • Annotation getDeclaredAnnotation(Class annotationClass):根据注解类型,获取该对象上的特定一个注解,无法获取继承下来的注解

    4. 通过反射获取泛型类型

    1. public class Demo {
    2. private List<Integer> list1;
    3. public static void getFieldGenericType() {
    4. try {
    5. Field field = Demo.class.getDeclaredField("list1");
    6. Type type = field.getGenericType();
    7. ParameterizedType pt = (ParameterizedType) type; //取得field的type
    8. Type[] genericTypes = pt.getActualTypeArguments(); //强转成具体的实现类
    9. System.out.println(genericTypes[0]); //取得包含的泛型类型
    10. } catch (NoSuchFieldException e) {
    11. e.printStackTrace();
    12. }
    13. }
    14. public static void main(String[] args) {
    15. getFieldGenericType();
    16. }
    17. }

    5. 反射的优缺点

  • 优点:反射可以提供运行时的类信息,并且这个类可以在运行时才加载进来,class.forName() 动态加载类,提高代码的灵活度。

  • 缺点:如果一个功能可以不用反射完成,那么最好就不用。反射有以下几个问题:
    1. 性能开销:反射涉及了动态类型的解析,所以 JVM 无法对这些代码进行优化。因此,反射操作的效率要比那些非反射操作低得多。不过在单次调用反射的过程中,性能损耗可以忽略不计。但是相比于反射带来的便利,如果不是高并发需要十分频繁的调用,反射的性能损耗可以忽略,并且反射性能损耗也有方法优化降低。(如 Spring 中的 BeanUtils 使用缓存进行优化)
    2. 安全限制:使用反射技术要求程序必须在一个没有安全限制的环境中运行。如果一个程序必须在有安全限制的环境中运行,如 Applet,那么这就是个问题了。
    3. 内部暴露:由于反射允许代码执行一些在正常情况下不被允许的操作(比如:访问私有的属性和方法),所以使用反射可能会导致意料之外的副作用,这可能导致代码功能失调并破坏可移植性。反射代码破坏了抽象性,因此当平台发生改变的时候,代码的行为就有可能也随着变化。

      6. 反射和内省(Introspector)的区别

      反射就像给类照镜子,这个的所有信息会毫无保留的反射到镜子中,将这个类的所有信息照出来,能照出来就是有,照不出来就是没有,得到的东西都是客观真实存在的。

内省是基于反射实现的,主要用于操作 JavaBean,相比反射使用起来要方便一些。可以获取 bean 的 getter/setter 方法,也就是说,只要 JavaBean 有getXxx()方法,不管这个 Bean 有没有 Xxx 属性,如果使用内省我们都认为它有。

7. new 关键字和 newInstance 的区别

  1. 两者创建对象的方式不同,前者是实用类的加载机制,后者则是直接创建一个类。
  2. newInstance 创建类时这个类必须已经加载过且已经连接(Class.forName(“A”)这个过程),new 创建类不需要这个类加载过。
  3. newInstance 是弱类型(GC时回收对象的限制条件很低,容易被回收)、相对低效。而new是强类型(GC不会自动回收,只有所有的指向对象的引用被移除是才会被回收,若对象生命周期已经结束,但引用没有被移除,经常会出现内存溢出),相对高效。
  4. newInstance实例化对象只能调用无参构造方法(如果重写了一个带参构造方法,想要使用newInstance,则必须指定一个无参构造方法,否则会报初始化错误)。而new能调用任何public的构造方法。

    一般来说,当我们不知道初始化哪个具体的类时,可以使用 Class 类的 newInstance 方法。

8. 总结

  • 反射的思想:反射就像是一面镜子一样,在运行时才看到自己是谁,可获取到自己的信息,甚至实例化对象。
  • 反射的作用在运行时才确定实例化对象,使程序更加健壮,面对需求变更时,可以最大程度地做到不修改程序源码应对不同的场景,实例化不同类型的对象。
  • 反射的特点:增加程序的灵活性、破坏类的封装性以及性能损耗。
  • 反射的应用场景
    • Spring的 IOC 容器
    • 反射+工厂模式 使工厂类更稳定
    • JDBC连接数据库时加载驱动类。

      参考

  1. Java中newInstance()和new()区别
  2. Java中newInstance()和new()