java反射机制(精讲)

一、概述

Java反射机制定义

Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类中的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。枚举除外

** 反射机制的功能**

  • 在运行时判断任意一个对象所属的类。
  • 在运行时构造任意一个类的对象。

  • 在运行时判断任意一个类所具有的成员变量和方法。

  • 在运行时调用任意一个对象的方法。

  • 生成动态代理。

    import java.util.Date;
    public class ReflactDemo_01 {
    public static void main(String[] args) throws Exception {
    /**
    需求1:通过obj对象,调用java.util.Date类中的toLocaleString方法.
    Object obj =new java.util.Date();
    java.util.Date d = (java.util.Date) obj;
    d.toLocaleString(); (过时了 )
    // 创建Class对象 如何来表示一个字节码对象

    // 需求2: 获取java.util.Date类的字节码对象
    // 方式1.使用class属性
    Class clz1 = java.util.Date.class;

    // 方式2,通过对象的getClass方法来获取,getClass是Object类中的方法
    java.util.Date date = new java.util.Date();
    Class clz2 = date.getClass();

    // 方式3,通过Class类中的静态方法forName(String className);
    Class clz3 = Class.forName("java.util.Date");
    /
    /
    *

    • 所有的数据类型都有class属性. Class clz = 数据类型.class; 九大内置Class实例:
    • JVM中预先提供好的Class实例,
      分别:byte,short,int,long,float,double,boolea,char,void.
    • 表示:byte.class,short.class,int.class,....void.class.
      /
      /
      *
    • 在8大基本数据类型的包装类中,都有一个常量:TYPE,用于返回该包装类对应基本类的字节码对象.
      System.out.println(Integer.TYPE == int.class);//true
    • 注意:Integer和int是不同的数据类型
      System.out.println(Integer.class == int.class);//false
      /
      // System.out.println(Integer.TYPE == int.class);
      // System.out.println(Integer.class == int.class);
      /
      *
    • 如何来表示数组的Class实例. 方式1: 数组类型.class; 方式2: 数组对象.getClass();
    • 注意:所有的具有相同的维数和相同元素类型的数组共享同一份字节码对象,和元素没有关系.
      /
      /
      *
    • Class clz4 = int[].class;
      System.out.println(clz4); String[] arr ={ "1", "3", "e" };
      Class clz5 = arr.getClass();
    • System.out.println(clz5);
      */
      }
      }

    **通过反射获取构造器 **

    import java.lang.reflect.Constructor;
    

    /**

    • 构造器最大的作用:创建对象. 为什么使用反射创建对象,为什么不直接来new呢? 在框架中,提供给我们的都是字符串.

    • 使用反射创建对象: 步骤:
    • 1);找到构造器所在类的字节码对象. 2):获取构造器对象. 3):使用 反射,创建对象

    • Constructor类:表示类中构造器的类型,Constructor的实例就是某一个类中的某一个构造器 常用方法:
    • public T newInstance(Object... initargs):如调用带参数的构造器,只能使用该方式. 参数:initargs:表示调用构造器的实际参数
    • 返回:返回创建的实例,T表示Class所表示类的类型
    • 如果:一个类中的构造器是外界可以直接访问,同时没有参数.,那么可以直接使用Class类中的newInstance方法创建对象.
    • public Object newInstance():相当于new 类名(); 调用私有的构造器:
      */
      class User {
      String name;
      int age;
      // create construct
      public User() {
      }
      public User(String name) {
      this.name = name;
      }
      private User(String name, int age) {
      this.name = name;
      this.age = age;
      }
      // 需求:通过反射来获取某一个类的构造器:
      // 1):获取该类的字节码对象.
      // 2):从该字节码对象中去找需要获取的构造器.

    // 获取指定的构造器
    public static void getOne() throws Exception {
    // 获取构造器所在类的字节码对象
    Class clz = User.class;
    // 获取clz对象中的所有构造器
    // 需求1.获取public User()
    Constructor con = clz.getConstructor();
    System.out.println(con);

      // 需求2.获取public User(String name)
      con = clz.getDeclaredConstructor(String.class);
      System.out.println(con);
      
      // 3.获取private User(String name, int age)
      con = clz.getDeclaredConstructor(String.class, int.class);
      System.out.println(con);
    

    }

    // 获取所有的构造器
    public static void getAll() {
    // 获取构造器所在类的字节码
    Class clz1 = User.class;
    // 获取clz1对象中所有的构造器
    Constructor[] con = clz1.getConstructors();
    System.out.println(con.length);// 2

      Constructor[] cons = clz1.getDeclaredConstructors();
      System.out.println(cons.length);// 3
      for (Constructor c : cons) {
      System.out.println(c);
      }
    }
    

    }

    public class ReflactDemo_02 {
    public static void main(String[] args) throws NoSuchMethodException, SecurityException {
    // User.getOne();
    User.getAll();
    }
    }

通过反射来获取某一个类的构造器

需求:通过反射来获取某一个类的构造器:
1):获取该类的字节码对象.
2)从该字节码对象中去找需要获取的构造器.
Class类获取构造器方法:
Constructor类:表示类中构造器的类型,Constructor的实例就是某一个类中的某一个构造器
public Constructor[] getConstructors():该方法只能获取当前Class所表示类的public修饰的构造器
public Constructor[] getDeclaredConstructors():获取当前Class所表示类的所有的构造器,和访问权限无关
public Constructor getConstructor(Class... parameterTypes)

注意获取当前Class所表示类中指定的一个public的构造器
参数:parameterTypes表示:构造器参数的Class类型
如:
public User(String name)
Constructor c = clz.getConstructor(String.class);
public Constructor getDeclaredConstructor(Class... parameterTypes)
获取当前Class所表示类中指定的一个的构造
访问私有的成员: 必须先设置可访问的 对象setAccessible(true);

import java.lang.reflect.Constructor;
class User1 {
String name;
int age;
// create constructor
public User1() {
    System.out.println("无参构造器");
}
public User1(String name) {
    this.name = name;
    System.out.println("构造器" + name);
}

private User1(String name, int age) {
    this.name = name;
    this.age = age;
    System.out.println("构造器" + name + age);
  }
}

public class ReflactDemo_03 {
public static void main(String[] args) throws Exception {
// 传统的方式创建对象
// new User1();
// new User1("CoderZS");
// 获取类的字节码
Class clz = User1.class;
// 获取public User1() 注意有public 修饰的 一定与方法相匹配
Constructor con = clz.getConstructor();
// 用构造器的newInstance方法来创建对象 会调用构造器的方法
con.newInstance();
System.out.println(con);

// 调用public User1(String name)
con = clz.getConstructor(String.class);
con.newInstance("CoderZS");

// 调用 private User1(String name,int age)!!!!!!!!!!!!!!
con = clz.getDeclaredConstructor(String.class, int.class);
// 设置当前构造器可以访问
// 访问私有的成员:必须先设置可访问的对象
// setAccessible(true);
con.setAccessible(true);
con.newInstance("CoderZS", 18);
  }
}
使用反射获取方法
 import java.lang.reflect.Method;
/**
 * 需求:使用反射获取类中的方法: 1):获取方法所在类的字节码对象.  2):获取方法.

 * Class类中常用方法: public Method[] getMethods():获取包括自身和继承过来的所有的public方法

*  public Method[] getDeclaredMethods():获取自身类中所有的方法(不包括继承的,和访问权限无关) 

 * public Method getMethod(String methodName, Class...parameterTypes)
 * :表示调用指定的一个公共的方法(包括继承的) 参数: methodName: 表示被调用方法的名字
 * parameterTypes:表示被调用方法的参数的Class类型如String.class

* public Method getDeclaredMethod(String name, Class... parameterTypes)
 * :表示调用指定的一个本类中的方法(不包括继承的) 参数: methodName: 表示被调用方法的名字
 * parameterTypes:表示被调用方法的参数的Class类型如String.class
 */
class User2 {
public void doWork() {
}
public static void doWork(String name) {
}

private String doNotWork(String name, int age) {

    return name + "有参有返回值" + age;
}

// 获取User2类中指定的一个方法
static void getOne1() throws Exception {
    // 获取类的字节码
    Class clz = User2.class;
    
// 需求1:获取doWork()
    Method m = clz.getMethod("doWork");
    System.out.println(m);
    
// 需求:获取private String doNotWork(String name,int age)
    m = clz.getDeclaredMethod("doNotWork", String.class, int.class);
    System.out.println(m);
}

static void getAll() {
    // 获取类的字节码
    Class clz = User2.class;
    // 需求1:获取doWork()
    Method[] ms = clz.getMethods();
    
    System.out.println(ms);
    System.out.println(ms.length);
    for (Method m : ms) {
        System.out.println(m);
    }
  }
}

// 使用反射获取类中方法
public class ReflactDemo_04 {
public static void main(String[] args) throws Exception {
    // User2.getOne1();
    User2.getAll();
  }
}
使用反射调用私有静态方法(public-private--static)
import java.lang.reflect.Method;
/**
 * 使用反射调用方法: 1):获取方法所在类的字节码对象. 2):获取方法对象. 3):使用反射调用方法. 
 * 如何使用反射调用一个方法:
 * 在Method类中有方法: public Object invoke(Object obj,Object...args):表示调用当前Method所表示的方法 
 * 参数: obj: 表示被调用方法底层所属对象
 *  Method m =clz.getMethod("doWork",String.class);
 * args:表示调用方法是传递的实际参数 返回: 底层方法的返回结果
 * 调用私有方法: 在调用私有方法之前:应该设置该方法为可访问的 又因为Method是AccessibleObject子类,所以Method中具有该方法.
 * doWorkMethod.setAccessible(true);
*/
class User3 {
public void doWork() {
    System.out.println("使用反射调用--无参--方法--doWork");
}

public void doWork(String name) {

    System.out.println("使用反射调用--有参--方法---doWork");
}

private String doWork(String name, int age) {

    return name + "使用反射调用--私有--方法" + age;
}

public static String doWork(String name, String sex, int age) {

    return name + "使用反射调用--静态--方法" + sex + age;
}
}

public class ReflactDemo_05 {
public static void main(String[] args) throws Exception {
    // TODO Auto-generated method stub
    // 使用反射调用方法
    // 需求1:调用public void doWork()
    // 获取类的字节码
    Class clz = User3.class;
    // 获取指定方法
    Method m = clz.getMethod("doWork");
    // 创建对象
    Object result = m.invoke(clz.newInstance());
    System.out.println(result);// null

    // 需求2:public static void doWork(String name)
    m = clz.getMethod("doWork", String.class);
    result = m.invoke(clz.newInstance(), "CoderZS");
    
    // 需求3:调用private String doNotWork(String name, int age)
    m = clz.getDeclaredMethod("doWork", String.class, int.class);
    m.setAccessible(true);
    result = m.invoke(clz.newInstance(), "CoderZS", 18);
    System.out.println(result);

    // 需求4,使用反射调用静态方法: 静态方法不属于任何对象,静态方法属于类本身.此时把invoke方法的第一个参数设置为null即可.
    m = clz.getDeclaredMethod("doWork", String.class, String.class, int.class);
    m.setAccessible(true);
    result = m.invoke(null, "CoderZS", "boy", 18);
    System.out.println(result);
  }
}
概括
  • 1.获取class对象的成员变量

    //获取class对象的所有属性
    Field[] allFields = class1.getDeclaredFields();
    //获取class对象的public属性
    Field[] publicFields = class1.getFields();
    //获取class指定属性
    Field ageField = class1.getDeclaredField("age");
    //获取class指定的public属性
    Field desField = class1.getField("des");
    
  • 2.获取class对象的方法
    //获取class对象的所有声明方法
    Method[] methods = class1.getDeclaredMethods();
    //获取class对象的所有public方法 包括父类的方法
    Method[] allMethods = class1.getMethods();
    //返回次Class对象对应类的、带指定形参列表的public方法
    Method method = class1.getMethod("info", String.class);
    //返回次Class对象对应类的、带指定形参列表的方法
    Method declaredMethod = class1.getDeclaredMethod("info", String.class);

  • 3.获取class对象的构造函数

     //获取class对象的所有声明构造函数
    Constructor[] allConstructors = class1.getDeclaredConstructors();
     //获取class对象public构造函数
    Constructor[] publicConstructors = class1.getConstructors();
      //获取指定声明构造函数
    Constructor constructor = class1.getDeclaredConstructor(String.class);
      //获取指定声明的public构造函数
     Constructor publicConstructor = class1.getConstructor(String.class);
    
  • 4.其他方法
    //获取class对象的所有注解
    Annotation[] annotations = (Annotation[]) class1.getAnnotations();
    //获取class对象指定注解
    Annotation annotation = (Annotation) class1.getAnnotation(Deprecated.class);
    //获取class对象的直接超类的 Type
    Type genericSuperclass = class1.getGenericSuperclass();
    //获取class对象的所有接口的type集合
    Type[] interfaceTypes = class1.getGenericInterfaces();

获取class对象的其他信息

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();//外部类

动态代理是指在运行时动态生成代理类。即,代理类的字节码将在运行时生成并载入当前代理的 ClassLoader。与静态处理类相比,动态类有诸多好处。

  • 不需要为(RealSubject )写一个形式上完全一样的封装类,假如主题接口(Subject)中的方法很多,为每一个接口写一个代理方法也很麻烦。如果接口有变动,则目标对象和代理类都要修改,不利于系统维护;
  • 使用一些动态代理的生成方法甚至可以在运行时制定代理类的执行逻辑,从而大大提升系统的灵活性。
动态代理涉及的主要类

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

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

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

static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) 返回代理类实例。参数与上述方法一致。

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

    //创建一个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 对象(表示哪个方法被调用了),第三个参数是指定调用方法的参数。

动态代理模式的简单实现

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()方法加入其他执行逻辑。

泛型和Class类

从JDK 1.5 后,Java中引入泛型机制,Class类也增加了泛型功能,从而允许使用泛型来限制Class类,例如:String.class的类型实际上是Class。如果Class对应的类暂时未知,则使用Class(?是通配符)。通过反射中使用泛型,可以避免使用反射生成的对象需要强制类型转换。
泛型的好处众多,最主要的一点就是避免类型转换,防止出现ClassCastException,即类型转换异常。以下面程序为例:

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对象,取出实例后需要强制类型转换。
如下例:

Date date = (Date) ObjectFactory.getInstance("java.util.Date");

String string = (String) ObjectFactory.getInstance("java.util.Date");

上面代码在编译时不会有任何问题,但是运行时将抛出ClassCastException异常,因为程序试图将一个Date对象转换成String对象。
但是泛型的出现后,就可以避免这种情况。

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

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

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

通过传入String.class便知道T代表String,所以返回的对象是String类型的,避免强制类型转换。

当然Class类引入泛型的好处不止这一点,在以后的实际应用中会更加能体会到。

使用反射来获取泛型信息

通过指定类对应的 Class 对象,可以获得该类里包含的所有 Field,不管该 Field 是使用 private 修饰,还是使用 public 修饰。获得了 Field 对象后,就可以很容易地获得该 Field 的数据类型,即使用如下代码即可获得指定 Field 的类型。
// 获取 Field 对象 f 的类型
Class a = f.getType();
但这种方式只对普通类型的 Field 有效。如果该 Field 的类型是有泛型限制的类型,如 Map 类型,则不能准确地得到该 Field 的泛型参数。
为了获得指定 Field 的泛型类型,应先使用如下方法来获取指定 Field 的类型。
// 获得 Field 实例的泛型类型
Type type = f.getGenericType();
然后将 Type 对象强制类型转换为 ParameterizedType 对象,ParameterizedType 代表被参数化的类型,也就是增加了泛型限制的类型。ParameterizedType 类提供了如下两个方法。
getRawType():返回没有泛型信息的原始类型。
getActualTypeArguments():返回泛型参数的类型。

下面是一个获取泛型类型的完整程序。
public class GenericTest
{
private Map score;
public static void main(String[] args)
    throws Exception
{
    Class 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("获取泛型类型出错!");
    }
  }
}

输出结果:

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

你可能感兴趣的:(java反射机制(精讲))