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

导读

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

认识反射机制

简单例子

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

Class类对象实例化

  在整个反射机制之中,Class类是整个反射操做的源头所在,若是如今能够获取Class类的对象,那么就能够进行全部的更加深层次的反射操做(上面案例仅仅是根据实例化对象的Class获取了类的基本名称)。安全

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

方式1、工具

  因为在Object类中提供有getClass()方法,因此任意的实例化对象均可以经过此方法来获取Class类的对象实例。学习

方式2、spa

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

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

方式3、设计

  在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运行机制,从而直接实现指定类的方法调用,而且连实例化对象的操做所有省略了。

完~~

相关文章
相关标签/搜索