Java - 反射

来源:互联网 发布:activemq javascript 编辑:程序博客网 时间:2024/06/09 21:36

Java程序中的许多对象在运行时都会出现两种类型:编译时类型和运行时类型,例如代码:Person p = new Student();,这行代码将会生成一个p变量,该变量的编译时类型为Person,运行时类型为Student;除此之外,还有更极端的情形,程序在运行时接收到外部传入的一个对象,该对象的编译时类型是Object,但程序有需要调用该对象运行时类型的方法。

为了解决这些问题,程序需要在运行时发现对象和类的真实信息。解决该问题有以下两种做法。
1.第一种做法是假设在编译时和运行时都完全知道类型的具体信息,这种情况下,可以先使用instanceof运算符进行判断,在利用强制类型转换将其转换成其运行时类型的变量即可。
2.第二种做法是编译时根本无法预知该对象和类可能属于哪些类,程序只依靠运行时信息来发现该对象和类的真实信息,这就必须使用反射。

获得Class对象

我们知道每个类被加载之后,系统就会为该类生成一个对应的Class对象,通过该Class对象就可以访问到JVM中的这个类。在Java程序中获得Class对象通常有如下三种方式:
1.使用Class类的forName(String clazzName)静态方法。该方法需要传入字符串参数,该字符串参数的值是某个类的全限定类名(必须添加完整包名)。
2.调用某个类的class属性来获取该类对应Class对象。例如,Person.class将返回Person类对应的Class对象。
3.调用某个对象的getClass()方法。该方法是java.lang.Object类中的一个方法,所以所有的Java对象都可以调用该方法,该方法将会返回该对象所属类对应的Class对象。

第二种方式有如下两种优势:
(1)代码更安全。程序在编译阶段就可以检查需要访问的Class对象是否存在。
(2)程序性能更好。因为这种方式无须调用方法,所以性能更好。

一旦获得了某个类对应的Class对象之后,程序就可以调用Class对象的方法来获得该对象和该类的真实信息了。

从Class中获取信息

我们直接通过一个例子来讲解Class中的一些常用方法:

package com.lyong.test;import java.lang.annotation.Annotation;import java.lang.annotation.Repeatable;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.reflect.Constructor;import java.lang.reflect.Method;import java.util.Arrays;// 定义可重复注解@Repeatable(Annos.class)@interface Anno {}@Retention(value=RetentionPolicy.RUNTIME)@interface Annos {    Anno[] value();}// 使用4个注解修饰该类@SuppressWarnings("unchecked")@Deprecated// 使用重复注解修饰该类@Anno@Annopublic class ClassTest {    // 为该类定义一个i的构造器    private ClassTest() {    }    // 定义一个有参数的构造器    public ClassTest(String name) {        System.out.println("执行有参数的构造器");    }    // 定义一个无参数的info方法    public void info() {        System.out.println("执行无参数的info方法");    }    // 定义一个有参数的info方法    public void info(String str) {        System.out.println("执行有参数的info方法" + ",其str参数值:" + str);    }    // 定义一个测试用得内部类    class Inner {    }    public static void main(String[] args) throws Exception {        // 下面代码可以获取ClassTest对应的Class        Class<ClassTest> clazz = ClassTest.class;        // 获取该Class对象所对应类的全部构造器        Constructor<?>[] ctors = clazz.getDeclaredConstructors();        System.out.println("ClassTest的全部构造器如下:");        for(Constructor<?> c : ctors) {            System.out.println(c);        }        // 获取Class对象所对应类的全部public构造器        Constructor<?>[] publicCtors = clazz.getConstructors();        System.out.println("ClassTest的全部public构造器如下:");        for(Constructor<?> c : publicCtors) {            System.out.println(c);        }        // 获取该Class对象对应类的全部public方法        Method[] mtds = clazz.getMethods();        System.out.println("ClassTest的全部public方法如下:");        for(Method m : mtds) {            System.out.println(m);        }        // 获取该Class对象所对应类的制定方法        System.out.println("ClassTest里带一个字符串参数的info方法为:" + clazz.getMethod("info", String.class));        // 获取该Class对象所对应类的全部注解        Annotation[] anns = clazz.getAnnotations();        System.out.println("ClassTest的全部Annotation如下:");        for(Annotation ann : anns) {            System.out.println(ann);        }        System.out.println("该Class元素上的@SuppressWarnings注解为:" + Arrays.toString(clazz.getAnnotationsByType(SuppressWarnings.class)));        System.out.println("该Class元素上的@Anno注解为:" + Arrays.toString(clazz.getAnnotationsByType(Anno.class)));        // 获取该Class对象对应类的全部内部类        Class<?>[] inners = clazz.getDeclaredClasses();        System.out.println("ClassTest的全部内部类如下:");        for(Class<?> c : inners) {            System.out.println(c);        }        // 使用Class.forName()方法加载ClassTest的Inner内部类        Class<?> inClass = Class.forName("com.lyong.test.ClassTest$Inner");        // 通过getDeclaringClass()访问该类所在的外部类        System.out.println("inClass对应类的外部类为:" + inClass.getDeclaredClasses());        System.out.println("ClassTest的包为:" + clazz.getPackage());        System.out.println("ClassTest的父类为:" + clazz.getSuperclass());    }}

运行结果:

ClassTest的全部构造器如下:private com.lyong.test.ClassTest()public com.lyong.test.ClassTest(java.lang.String)ClassTest的全部public构造器如下:public com.lyong.test.ClassTest(java.lang.String)ClassTest的全部public方法如下:public static void com.lyong.test.ClassTest.main(java.lang.String[]) throws java.lang.Exceptionpublic void com.lyong.test.ClassTest.info(java.lang.String)public void com.lyong.test.ClassTest.info()public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedExceptionpublic final native void java.lang.Object.wait(long) throws java.lang.InterruptedExceptionpublic final void java.lang.Object.wait() throws java.lang.InterruptedExceptionpublic boolean java.lang.Object.equals(java.lang.Object)public java.lang.String java.lang.Object.toString()public native int java.lang.Object.hashCode()public final native java.lang.Class java.lang.Object.getClass()public final native void java.lang.Object.notify()public final native void java.lang.Object.notifyAll()ClassTest里带一个字符串参数的info方法为:public void com.lyong.test.ClassTest.info(java.lang.String)ClassTest的全部Annotation如下:@java.lang.Deprecated()@com.lyong.test.Annos(value=[@com.lyong.test.Anno(), @com.lyong.test.Anno()])该Class元素上的@SuppressWarnings注解为:[]该Class元素上的@Anno注解为:[@com.lyong.test.Anno(), @com.lyong.test.Anno()]ClassTest的全部内部类如下:class com.lyong.test.ClassTest$InnerinClass对应类的外部类为:[Ljava.lang.Class;@7ea987acClassTest的包为:package com.lyong.testClassTest的父类为:class java.lang.Object

关于Class的方法详细信息大家可以通过查阅API来了解,这里就不在重复解释了。

从运行结果来看,Class提供的功能非常丰富,它可以获取该类里包含的构造器、方法、内部类、注解等信息,也可以获取该类所包括的成员变量(Field)信息——通过getFields()或getField(String name)方法即可。

值得指出的是,虽然定义ClassTest类时使用了@SuppressWarnings注解,但程序运行时无法解析出该类里包含的该注解,这是因为@SuppressWarnings使用了@Retention(value=SOURCE)修饰,这表明@SuppressWarnings只能保存在源码级别上,通过ClassTest.class获取该类的运行时Class对象,所以程序无法访问到@SuppressWarnings注解。

方法参数反射

Java 8 在java.lang.reflect包下新增了一个Executable抽象基类,该对象代表可执行的类成员,该类派生了Constructor、Method两个子类。

Executable基类提供了大量方法来获取修饰该方法或构造器的注解信息;还提供了isVarArgs()方法用于判断该方法或构造器是否包含数量可变的形参,以及通过getModifiers()方法来获取该方法或构造器的修饰符。除此之外,Executable提供了如下两个方法来获取该方法或参数的形参个数及形参名。
1.int getParameterCount():获取该构造器或方法的形参个数。
2.Parameter[] getParameters():获取该构造器或方法的所有形参。

上面两个方法返回了一个Parameter[] 数组,Parameter也是Java 8新增的API,每个Parameter对象代表方法或构造器的一个参数。Parameter也提供了大量方法来获取声明该参数的泛型信息,还提供了如下常用方法来获取参数信息。

getModifiers():获取修饰该形参的修饰符。
String getName():获取形参名。
Type getParameterizedType(): 获取带泛型的形参类型。
Class< ? > getType():获取形参类型。
boolean isNamePresent():该方法返回该类的class文件中是否包含了方法的形参名信息。
boolean isVarArgs():该方法用于判断该参数是否为个数可变的形参。

需要指出的是,使用javac命令编译Java原文件时,默认生成的class文件并不包含方法的形参名信息,因此调用isNamePresent()方法将会返回false,调用getName()方法也不能得到该参数的形参名。如果希望javac命令编译Java源文件是可以保留形参信息,则需要为该命令指定-parameters选项。

如下程序示范了Java 8的方法参数反射功能。

package com.lyong.test;import java.lang.reflect.Method;import java.lang.reflect.Parameter;import java.util.List;class Test {    public void replace(String str,List<String> list) {}}public class MethodParameterTest {    public static void main(String[] args) throws Exception {        // 获取Test的类        Class<Test> clazz = Test.class;        // 获取String类的带两个参数的replace()方法        Method replace = clazz.getMethod("replace", String.class,List.class);        // 获取指定方法的参数个数        System.out.println("replace方法参数个数:" + replace.getParameterCount());        // 获取replace的所有参数信息        Parameter[] parameters = replace.getParameters();        int index = 1;        // 遍历所有参数        for(Parameter p : parameters) {            if(p.isNamePresent()) {                System.out.println("---第" + index + "个参数信息---");                System.out.println("参数名:" + p.getName());                System.out.println("形参类型:" + p.getType());                System.out.println("泛型类型:" + p.getParameterizedType());            }        }    }}

编译上面的程序需要使用-parameters选项来控制javac命令保留方法形参名信息。否则我们获取不到参数信息。

使用反射生成并操作对象

Class对象可以获得该类里的方法(由Method对象表示)、构造器(由Constructor对象表示)成员变量(由Field对象表示),这三个类都位于java.lang.reflect包下,并实现了java.lang.reflect.Member接口。程序可以通过Method对象来执行对应的方法,通过Constructor对象来调用对应的构造器创建实例,能通过Field对象直接访问并修改对象的成员变量值。

创建对象

通过反射来生成对象有如下两种方法。
1:使用Class对象的newInstance()方法来创建该Class对象对应类的实例,这种方式要求该Class对象的对应类有默认构造器,而执行newInstance()方法时实际上是利用默认构造器来创建该类的实例。
2.先使用Class对象获取指定的Constructor对象,再调用Constructor对象newInstance()方法来创建Class对象对应类的实例。通过这种方式可以选择使用指定的构造器来创建实例。

如果不想利用默认构造器来创建Java对象,而想利用指定的构造器来创建Java对象,则需要利用Constructor对象,每个Constructor对应一个构造器。为了利用指定的构造器来创建Java对象,需要如下三个步骤。
1.获取该类的Class对象。
2.利用Class对象getConstructor()方法来获取指定的构造器。
3.调用Constructor()的newInstance()方法来创建Java对象。

调用方法

当获得某个类对应的Class对象后,就可以通过该Class对象的getMethods()方法或者getMethod()方法来获取全部方法或指定方法——这两个方法的返回值都是Method数组,或Method对象。

每个Method对象对应一个方法,获得Method对象之后,程序就可以通过该Method来调用它对应的方法。在Method里包含一个invoke()方法,该方法的签名如下。

Object invoke(Object obj,Object…args):该方法中的obj是执行该方法的主调,后面的args是执行该方法时传入该方法的实参。

当通过Method的invoke()方法来调用对应的方法时,Java会要求程序必须有调用该方法的权限。如果程序确实需要调用某个对象的private方法,则可以先调用Method对象的如下方法。

setAccessible(boolean flag):将Method对象accessible设置为指定的boolean值。值为true,指示该Method在使用时应该取消Java语言的访问权限检查;值为false,则指示该Method在使用时实施Java语言的访问权限检查。

访问成员变量值

通过Class对象getFields()或getField()方法可以获取该类所有包括的全部成员变量或指定成员变量。
Field提供了如下两组方法来读取或设置成员变量值。
1.getXxx(Object obj):获取obj对象的成员变量的值。此处的Xxx对应8中基本类型,如果该成员变量的类型是引用类型,则取消get后面的Xxx。
2.setXxx(Object obj,Xxx val):将obj对象的该成员变量设置成val值。此处的Xxx对应8中基本类型,如果该成员变量的类型是引用类型,则取消set后面的Xxx。

操作数组

在java.lang.reflect包下还提供了一个Array类,Array对象可以代表所有的数组。程序可以通过使用Array来动态地创建数组,操作数组元素等。

Array提供了如下几类方法:
1、static Object newInstance(Class< ? > componentType, int … length):创建一个具有指定的元素类型、指定维度的新数组。

2、static xxx getXxx(Object array,int index):返回array数组中第index个元素。其中xxx是各种基本数据类型,如果数组元素是引用类型,则该方法变为get(Object array,int index)。

3、static void setXxx(Object array,int index,xx val):将array数组中第index个元素的值设为val。其中xxx是各种基本数据类型,如果数组元素是引用类型,则该方法变成set(Object array,int index,Object val)。

使用反射生成JDK动态代理

在Java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过使用这个类和接口可以生成JDK动态代理类或动态代理对象。

使用Proxy和InvocationHandler创建动态代理

Proxy提供了用于创建动态代理类和代理对象的静态方法,它也是所有动态代理类的父类。如果在程序中为一个或多个接口动态地生成实现类,就可以使用Proxy来创建动态代理类;如果需要为一个或多个接口动态创建实例,也可以使用Proxy来创建动态代理实例。

Proxy提供了如下两个方法来创建动态代理类和动态代理实例。
1.static Class< ? > getProxyClass(ClassLoader loader,Class< ? >…interfaces):创建一个动态代理类所对应的Class对象,该代理类将实现interfaces所指定的多个接口。第一个ClassLoader参数指定生成动态代理类的类加载器。

2.static Object newProxyInstance(ClassLoader loader,Class< ? >[] interfaces,InvocationHandler h):直接创建一个动态代理对象,该代理对象的实现类实现了interfaces指定的系列接口,执行代理对象的每个方法时都会被替换执行InvocationHandler对象的invoke方法。

实际上,即使采用第一个方法生成动态代理类之后,如果程序需要通过该类来创建对象,依然需要传入一个InvocationHandler对象。也就是说,系统生成的每个代理对象都有一个与之关联的InvocationHandler对象。

程序中可以采用先生成一个动态代理类,然后通过动态代理类来创建代理对象的方式生成一个动态代理对象。代码如下:

package com.lyong.test;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Proxy;public class Test4 {    public static void main(String[] args) throws Exception {        // 创建一个InvocationHandler对象        InvocationHandler handler = new MyInvokationHandler();        // 使用Proxy生成一个动态代理类proxyClass        Class<?> proxyClass = Proxy.getProxyClass(Person.class.getClassLoader(), new Class[]{Person.class});        // 获取proxyClass类中带一个InvocationHandler参数的构造器        Constructor<?> ctor = proxyClass.getConstructor(new Class[]{InvocationHandler.class});        // 调用ctor的newInstance方法来创建动态实例        Person p = (Person) ctor.newInstance(new Object[]{handler});        // 上面的代码可以简化成如下形式        InvocationHandler handler2 = new MyInvokationHandler();        Person person = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[]{Person.class}, handler2);    }}

下面程序示范了使用Proxy和InvocationHandler来生成动态代理对象。

package com.lyong.test;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;interface Person {    void walk();    void sayHello(String name);}class MyInvokationHandler implements InvocationHandler {    /**     * 执行动态代理对象的所有方法时,都会被替换成执行如下的invoke方法。     * @param proxy 代表动态代理对象     * @param method 代表正在执行的方法     * @param args 代表调用目标方法时传入的实参     */    @Override    public Object invoke(Object proxy, Method method, Object[] args)            throws Throwable {        System.out.println("----正在执行的方法" + method);        if(args != null) {            System.out.println("下面是执行该方法时传入的实参为:");            for(Object val : args) {                System.out.println(val);            }        } else {            System.out.println("调用该方法没有实参!");        }        return null;    }}public class ProxyTest {    public static void main(String[] args) {        // 创建一个InvocationHandler对象        InvocationHandler handler = new MyInvokationHandler();        // 使用指定的InvocationHandler来生成一个动态代理对象        Person p = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[]{Person.class}, handler);        // 调用动态代理对象的walk()和sayHello()方法        p.walk();        p.sayHello("孙悟空");    }}

上面程序首先提供了一个Person接口,该接口中包含了walk()和sayHello()两个抽象方法,接着定义了一个简单的InvocationHandler实现类,定义该实现类时需要重写invoke()方法——调用代理对象的所有方法时会被替换成调用该invoke()方法。对于参数的解释代码中有,这里不再重复。

看完上面的示例程序,可能有读者会觉得这个程序没有太大的使用价值,难以理解Java动态代理的魅力。实际上,在普通编程中,确实无须用动态代理,但在编写框架或底层基础代码时,动态代理的作用就非常大了。

反射和泛型

从JDK 5以后,Java的Class类增加了泛型功能,从而允许使用泛型来限制Class类,例如:String.class的类型实际上是Class< String >。如果Class对应的类暂时未知,则使用Class< ? >。通过在反射中使用泛型,可以避免使用反射生成的对象需要强制类型转换。

泛型和Class类

使用Class< T >泛型可以避免强制类型转换。例如,下面提供一个简单的对象工厂,该对象工厂可以根据指定类提供该类的实例。

package com.lyong.test;import java.util.Date;import javax.swing.JFrame;public class CrazyitObjectFactory {    public static Object getInstance(String clsName) {        try {            // 创建指定类对应的Class对象            Class cls = Class.forName(clsName);            // 返回使用该Class对象创建的实例            return cls.newInstance();        } catch (Exception e) {            e.printStackTrace();            return null;        }    }    public static void main(String[] args) {        // 获取实例后需要强制类型转换        Date d = (Date) CrazyitObjectFactory.getInstance("java.util.Date");        // 下面的代码编译时不会有任何问题,但运行时将抛出ClassCastException异常,因为        // 程序视图将一个Date对象转换成JFrame对象。        JFrame f = (JFrame) CrazyitObjectFactory.getInstance("java.util.Date");    }}

我们可以使用泛型来优化上面的程序,避免类似的问题发生。代码如下:

package com.lyong.test;import java.util.Date;import javax.swing.JFrame;public class CrazyitObjectFactory {    public static <T> T getInstance(Class<T> clsName) {        try {            return clsName.newInstance();        } catch (Exception e) {            e.printStackTrace();            return null;        }    }    public static void main(String[] args) {        Date d = CrazyitObjectFactory.getInstance(Date.class);        JFrame f = CrazyitObjectFactory.getInstance(JFrame.class);    }}

如上代码所示,我们将getInstance()方法中传入一个Class< T >参数,这是一个泛型化的Class对象,调用该Class对象的newInstance()方法将返回一个T对象,使用上面的代码在客户端也无须强制类型转换,系统会执行更严格的检查,不会出现ClassCastException运行时异常。

使用反射来获取泛型信息

通过制定类对应的Class对象,即可获得该类里包含的所有成员变量,不管该成员变量是使用private修饰,还是使用public修饰。获得了成员变量对应的Field对象后,就可以很容易地获得该成员变量的数据类型,即使用如下代码即可获得指定成员变量的类型。

// 获取成员变量f的类型Class< ? > a = f.getType();

但这种方式只对普通类型的成员变量有效。如果该成员变量的类型是由泛型类型的类型,如Map< String , Integer >类型,则不能准确地得到该成员变量的泛型参数。

为了获得指定成员变量的泛型类型,应先使用如下方法来获取该成员变量的泛型类型。

// 获取成员变量f的泛型类型Type gType = f.getGenericType();

然后将Type对象强制类型转换为ParameterizedType对象,ParameterizedType代表被参数化的类型,也就是增加了泛型限制的类型。ParameterizedType类提供了如下两个方法。
1.getRawType():返回没有泛型信息的原始类型。
2.getActualTypeArguments():返回泛型参数的类型。

下面是一个获取泛型类型的完整程序。

package com.lyong.test;import java.lang.reflect.Field;import java.lang.reflect.ParameterizedType;import java.lang.reflect.Type;import java.util.Map;public class GenericTest {    private Map<String, Integer> score;    public static void main(String[] args) throws Exception {        Class<GenericTest> clazz = GenericTest.class;        Field f = clazz.getDeclaredField("score");        //直接使用getType()取出类型只对普通类型的成员变量有效        Class<?> a = f.getType();        // 下面将看到仅输出java.util.Map        System.out.println("score的类型是:" + a);        // 获得成员变量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.String1个泛型类型是:class java.lang.Integer

从上面的运行结果可以看出,使用getType()方法只能获取普通类型的成员变量的数据类型;对于增加了泛型的成员变量,应该使用getGenericType()方法来取得其类型。

Type也是java.lang.reflect包下得一个接口,该接口代表所有类型的公共高级接口,Class是Type接口的实现类。Type包括原始类型、参数化类型、数组类型、类型变量和基本类型等。

0 0