深入理解java---反射篇

来源:互联网 发布:网络爬虫好学么 编辑:程序博客网 时间:2024/06/11 01:52


                                                深入理解java---反射篇

 背景

   在Java中如果我们预先不知道一个对象的确切类型,RTTI可以告诉你,但是有一个限制,那就是在编译的时候这个对象类型必须是确定的(需要有一个确定的编译类型),这样我们才能使用RTTI去识别它,并且利用这些信息做一些有用的事,事实上在编译的时候你的程序压根儿是不知道某个对象是属于哪一个类的,比如在编译的时候,你可以从你的本机文件、或者网络上获取一段字节,并且被告知这就是你所需要的类。既然这个类在编译器为你的程序生成代码很久之后才出现,那么你要怎样才能区正确的使用这个类呢??

  首先我们来谈谈反射的出现的原因,在快速化开发工具中,类的所有信息对程序员来说都是可见的,可触及的。我们可以通过使用集成开发环境(IDE)中的表单工具直接的区创建一个类,或者使用某些构件直接的修改一个类的某些部分,而不是直接区手动重写代码。那么IDE要怎么去实现这样的一个功能,使一个类的所有信息都暴露给开发人员。反射就提供了一种机制--用来检查可用的方法、属性等等,并返回方法名。除此之外人们想要在运行是获取类的信息的另一个动机就是,希望实现在扩网络的远程平台上创建和运行的能力,最常见的例子就是现在的分布式服务器架构,应用运行的时候不同的功能在不同的服务器上运行实现,这就需要类能在不同的服务器上被创建、使用。


走近反射家族


    在反射中,java提供了java.lang.Class类和java.lang.reflect包对反射的实现进行了支持。在Java中基于一起皆对象的思维,在java.lang.lang.reflect中也提供几个相关的类对我们定义的类的属性和方法进行了描述。

    -java.lang.reflect.Constructor<?> 类:封装了类中构造器的的相关信息,可以通过获取某个类的构造器的java.lang.reflect.Constructor<?>对象来构建类的对象。


    -java.lang.reflect.Method类:封装类java中方法的相关信息,可以通过获取某个对象的某个方法的java.lang.reflect.Method对象以此来访问该对象的该方法。


    -java.lang.reflect.Field类:封装类java类中的属性信息,可以通过获取某个对象的某个属性的java.lang.reflect.Field对象,以此来访问该对象的属性值。


    -java.lang.annotation.Annotation类封装了java类中(类、类的属性、类的方法)的注解相关的信息,可以通过获取某个类的java.lang.annotation.Annotation对象来访问类中的相关注解。


    -java.lang.reflect.Modifier类:封装类java类中的所有修饰符号的信息可以通过访问类、类中的属性、方法的相关方法来获取他们的修饰符信息。


    -java.lang.reflect.Parameter类:封装了类中的方法的参数信息。可以通过Method对象的相关方法获取java.lang.reflect.Parameter对象访问方法的参数信息。


    认识完了反射家族的部分成员以后,可能大家对java的一切皆对象的思维又有一定的提高。类中的一个个构建都被封装到相应的类,获取相应的类对象就能访问,那么我们从哪里区获取这些对象呢?那我们继续往下看。


从Class说起


    大家在看完我的上一篇博客 深入理解java 类在jvm中的生命周期

可以直到类在经过就jvm的加载后会在方法区里面生成类对应数据结构,在堆区中生成方法区中数据结构对应Class对象,意思就是说jvm Class对象存有某个类的所有信息,那么 我们只要能获取到某个类的Class对象就能访问该类,创建该类的对象,访问该类的方法、修改生成对象的属性值。在java中我们吧这种获取类的Class对象然后访问类的方式叫做反射。

简单再说Class

   

Class类位于java.lang包中, Class类的实例表示正在运行的Java应用程序中的类和接口.
枚举算类, 注解算接口; 数组算类,它映射的Class对象被有着相同元素和大小的数组共享;
Java的原生(primitive types)类型(boolean、byte、char、short、int、long、float、double)和关键字void也代表Class对象.

Class类实现了Serializable、GenericDeclaration、Type、AnnotatedElement接口

Class类无public构造函数, Class类对象是由JVM在类加载的时候调用类加载器的defineClass方法创建的.
如下方法能够获取类
怎么获取Class  

(1)从java.lang.Class.forName系列方法中获取Class对象,该系列方法是静态方法,可以直接访问。

eg:

Class<?> forName = Class.forName("java.lang.String");
    (2)通过类字面常量class获取Class对象,即以"类名.class"的形式获取

eg:

Class<Integer> intClazz=Integer.class;
    (3)通过java.lang.Object.getClass()方法获取类的Class对象,该方法是Object类中的被所有类继承拥有。最常用的形式是  this.getClass()  。

eg:

Class<? extends Test> class1 = test.getClass();或Class<? extends Test> class1 = this.getClass();

对于基本类型的封装类来说还可以使用编译常量字段TYPE来获取到,自身所对应的基本类型的Class对象,但是基本类型封装类还是只能通过上面提及的几种方法来获取他们自己的Class对象。

eg:

               /*2*/Class< Integer> intClass=Integer.class;Class<Integer> intClass1=int.class;/*3*//*4对于基本类型的封装类*/Class<Integer> intClass2=Integer.TYPE;Class<Boolean> booleanClass=Boolean.TYPE;Integer integer=new Integer(0);Class<? extends Integer> intClass3 = integer.getClass();System.out.println("intClass1==intClass2 ??  "+(intClass1==intClass2));                //输出intClass1==intClass2 ?? true                System.out.println("intClass3==intClass2 ??  "+(intClass3==intClass2));                //输出intClass3==intClass2 ?? false   


判定方法

这些方法可以判断某个类是否是接口、是否是枚举、是否是注解、是否有某个注解、是否是匿名类、是否是某个类的超类(是否可强制转换为该类)等

  1. boolean isArray(): 是否是数组; int[].class.isArray()true
  2. boolean isAnnotation(): 是否是注解, 若返回true,则isInterface也为tureTarget.class.isAnnotation()true
  3. boolean isEnum(): 是否是枚举类
  4. boolean isPrimitive(): 是否是原生类型(共9个, 包装类返回false), void.class.isArray()true
  5. boolean isInterface(): 是否是接口
  6. boolean isMemberClass(): 是否是成员类, 类的定义在另一个类里面的那种
  7. boolean isAnonymousClass(): 是否是匿名类
  8. boolean isLocalClass(): 是否是本地类
  9. boolean isSynthetic(): 是否是复合类 (接口Member也有这个方法)
  10. boolean isInstance(Object obj): obj是否是该类的一个实例
  11. boolean isAssignableFrom(Class<?> cls): cls是否可以被转换成该类
  12. boolean isAnnotationPresent(Class<? extends Annotation> annotationClass): 该类上是否存在这个注解(继承自接口AnnotatedElement)
public class Main {    class Test{}  // 这是传说中的成员类    public static void main(String[] args) {        class LocalClass{}  // 这是传说中的本地类        Object obj = new Hello(){    // Hello是其他地方定义的一个接口, 有一个sayHello()方法, 这就是匿名类(无类名)            public void sayHello() {            }        };        System.out.println(Test.class.isMemberClass());         // true        System.out.println(obj.getClass().isAnonymousClass());  // true        System.out.println(LocalClass.class.isLocalClass());    // true        System.out.println(Hello.class.isInstance(obj));        // true        System.out.println(Bean.class.isAnnotationPresent(Target.class));        // true    }}

  instanceof、 isInstance、 isAssignableFrom的区别

instanceof运算符只被用于对象引用变量, 比如: 自身类或子类的实例 instanceof 自身类 返回true
isInstance(Object obj)instanceof运算符的动态等价, 比如: 自身类.class.isInstance(自身类或子类的实例) 返回true
isAssignableFrom(Class<?> cls)是两个类之间的关系, 比如: 自身类.class.isAssignableFrom(自身类或子类.class) 返回true

获取注解

这几个方法均继承自接口AnnotatedElement

  1. Annotation[] getAnnotations(): 获取这个元素上的所有注解(包括父类上被@Inherited标记的注解)
  2. <A extends Annotation> A getAnnotation(Class<A> annotationClass): 获取这个元素上指定类型的注解, 没有返回null
  3. Annotation[] getDeclaredAnnotations(): 获取直接标注在这个元素上的注解

父类子类(接口)相关

  1. Class<? super T> getSuperclass(): 返回本类的父类(直接超类);int[].classObjectint.classnullObject.classnull
  2. Type getGenericSuperclass(): 以Type的形式返回本类的父类, 带有范型信息(没有范型信息时把Class以Type形式返回)
  3. Class<?>[] getInterfaces(): 返回本类直接实现的接口
  4. Type[] getGenericInterfaces(): 以Type的形式返回本类直接实现的接口, 带有范型信息
  5. <U> Class<? extends U> asSubclass(Class<U> clazz) : 把当前类转为clazz表示的子类(或自己), 不能转抛ClassCastException异常

asSubclass的作用

ArrayList.class.asSubclass(List.class)得到的还是ArrayList.class, 看起来没什么作用
但是它的作用体现在窄化未知的Class类型的范围, 比如通常我们用到Class.forName("XXX"), 它的返回是Class<?>比较宽泛, 我们可以窄化一下:Class.forName("XXX").asSubclass(List.class).newInstance(). 当XXX不是List的子类时,抛出ClassCastException异常

内部类相关

  1. Class<?> getEnclosingClass(): 获取底层类的直接封闭类, 如上面LocalClass的封闭类为Main, 那个匿名类的封闭类也是Main
  2. Constructor<?> getEnclosingConstructor(): 若该Class对象是在一个构造方法中的本地类或匿名类时, 返回这个构造器对象, 表示底层类直接封闭构造方法, 否则返回null; 上面的LocalClass不在构造方法中,因此返回null
  3. Method getEnclosingMethod(): 若该Class对象是在一个方法中的本地类或匿名类时, 返回这个Method对象, 表示底层类的直接封闭方法, 否则返回null
  4. Class<?> getDeclaringClass(): 该类是另一个类的成员(isMemberClass),则返回该类的声明类(外部类); 接口Member中也有该方
  5. Class<?>[] getDeclaredClasses() : 返回该类中直接声明的所有类
    1. Class<?>[] getClasses() : 返回该类中直接声明的所有public类

其他方法

  1. TypeVariable<Class<T>>[] getTypeParameters(): 继承自接口GenericDeclaration,按照声明顺序返回声明的类型变量
  2. Class<?> getComponentType(): 若该类是个数组,则返回组件的类型

 

    2.从Class对象中获取构造器类java.lang.reflect.Constructor<?>的对象

       获取构造器由一系列的方法为了方便讲解我们先写一个模板类出来,接下来我们就用反射类操作模板类。以下是模板类代码:

<span style="font-size:14px;">public class ModelClass {    String field1;    private ModelClass(String field1) {        this.field1 = field1;        field1 = "Constractor1";    };    public ModelClass() {        field1 = "Constractor2";    }        ModelClass(Integer a) {        field1 = "Constractor3";    }    private void method1(String name, int a) throws Exception{        System.out.println("name : "+name+"  a : "+a);    }        public String method2(String name, int a) {        System.out.println("name : "+name+"  a : "+a);        return "method2";    }}</span>
    反射中获取Constractor对象的API

    a.Constructor<?>[] java.lang.Class.getConstructors() throws SecurityException:获取当前类的所有public 修饰的构造器。返回一个 Constructor数组。

<span style="font-size:14px;">        Constructor<?>[] constructors = modelClazz.getConstructors();System.out.println(constructors.length);for (Constructor<?> constructor : constructors) {System.out.println(constructor);}</span>


    b.Constructor<?>[] java.lang.Class.getDeclaredConstructors() throws SecurityException获取当前类的所有的构造器(包括private修饰的和默认包权限的和public的)。返回一个 Constructor数组。

<span style="font-size:14px;">        System.out.println("------------------------------------------------------------------------------------------");Constructor<?>[] declaredConstructors = modelClazz.getDeclaredConstructors();System.out.println(declaredConstructors.length);for (Constructor<?> constructor : declaredConstructors) {System.out.println(constructor);}</span>

    c.Constructor<ModelClass> java.lang.Class.getDeclaredConstructor(Class<?>... parameterTypes) throws
 NoSuchMethodException,
SecurityException获取指定参数列表的构造器对象(包可访问和private修饰的也可以获取到)
<span style="font-size:14px;">Constructor<ModelClass> constructor1=null;try { constructor1= modelClazz.getDeclaredConstructor(String.class);} catch (NoSuchMethodException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (SecurityException e) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println(constructor1);//输出 : private test.clz.ModelClass(java.lang.String)</span>

    d.Constructor<ModelClass> java.lang.Class.getConstructor(Class<?>... parameterTypes) throws
 NoSuchMethodException, SecurityException
获取指定参数列表的public修饰的构造器,如果没有这个参数列表的构造器则会返回null
<span style="font-size:14px;">Constructor<ModelClass> constructor2=null;try { constructor2= modelClazz.getConstructor(Integer.class);} catch (NoSuchMethodException e) {e.printStackTrace();} catch (SecurityException e) {e.printStackTrace();}System.out.println(constructor2);//输出 : private test.clz.ModelClass(java.lang.Integer)</span>
    当我们获取到构造器对象以后最想做的就是创建该类的对象,好吧让我们开始用Constractor创建对象吧!

    a.通过public构造器创建对象

<span style="font-size:14px;">try {System.out.println("newInstanceByPublicConstractor -------------- newInstanceByPublicConstractor");<span style="color:#FF0000;">ModelClass newInstance = constructor2.newInstance(1);</span>System.out.println(newInstance);} catch (InstantiationException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (IllegalArgumentException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}</span>

b通过私有构造器或者包可访问构造器创建对象。
<span style="font-size:14px;">try {System.out.println("newInstanceByPrivateConstractor -------------- newInstanceByPrivateConstractor");                <span style="color:#FF0000;">System.out.println("isAccessAble  ---"+constructor1.isAccessible());                constructor1.setAccessible(true);ModelClass newInstance = constructor1.newInstance(1);</span>System.out.println(newInstance);} catch (InstantiationException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (IllegalArgumentException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}</span>

大家学习 了java都应该知道包可访问在相同包之外是不能访问的,private在本类以外是不能访问的,要想在反射中要想打破这种局限就必须进行设置当我们在通过构造器创建对象的时候先调用java.lang.reflect.AccessibleObject.setAccessible(boolean) 并且传递true参数进去才能成功的常见非public修饰的构造器初的始化对象。否则回报java.lang.IllegalAccessException(违反反射机制)eg  :  constructor1.setAccessible(true); 

对象都有了是不是就可以为所欲为了呀!!!!


通过Class对象获取java.lang.reflect.Method对象

    首先来普及一下java中方法的分类,由于继承的存在所以由继承的角度来讲java中方法分为两类,即:继承的父类的方法、本类中定义的方法;另外从修饰符的角度来讲又可以分为三类,但是我们暂且以非public类和public类来分。接下来开始获取你的java.lang.reflect.Method对象吧!

a.Method[] java.lang.Class.getMethods() throws SecurityException获取类或者接口中所有的public修饰的方法(包括基类中的)的java.lang.reflect.Method对象,返回Method数组

<span style="font-size:14px;">Method[] methods = modelClazz.getMethods(); for (Method method : methods) {System.out.println(method);}</span>

b.Method[] java.lang.Class.getDeclaredMethods() throws SecurityException获取类或者接口中所有的的方法(包括基类中的)的java.lang.reflect.Method对象,返回Method数组
<span style="font-size:14px;">Method[] declaredMethods = modelClazz.getDeclaredMethods();for (Method method : declaredMethods) {System.out.println(method);}</span>

c.Method java.lang.Class.getDeclaredMethod(String name, Class<?>... parameterTypes) throws
 NoSuchMethodException, SecurityException
获取类或者接口中指定方法签名的方法(包括基类中的)的java.lang.reflect.Method对象,返回Method对象,方法签名包括两部分,名称和参数列表,返回值不算其中的一部分,注意在调用该方法的时候参数列表的类型和名称一定要正确否则回报NoSuchMethodException。
<span style="font-size:14px;">try {        Method declaredMethod = modelClazz.getDeclaredMethod("method1",String.class,int.class);        System.out.println(declaredMethod.getName());            } catch (NoSuchMethodException e) {        e.printStackTrace();    } catch (SecurityException e) {        e.printStackTrace();    }</span>
d.Method java.lang.Class.getMethod(String name, Class<?>... parameterTypes) throws
 NoSuchMethodException, SecurityException获
取类或者接口中指定方法签名的public修饰的方法(包括基类中的)的java.lang.reflect.Method对象,返回Method对象,方法签名包括两部分,名称和参数列表,返回值不算其中的一部分,注意在调用该方法的时候参数列表的类型和名称一定要正确否则回报NoSuchMethodException。
<span style="font-size:14px;">try {        Method method = modelClazz.getMethod("method2",String.class,int.class);        System.out.println(method.getName());            } catch (NoSuchMethodException e) {        e.printStackTrace();    } catch (SecurityException e) {        e.printStackTrace();    }</span>

    既然已经获取到方法的Method的对象了,我们为什么不试试执行他呢。接着看吧!

a .反射调用public修饰的方法

<span style="font-size:14px;">try {<span style="color:#FF0000;">Object returnValue = method.invoke(modelClazz.newInstance(), "111",2);        System.out.println(returnValue);</span>} catch (IllegalAccessException e) {e.printStackTrace();} catch (IllegalArgumentException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();} catch (InstantiationException e) {e.printStackTrace();}</span>
    直接执行java.lang.reflect.Method.invoke(Object, Object...)方法就可以实现对方法的调用,该方法中由两类参数,一类是调用这个方法的对象,即那个对象调用了这个方法,第二类是你需要给方法传递的参数值数组,数组值的顺序就是参数类型的顺序,不然回报IllegalArgumentException。此外方法还应该有返回值,执行成功以后还会返回返回值的Object对象如果没有返回值则返回null

注意ModelClass中一定要有一个public 修饰的无参构造方法,否则modelClazz.newInstance()访问不到

.反射调用非public修饰的方法

<span style="font-size:14px;">try {        <span style="color:#FF0000;">System.out.println("method isAccessible  "+declaredMethod.isAccessible());        <span style="color:#FF0000;">declaredMethod.setAccessible(true);</span>        Object returnValue = declaredMethod.invoke(modelClazz.newInstance(), "111",2);        System.out.println(returnValue);</span>    } catch (IllegalAccessException e) {        e.printStackTrace();    } catch (IllegalArgumentException e) {        e.printStackTrace();    } catch (InvocationTargetException e) {        e.printStackTrace();    } catch (InstantiationException e) {        e.printStackTrace();    }</span>
   由上可以看出对于一个非public的方法在调用之前需要先执行setAccessible方法传入true值,才能访问不然回报IllegalAccessException

是不是很神奇,在上面的代码中我们完全没有创建ModelClass的实例但是我们执行了他之中的方法。其实在这里我们还能看到ModelClass这个类的源码。但是其实真的是不需要源码的就可以执行的。试想我们的web容器,我们程序员可以随意的在Controller层中处理请求的action,我们定义的时候是不要告诉web容器的,但是他在运行系统的时候就能根据解析请求路径找到正确的action处理请求。这其实就是反射调用方法,是不是 很强大。

通过Class对象获取java.lang.reflect.Field对象


a.java.lang.Class.getFields()获取所有的public域的Field对象,返回Field数组。

<span style="font-size:14px;">Field[] fields = modelClazz.getFields();for (Field field : fields) {System.out.println(field);}</span>

b.java.lang.Class.getDeclaredFields()获取所有的域的Field对象,返回Field对象数组。

<span style="font-size:14px;">Field[] declaredFields = modelClazz.getDeclaredFields();for (Field field : declaredFields) {System.out.println(field);}</span>

c.java.lang.Class.getField(String)获取指定名称的public修饰的域的Field对象

<span style="font-size:14px;">Field field =null;try {System.out.println("----------------------------------------------------------");<span style="color:#FF0000;">field = modelClazz.getField("field1");</span>System.out.println(field);} catch (NoSuchFieldException e) {e.printStackTrace();} catch (SecurityException e) {e.printStackTrace();}</span>

d.java.lang.Class.getDeclaredField(String)获取指定名称的域的Field对象。

<span style="font-size:14px;">Field declaredField=null;try {System.out.println("----------------------------------------------------------");<span style="color:#FF0000;">declaredField = modelClazz.getDeclaredField("field2");</span>System.out.println(declaredField);} catch (NoSuchFieldException e) {e.printStackTrace();} catch (SecurityException e) {e.printStackTrace();}</span>

按照之前的套路,既然已经获取到了域的Field对象了那么我们何不试着通过Field对象改变和使用一下对应域的值呢!


获取和改变public修饰的域的值

try {
        Object object = field.get(instance);
        System.out.println("field1`s value  ->"+object);
        System.out.println("--------------------------------------------------------------");
        field.set(instance, "field1 value changed!");
        System.out.println(field.get(instance));
    } catch (IllegalArgumentException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }

不难从代码中看出,获取某个对象的某个域的值只需要通过垓域的Field的对象的get系列方法就能返回特定类型的域值,同样如果想为一个对象的域设置值,那么也可以通过调用对应域的Filed对象的set方法。


操作非public修饰的域的值

<span style="font-size:14px;">try {<span style="color:#FF0000;">declaredField.setAccessible(true);</span><span style="color:#FF0000;">Object object = declaredField.get(instance);</span>System.out.println("field2`s value  ->"+object);System.out.println("--------------------------------------------------------------");<span style="color:#FF0000;">declaredField.set(instance, "field2 value changed!");</span>System.out.println(declaredField.get(instance));} catch (IllegalArgumentException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();}</span>

从代码中可以看出想要访问一个非public的域也是调用一个setAccessable的方法,并且传递参数true。



对AccesObjectAPI的讲解


或者大家也可以直接打开jdkAPI进行查看



反射中的异常


1.  java.lang.ClassNotFoundException :了解了java类的生命周期以后可以直到这个异常是因为没有类加载器能够加载到类而抛出的异常。是开发中最常遇到的异常,例如,在反射中获取Class对象的时候就会要求用户显示的处理这个异常。

2.  java.lang.IllegalAccessException : 当一个程序想要利用反射去创建一个对象,修改域,调用方法但是又没有权限的时候就会跑出这个异常。例如使用反射调用非public方法的时候。

3. java.lang.NoSuchMethodException : 当找不到对应的方法的时候就会跑出这个异常。

4. java.lang.NoSuchFieldException : 没有特定名称的域的时候会跑出这个异常。

5. java.lang.SecurityException : 当违反了java安全机制的时候就会抛出。

6. java.lang.IllegalArgumentException : 当传递给执行方法的参数是不合适的不合法的时就会抛出这个错。

7. java.lang.reflect.InvocationTargetException :这是反射中特别重要的一个异常,这是代表你在反射调用一个方法的时候方法内部出现异常的包装异常类,可以通过个getTargetException()方法获取方法内部的异常信息。


最后总结一下这篇文章吧,写这篇文章初衷是因为要给学习讨论班,为了讲的更清晰就动手写了,其中很大一部分都是自己亲力亲为敲得测试代码、查的API。其实反射是一个很大的机制,想要一次写全实在太难,但是,过程中为了全面的了解反射API也查了好些博客,所以发现了几篇写的好的博客,在这里推荐一篇我的文中没有讲到的反射中的Type接口的讲解 
大家可以参照一下,博主是一个很厉害的人博文也写的很好。

还要吐槽一下CSDN的编辑器,总共两个版本的编辑器都写分别用着写了一篇博文,但是这的是不太好用,每篇都重写了三遍左右,每次保存到草稿箱回去打开寝室电脑再继续编写都发现没有了害的我有重新来,气得我吧我寝室桌子都快捶坏了。不知道是我刚开始用csdn不会用还是我不是会员所以没有他们核心的好用的功能。好吧就这样吧,活到老学到老。对文中内容有问题或者是想要讨论技术的java君们都可以可我留言哦哦!









   















































1 0