1.背景介绍
反射的概述
反射是框架设计的灵魂
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取信息以及动态调用对象的方法的特性称为java语言的反射机制。
要想获得类的各种信息,必须先要获取到该类的字节码文件对象。该对象也就是java.lang.Class类,Class类用于表示java程序编译后得到的.class文件。
Class类的理解
Java程序在运行时,系统会对所有的对象进行所谓的运行时类型标识(RTTI,Run-Time Type Identification),其作用是在运行时识别一个对象的类型和类的信息。
传统的”RRTI”,它假定我们在编译期已知道了所有类型(在没有反射机制时,一般都是编译期已确定其类型,如new对象时该类必须已定义好);另外一种是反射机制,它允许我们在运行时发现和使用类型的信息。
在Java中用来表示运行时类型信息的对应类就是Class类,Class类也是一个实实在在的类,存在于JDK的java.lang包中。
Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的,因此不能显式地声明一个Class对象。
JVM为每种类型管理一个独一无二的Class对象。也就是说,无论创建多少个实例对象,在内存中每个类有且只有一个相对应的Class对象,并且基本的java类型和关键字void也都对应一个Class对象。运行程序时,JVM首先检查所要加载的类对应的Class对象是否已经加载。如果没有加载,JVM就会根据类名查找.class文件,并将其Class对象载入。
2.知识剖析
在这里先看一下java为我们提供了哪些反射机制中的类:
java.lang.Class;
java.lang.reflect.Constructor;
java.lang.reflect.Field;
java.lang.reflect.Method;
java.lang.reflect.Modifier;
反射的基本运用:Class对象获取方式
之前说了,Class对象是jvm用来保存对应实例对象的相关信息的,除此之外,我们完全可以把Class对象看成一般的实例对象。得到一个实例对象对应的Class对象有以下三种方式:
1.通过实例变量的getClass()方法
Test3 test3 =new Test3(); Class c3 =test3.getClass();
根据已经实例化的对象,利用getClass方法
2.通过类Class的静态方法forName()
Classc2 =Class.forName("com.getClassObject.Test2");
传入的参数是对应类的全限定名
3.直接给出对象类文件的.class
Class c1 =Test1.class;
反射的基本运用:判断是否为某个类的实例
一般地,我们用instanceof关键字来判断是否为某个类的实例。同时我们也可以借助反射中Class对象的isInstance()方法来判断是否为某个类的实例,它是一个Native方法
Class cls =Class.forName("com.isInstance.Demo"); //创建了一个Demo类的Class对象 boolean b1 =cls.isInstance(new Integer(37));
反射的基本运用:创建实例
通过反射来生成对象主要有两种方式:
(1)使用Class对象的newInstance()方法来创建Class对象对应类的实例。
//获取class对象
Class c1 =Class.forName("com.createInstance.Instance1");
//第一种方式:根据class对象,创建对应的实例
//调用无参数的构造函数,直接调用Class类中的newInstance Instance1 instance1 = (Instance1)c1.newInstance();
(2)先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建实例。这种方法可以用指定的构造器构造类的实例。
//获取Instance1所对应的Class对象
Class c2 =Instance1.class;
//获取Instance1类带一个int参数的构造器
//若想调用有参构造函数,则需要调用Constructor类中newInstance()方法
Constructor constructor =c2.getConstructor(int.class);
//根据构造器创建实例
Object obj =constructor.newInstance(3213);
反射的基本运用:获取方法
获取某个Class对象的方法集合,主要有以下几个方法:
(1) getDeclaredMethods() 方法返回类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。
(2) getMethods() 方法返回某个类的所有公用(public)方法,包括其继承类的公用方法。
(3) getMethod("add", int.class, int.class) 方法返回一个特定的方法,其中第一个参数为方法名称,后面的参数为方法的参数对应Class的对象。
Class c =Class.forName("com.getMethod.methodClass");
//获取methodClass类的所有公有方法(可获取到父类的方法)
Method[]methods =c.getMethods();
//获取methodClass类的所有的方法
Method[]declaredMethods =c.getDeclaredMethods();
//获取指定方法,methodClass类的add方法
Method publicMethod =c.getMethod("add", int.class, int.class);
//获取指定私有方法,div方法
Method privateMethod =c.getDeclaredMethod("div", int.class, int.class);
反射的基本运用:获取构造器信息
获取类构造器的用法与上述获取方法的用法类似。主要是通过Class类的getConstructor方法得到Constructor类的一个实例,而Constructor类有一个newInstance方法可以创建一个对象实例。
public T newInstance(Object ... initargs)
具体方法同获取对应类方法
反射的基本运用:获取类的成员变量(字段)信息
主要是这几个方法:
getFiled: 访问公有的成员变量
getDeclaredField:所有已声明的成员变量。但不能得到其父类的成员变量。
getFileds和getDeclaredFields用法同上(参照Method)
反射的基本运用:反射用于工厂模式
先看看传统的工厂模式
主类如下
interface Fruit { void eat(); } class Apple implements Fruit { public void eat(){ System.out.println("Eat Apple"); } } class Orange implements Fruit { public void eat(){ System.out.println("Eat Orange"); } }
工厂类
public class Factory { public static Fruit getInstance(String fruitName){ Fruit f=null; if("Apple".equals(fruitName)){ f=new Apple(); } if("Orange".equals(fruitName)){ f=new Orange(); } return f; } }
可以看到如果我要新增一个实现类,就要在工厂类中改代码,耦合度较高。
利用反射实现工厂
public class ReflexFactory { public static Fruit2 getInstances(String className) { Fruit2 f2 =null; try { //利用反射 f2 = (Fruit2)Class.forName(className).newInstance(); }catch (Exception e) { e.printStackTrace(); } return f2; } }
当如果要新增实现类的话,只要将传入的参数改变就好,无需更改工厂内的代码。
3.常见问题
反射机制的作用?
1,反编译:.class-->.java
2,通过反射机制访问java对象的属性,方法,构造方法等;
暴力反射?
获取类的私有成员。通过setAccessible(true)方法,设置成可访问。
类加载的过程?
加载:通过一个类的完全限定查找此类字节码文件,并利用字节码文件创建一个Class对象。
链接:验证字节码的安全性和完整性,准备阶段正式为静态域分配存储空间,注意此时只是分配静态成员变量的存储空间,不包含实例成员变量,如果必要的话,解析这个类创建的对其他类的所有引用。
初始化:类加载最后阶段,若该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化成员变量。
反射的应用场景?
Java的反射特性一般结合注解和配置文件(如:XML)来使用,这也是大部分框架(Spring等)支持两种配置方式的原因。还有著名的junit测试框架也是利用反射方法名和参数名来进行测试的。
理解泛化的Class对象引用
由于Class的引用总是指向某个类的Class对象,利用Class对象可以创建实例,这也就说明Class对象的引用指向的是对象确切的类型。在Java SE5引入泛型后,使我们可以利用泛型来表示Class对象更具体的类型,即使在运行期间会被擦除,但编译期足以确保我们使用正确的对象类型。