Java 从入门到精通-反射机制

导读

  Java反射机制是开发者迈向结构化开发的重要一步,同时掌握了反射机制也就掌握了所有框架的核心实现思想。

认识反射机制

简单例子

  通过以上的程序就会发现,除了对象的正向处理操作之外,还可以通过getClass()方法来获取一个类对应的完整的信息的结构,而这就是反射的开始。

Class类对象实例化

  在整个反射机制之中,Class类是整个反射操作的源头所在,如果现在可以获取Class类的对象,那么就可以进行所有的更加深层次的反射操作(上面案例仅仅是根据实例化对象的Class获取了类的基本名称)。

  在Java的处理机制之中,实际上会有三种方式可以获取Class类的实例化对象。

方式一、

  由于在Object类中提供有getClass()方法,所以任意的实例化对象都可以通过此方法来获取Class类的对象实例。

方式二、

  在Java处理过程之中,可以直接使用“类名称.class”的形式直接在没有产生实例化对象的时候获取Class类的实例。

  这个时候输出会直接通过toString()方法来获取相关的对象完整信息。

方式三、

  在Class类的内部提供一个根据“类名称”字符串实现类反射加载的处理方法

 public static Class<?> forName(String className) throws ClassNotFoundException {}

  在之前获取Class类对象的情况下都必须获取类本身对应的程序包,但是如果使用了"Class.forName()"方法进行Class类对象实例化获取的时候,就可以直接将类名称以字符串的形式定义在程序之中。

  这个时候就通过字符串实现了类的加载,但是需要明确的是,以上的这几点处理语法在整个项目的实际开发过程之中全部都有可能使用到,不可能做一个优先级区分。

反射实例化类对象

  上面三种方式,我们已经可以得到三种实例化Class类对象的方法,但是不理解的是,为什么我们要分析这三种方法,或者是为什么要获取Class类的实例化对象呢?

反射对象实例化

  在Java之中如果要想产生一个类的实例化对象,那么你给要通过关键字new进行构造方法的调用,随后再通过该对象进行具体的类的结构操作,可以除了这种关键字new之外,如果此时已经获得了Class类的对象实例,那么就可以通过Class类的如下方法来实现类对象的实例化处理。

传统对象实例化

反射对象实例化

  通过以上两个代码的对比可以发现,使用关键字new或者使用反射机制中提供newInstance()两个方法都可以实现类对象实例化处理,这样就意味着从此之后可以不局限于关键字“new”的使用。JDK 1.9之后传统所使用的newInstance()方法不推荐使用了,变为了以下使用方式

class.getDeclaredConstructor().newInstance();

  在JDK 1.9之后如果要想通过Class类对象获取其他类的实例,那么就需要进行方法的更换,但是又另外一点需要注意的是,当通过Class类对象获取指定类实例的时候,newInstance()方法所返回的数据类型为Object,那么这个时候就需要进行一些对象的向下转型处理(对象的向下会存在有安全隐患)

  需要注意的是,虽然以上的操作可以通过向下转型获取指定类型的对象实例,但是这种操作的代码是存在有设计上问题的,之所以使用反射很大的程度上是不希望进行完整类信息的导入,但是如果仅仅是按照如上的方式进行处理,那么如果真的有一些其他包的类,则依然会出现导入包的情况。

  当有了反射机制的支持之后,那么就可以得到第二种对象实例化的方案,而这种方案之中主要依靠的是Class完成。

反射与工厂设计模式

  通过反射可以获取类的实例化对象,但是现在就需要去思考为什么要提供反射的机制来获取实例化对象,或者说如果直接使用关键字new有什么问题嘛?如果要想回答这个问题最佳的做法是通过工厂设计模式来进行分析。

  但是如果说此时IPeople接口里面有几万个子类呢?此时Factory类维护起来是不是很麻烦?所以这种传统的静态工厂类是不可能满足于现实的项目开发要求的,最佳的做法要采用动态工厂类,反射机制就可以登场了。

  在使用反射操作的时候只需要根据字符串(类名称)获取Class类的实例化对象之后就可以直接反射实例化对象处理,这样的操作最适合完成工厂设计的改良。

通过动态工厂设计模式解决当前的设计问题:

  此时工厂类完全变为了一种独立的操作模式,不管你的项目中IPeople接口到底会产生多少个子类,那么对于整个的工厂类来讲都没有任何的区别,只要给出类的完全路径即可,并且该类属于IPeople接口的子类,就都可以动态实例化。

反射机制与单例设计模式

  可以看到在JVM进程之中某一个类只允许提供唯一的一个实例化对象。

 线程安全的单例模式

 反射获取类结构信息

  反射机制除了可以通过Class类的方式获取一个类的实例化对象之外,其最大的特点还可以实现整个类结果的剖析。例如:该类的父类、他实现的父接口、类中的构造方法、成员属性或者普通方法等等。

获取类结构信息

  如果要进行一个类的定义,那么在这个类中往往会存在有程序所处的包、一个类所继承的父类或者是相关的实现接口,那么这些信息都可以通过Class类直接获取,在Class中提供如下的几个方法。

  1. public Package getPackage(){} ==>获取指定类的所在包
  2. public Class<? super T> getSuperclass(){} ==>获取实现父类
  3.  public Class<?>[] getInterfaces(){} ==>获取所有实现的全部接口

  在整个的程序之中利用Class这种处理结构,就可以持续进行当前类继承关系的剖析,这样的操作的结构就是对已有类执行反射的处理过程。之所以现在可以实现这样的功能,主要的原因是针对于生成的二进制字节码文件进行的处理。

反射调用构造方法

  在一个类中会存在有若干个构造方法的信息,那么这样就在Class类里面可以基于反射机制来获取一个类中全部已经存在的构造方法,具体的操作方法如下。

  1. public Constructor<T> getConstructor(Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException  -->根据指定参数类型获取指定构造方法对象

  2. public Constructor<?>[] getConstructors() throws SecurityException  -->获取类中全部构造方法,只能访问public构造方法的访问权限
  3. public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException  -->获取类中指定参数类型构造方法

  4. public Constructor<?>[] getDeclaredConstructors() throws SecurityException -->获取全部构造方法,可以获得类中全部构造方法

  可以发现在Class类中对于构造方法信息的获取分为了两组的方法名称,之家有什么区别呢?

  在Java反射机制之中,每一个Constrcutor类对象实例实际上都会描述一个对应的构造方法信息,于是可以使用Constructor类中的如下方法进行构造方法的处理

  1. public String getName()  -->获取构造方法的名称
  2. public int getParameterCount()  --> 获取方法中的参数个数
  3. public TypeVariable<?>[] getTypeParameters()  -->获取构造方法的参数类型
  4. public T newInstance(Object... initargs)  -->调用构造方法进行对象的反射实例化

  获取构造方法的主要目的是进行指定有参构造的对象实例化处理操作。

 反射调用方法

  在一个类中处理构造之外还会存在有许多类中提供的方法,那么在这种情况下,所有的方法信息也是可以通过Class类的对象反射获取的,使用如下方法获取即可

  1. public Method getMethod(String name, Class<?>... parameterTypes)  -->获取类中的public访问权限定义的指定方法
  2. public Method[] getMethods()  -->获取类中所有定义的public方法
  3. public Method getDeclaredMethod(String name, Class<?>... parameterTypes)  -->获取本类中的指定参数的方法,不区分访问控制权限
  4. public Method[] getDeclaredMethods()  -->获取本类中所有方法(不区分访问控制权限)

  在Java反射机制运行过程之中,每一个方法都通过Method类的对象实例来进行包装,这种情况下如果要想进行方法更深入层次的调用,就需要去研究Method类的重要组成,在Method类中提供有如下几个常用方法

  1. public Class<?> getReturnType()  -->获取方法的返回值类型
  2. public Type[] getGenericParameterTypes()  -->获取方法的参数类型
  3. public Type[] getGenericExceptionTypes()  -->获取方法中抛出的异常类型
  4. public Object invoke(Object obj, Object... args)  -->方法的调用
  5. public int getModifiers()  -->方法的访问修饰符

  通过反射获取类中的全部的方法的信息内容,在之前进行方法内容输出的时候所采用的方式是由Method类提供的toString()方法,我们现在可以实现所有方法信息的获取

package com.cyb.demo;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;

class People { // People类中的构造方法使用不同的访问权限
public void Speak() {}
private String Run(String name) throws Exception{
    return name+"正在跑。。。";
}
public String SelfInfo(String name,int age) throws RuntimeException,Exception{
    return "我叫:"+name+",今年:"+age+"岁了";
}
}

public class Demo {
    public static void main(String[] args) throws Exception {
        Class<?> clazz = Class.forName("com.cyb.demo.People");
        Method[] methods = clazz.getDeclaredMethods();
        for(Method m:methods) {
            System.out.print(Modifier.toString(m.getModifiers())+" "); //方法的修饰符
            System.out.print(m.getGenericReturnType().getTypeName()+" "); //返回值类型
            System.out.print(m.getName()+" ("); //方法的名称
            Type[] parameterTypes = m.getGenericParameterTypes();
            for (int i = 0; i < parameterTypes.length; i++) {
                if (i>0) {
                    System.out.print(",");
                }
                System.out.print(parameterTypes[i].getTypeName()+" arg"+i);
            }
            System.out.print(")");
            Type[] exceptionTypes = m.getGenericExceptionTypes(); //获取所有抛出的异常信息
            if (exceptionTypes.length>0) {
                System.out.print(" throws"); //输出throws信息
                for (int x = 0; x < exceptionTypes.length; x++) {
                    if (x>0) {
                        System.out.print(", ");
                    }
                    System.out.print(exceptionTypes[x].getTypeName());
                }
            }
            System.out.println();//换行
        }
    }
}

  在实际项目的开发过程之中,使用Method类的对象最大的用途并不是进行方法结构的剖析(Method方法缺陷就是无法获取参数具体名称定义),最大的用途在于可以实现方法的反射调用

  使用如上的形式代替掉传统的关键字new以及明确的“对象.方法()”形式,本质上来将就是为了进行解耦合设计。

反射调用成员属性

  类中除了提供有构造还有方法之外,最为重要的概念就是属性,因为在不同的对象里面所保存的内容就属于属性的信息,属性严格来讲在Java中成为成员,所以如果要想获得所有程序的信息,就需要通过Class类的对象来完成。

  1. public Field[] getFields()  -->获取所有继承而来的public成员
  2. public Field getField(String name)  -->获取一个指定名称的成员
  3. public Field[] getDeclaredFields()  -->获取本类定义的全部成员
  4. public Field getDeclaredField(String name)  -->获取本类中指定名称的成员对象

  对于成员来讲一定分为本类成员、父类成员以及接口中的常量成员等信息,那么下面来获取这些信息。

  在实际项目开发过程之中,如果使用反射进行处理的时候,一般来讲都会采用“getDeclaredFields、getDeclaredField”,方式来获取本类的操作属性(即便使用了private封装也可以返回),所有的成员在Java中都使用Field类型来进行描述。

Field类的相关方法

  1. public Object get(Object obj)  -->获取指定成员的内容
  2. public void set(Object obj, Object value)  -->设置成员的属性内容
  3. public String getName()  -->获取成员名称
  4. public Class<?> getType()  -->获取成员类型
  5. public void setAccessible(boolean flag)  -->设置封装的可见性

  如果在一个类的对象里面要进行成员的操作,那么一定要首先获取本类的实例化对象,然后才可以进行,在Field类中就直接提供有set()方法设置属性,get()方法获取属性的操作。

通过Field实现属性的直接操作

  从上面例子我们可以看到,童话里都是骗人的,设置为private私有属性,直接可以用反射暴力(setAccessible)打开可见性。对于属性的操作一般还是建议通过setter、getter方法完成,Field类只是表示有这种能力,但并不推荐。

Unsage工具类

  java.lang.reflect 本身所描述的是一种反射的基本操作功能,除了这个基本的功能之外,在JDK里面还提供有一个比较特殊的反射类:sun.misc.Unsafe (按照Java开发的原则来将,所有以“sun”开头的包一般都不建议调用,因为这些包都会与操作系统的底层有关,可以直接通过C++代码进行操作),其中Unsafe类可以实现在没有实例化对象的情况下进行类中方法的调用,在这个类中提供有如下两个重要的结构

  1. private Unsafe(){}
  2. private static final Unsafe theUnsafe = new Unsafe();

  一般在单例设计模式之中,如果类中的构造方法被私有化了,往往会提供有一个static方法获取本类对象,但是Unsafe类没有这样的处理方法,这个时候可以考虑通过反射的机制来获取内部提供的“theUnsafe”对象

获取Unsafe类对象的实例

  获取Unsafe类的对象实例最为重要的目的是可以绕过JVM的管理机制来实现一些类的调用处理,例如:传统的开发之中,只要调用类中的普通方法,就必须有实例化对象存在,但是如果使用了Unsafe类,这个机制就可以被打破了。

通过Unsafe类绕过JVM的对象管理机制实现方法调用

结尾

  通过上面一系列的学习,你已经学会了反射的基本方法调用,实际开发中还需要多多应用实战练习,利用反射可以大幅度减少重复性代码的开发。

  通过一系列的分析可以发现利用Unsafe类的对象实例可以直接绕过JVM运行机制,从而直接实现指定类的方法调用,并且连实例化对象的操作全部省略了。

完~~

posted @ 2020-06-03 21:43  陈彦斌  阅读(...)  评论(...编辑  收藏