--------- 、、期待与您交流! ----------

一、Class类(反射的基石)

1、在了解什么是反射之前,我们可以先了解一下Class这个类。我们都知道,Person类代表人,它的

实例对象就是张三,李四这样一个个具体的人,Java程序中的各个Java类属于同一类事物,描述这类事物的Java类名就是Class。也就是这样:

  • 人------>Perosn

  • Java类------>Class

2、Class类代表Java类,它的各个实例对象分别对应这些实例在内存中的字节码。

如内存中存在三份字节码,那么这三份字节码都是Class类的实例对象。

在Java程序中获得Class对象通常有如下3种方式:

①使用Class类的forName(String clazzName)静态访求。该方法需要传入字符串参数,该字符串参数的值是某个类的全限定类名(必须是完整的包名):

Class.forName("类名"),例如,Class.forName("java.util.Date");

②调用某个类的class属性来获取该类对应的Class对象。例如,Person.class将会返回Person类对应的Class对象。

类名.class,例如,System.class

③调用某个对象getClass()方法。该方法是java.lang.Object类中的一个方法,所以所有的Java对象都可以调用该方法,该方法将会返回该对象所属类对应的Class对象。

对象.getClass(),例如,new Date().getClass()

   第二种方式在反射应用是最常用的,它具有如下两种优势:

  • 代码更安全。程序在编译阶段就可以检查需要访问的Class对象是否存在程序性能更好。

  • 因为这种方式无须调用方法,所以性能更好。

   第一种方式,因为传递的是字符串,所以有可能会抛出ClassNotFoundException异常,但这个方法因为传递是的字符串,所以我们可以把它当作从外面传递进来参数来使用。

3、Java中有九个预定义Class实例对象:

boolean,byte,char,short,int,long,float,double 再加一个特殊的 void

下面做一简单的测试:

package itheimareview;class ReflectTest1{    public static void main(String[] args)    {        String str1 = "abc";        Class cls1 = str1.getClass();        Class cls2 = String.class;        Class cls3 = Class.forName("java.lang.String");        System.out.println(cls1 == cls2);//true        System.out.println(cls1 == cls3);//true        System.out.println(cls1.isPrimitive());//false        System.out.println(int.class.isPrimitive());//true        System.out.println(int.class == Integer.class);//false        System.out.println(void.class.isPrimitive());//true        System.out.println(int.class == Integer.TYPE);//true        System.out.println(int[].class.isPrimitive());//false        System.out.println(int[].class.isArray());//true    }}

4、注意:

   只要是在源程序中出现的类型,都有各自的Class实例对象,例如,int[],void…

二、反射(Reflect)

1、反射主是把Java类中的各种成分包括其本身映射成相应的java类。其本身就可以用一个Class类来表示,这个类中的可能有的组成部分:包,成员变量,方法,构造方法,注释等信息,这些信息就是用相应类的实例对象来表示,它们是Package、Field、Method、Contructor、Annotation等。

2、一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象,这些实例对象都有各自的成分,如:方法,构造方法,成员变量等,然后可以通过这些成分对应的类进行进一步操作。

3、Class.newInstance()方法:

例子:String obj = (String)Class.forName("java.lang.String").newInstance();该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象。

   思考:该方法内部的具体代码是怎样写的呢?

   用到了缓存机制来保存默认构造方法的实例对象。

4、具体操作以例子来说明:

示例一:

Constructor类:它代表某类中的一个构造方法。

如我们想反射的方式实现如下代码:

//new String(new StringBuilder("abc"));
package itheimareview;import java.lang.reflect.Constructor;class ReflectTest2{    public static void main(String[] args)    {          /*        先得到String字节码,得到了String类的字节码文件,就相当于得到了String类        里的各种成分,然后通过getConstructor()获取成分中的构造方法的那一部分,其        参数传递的是所要获取构造方法中需要传递参数的Class对象(也就是字节码文件)        它返回的还Constructor对象        */        Constructor constructor1 = String.class.getConstructor(StringBuilder.class);        /*        这个地方我们从里往外看,new StringBuilder("abc")就是创建一个指定参数的StringBuilder        实例,然后Constructor调用newInstance()方法,实际上就是调用它对应的构造方法,        因为传递进了一个StringBuilder对象,所以对应的就是如下方法:        String(StringBuilder builder)        因为编译的时候并不知道右边是什么类型,也就是编译时期jvm并没有把        “String.class.getConstructor(StringBuilder.class);”这句代码当作宏变量        传递给了constructor1,所以这里要进行类型的强制转换。        */        String str2 = (String)constructor1.newInstance(new StringBuilder("abc"));        //打印输出结果        System.out.println(str2.charAt(2));//c        //经过上面的分析,我们不难看出上面代码是下面的代码的简写形式。        StringBuilder sBuilder = new StringBuilder("abc");        Class parameterTypes = sBuilder.getClass();        Constructor constructor2 = String.class.getConstructor(parameterTypes);        String str3 = (String)constructor2.newInstance(sBuilder);        System.out.println(str3)//abc    }}

示例二:

Field类:它代表某一个类的一个成员变量

首先定义一个有成员量的类:

package itheimareview;public class ReflectPoint1{      private int x;    public int y;//本该私有,这里只作测试用。    public ReflectPoint(int x, int y)    {        super();        this.x = x;        this.y = y;    }}

测试类:

import java.lang.reflect.Field;class ReflectTest3{    public static void main(String[] args)    {          ReflectPoint pt1 = new ReflectPoint(3, 5);        Field fieldY = pt1.getClass().getField("y");        /*        fieldY的值是多少?是5,错,fieldY不是对象身上的变量,而是类上的,        要用它去取某个对象上对应的值        */        System.out.println(fieldY);//public int itheimareview.ReflectPoint.y        System.out.println(fieldY.get(pt1));//5        /*        下面这一行会出现异常:NoSuchFieldException,是因为x被私有化了,看不到,        java提供了一个方法,可以忽视,getDeclaredField();        */        //Field fieldX = pt1.getClass().getField("x");        Field fieldX = pt1.getClass().getDeclaredField("x");                                                                                              /*        解决了上面的问题后发现下面这句话会发生:IllegalAccessException异常        因为它被私有化了,没有访问权限。如果不能理解,可以这样想:先前私有化        了别人是看不到的,如你存了100W在银行,别人不知道,后来你把存折给别人看        别人发现上面有100W,不可能因为看了一下存折就可以取走你的钱了吧?        显然是不可以的。java中又提供一个方法setAccessible(true);这个方法是        Field的父类的AccessibleObject的方法(AccessibleObject有Constructor,Field,        Method三个子类)值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。        我们称其为暴力反射        */        fieldX.setAccessible(true);//暴力反射        System.out.println(fieldX.get(pt1));//3    }}

示例三:

Method类代表某个类中的一个成员方法。

package itheimareview;import java.lang.reflect.Method;class ReflectTest4{    public static void main(String[] args)    {          Method methodCharAt = String.class.getMethod("charAt", int.class);        /*        invoke是方法对象上的方法,如人在黑板上面画圆,有人,黑板,圆三个对象,        画圆时有圆心和半径两个参数,那么圆心和半径肯定是圆身上的,而画圆时要用        到圆心和半径,圆心和半径是私有的,如果把圆心和半径分配给人,也就是把画圆        方法分配给人,那么就是人要访问圆的私有属性,这显然不合适,所以只有分配        给圆才合适。这也是面向对象的思想,谁拥有数据,谁就是操作这数据的专家        */        System.out.println(methodCharAt.invoke(str1, 1));//b               //Object[] obj  = new Object[]{"abc",1,"fa"};        //JDK1.4里的语法规则。        System.out.println(methodCharAt.invoke(str1, new Object[]{2}));//c                                                                                        /*        如果传递给Method对象的invoke()方法的第一个参数为null,说明该Method对象        对应的是一个静态方法,如下代码展示:        */        char[] chs = new char[]{'a','b','c','d'};        //String.valueOf(chs);        Method methodValueOf = String.class.getMethod("valueOf", char[].class);        System.out.println(methodValueOf.invoke(null, chs));//abcd        System.out.println("---------------------------------------------");        //普通方法        //TestArguments.main(new String[]{"111","222","333"});        String startingClassName = args[0];        Method mainMethod = Class.forName(startingClassName).getMethod("main", String[].class);        /*        思考:为什么要加Object?        请参考JDK1.4和1.5JDK的invoke方法:        Jdk1.5:public Object invoke(Object obj,Object... args)        Jdk1.4:public Object invoke(Object obj,Object[] args)        main方法只接收一个String[]数组类型的数据,当new String[]{"111","222","333"};时,        java为了兼容以前的版本会把它当作三个参数来传递。所以会发生以下异常:        IllegalArgumentException抛出的异常表明向方法传递了一个不合法或不正确的参数。        */        mainMethod.invoke(null, (Object)new String[]{"111","222","333"});        //或        mainMethod.invoke(null, new Object[]{new String[]{"111","222","333"}});    }}

以上的三个类正是java.lang.reflect.AccessibleObject类的三个直接子类。

三、数组的反射:

1.具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。

2.代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class。

3.基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。

4、Array类:它提供了动态创建和访问 Java 数组的方法。

5、具体应用如以下例子:

package itheimareview;import java.util.Arrays;import java.lang.reflect.Array;class ReflectTest5{    public static void main(String[] args)    {        int[] a1 = new int[]{1,2,3};        int[] a2 = new int[4];        int[][] a3 = new int[2][3];        String [] a4 = new String[]{"a","b","c"};        System.out.println(a1.getClass().equals(a2.getClass()));//true        System.out.println(a1.getClass() == a2.getClass());//true        System.out.println(a1.getClass().equals(a3.getClass()));//false        System.out.println(a1.getClass().equals(a4.getClass()));//false        /*        最后两句代码为什么会报错?        错误信息:Incompatible operand types Class
and Class
至今未解决(所用版本1.7)。 System.out.println(a1.getClass() == a3.getClass()); System.out.println(a1.getClass() == a4.getClass()); */ System.out.println("----------------------------------------------------"); System.out.println(a1.getClass().getSuperclass().getName()); System.out.println(a3.getClass().getSuperclass().getName()); System.out.println(a4.getClass().getSuperclass().getName());// 发现它们的父类都是Object Object aObj1 = a1;//因为int[]是Object Object aObj2 = a4;//因为String[]是Object// Object[] aObj3 = a1;会报错,因为int不是Obejct Object[] aObj4 = a3;//因为int[]是Object Object[] aObj5 = a4;//因为String是Object System.out.println(a1);//[I@f47396 System.out.println(a4);//[Ljava.lang.String;@d0af9b System.out.println("----------------------------------------------------"); /* 思考为什么以下int类型的没有打印出来? 首先要明白JDK1.4和JDK1.5Arrays的asList方法的区别: 1.4:public static List asList(Object[] a); 1.5: publci static
List
asList(T... a); 如果传的是String数组,它符合JDK1.4的语法,那么他就按JDK1.4来操作,如果 传了了一个int类型数组,显然JDK1.4不匹配(int[] a1 = new int[]{1,2,3}; Object[] aObj3 = a1;会报错),这时就会调用JDK1.5的方法 这时它会把int[]当作一个Object来处理,而不是当作Object数组来处理。 */ System.out.println(Arrays.asList(a1));//[[I@d0af9b] System.out.println(Arrays.asList(a4));//[a, b, c] System.out.println("----------------------------------------------------"); Object obj = "xyz"; printObject(obj); printObject(a1); /* 实现一个功能,当传递进来的对象是数组时,就打打印其元素,如果不是数组,原样打印。 这时用反射是非常方便的。 */ private static void printObject(Object obj) { /*获取传递进来的对象的Class对象,以便获取isArray()方法来判断对象 是否是数组类。*/ Class clazz = obj.getClass(); if (clazz.isArray()) { //Array.getLength(Object array):以 int 形式返回指定数组对象的长度 int len = Array.getLength(obj); for (int i = 0; i < len; i++) { //get(Object array, int index):返回指定数组对象中索引组件的值。 System.out.println(Array.get(obj, i)); } } else { System.out.println(obj); } } }}

四、泛型与反射

1、泛型在反射中的应用,除了将反射中运行的ClassCastException异常转移到编译时期,还有就是通过反射来获取泛型信息。通过反射来获取泛型信息是比较重要的知识点。

2、如:我们想提取一个带泛型对象上的泛型的类型,很显然通过普通的方式是做不到的,因为泛型是编译时期就去类型化了。这时候我们就要用到反射了。下面将通过两种方式来获取:

①方式一:

   通过方法来获取:

   说明:因为Method方法里有一个getGenericParameterTypes()的方法,它的返回值类型是Type[],Type是Java 编程语言中所有类型的公共高级接口。它们包括原始类型、参数化类型、数组类型、类型变量和基本类型。它底下有一个叫ParameterizedType(表示参数化类型)子类,这个子类有三个方法:

  • Type[] getActualTypeArguments():返回表示此类型实际类型参数的Type对象的数组。

  • Type getOwnerType():返回Type对象,表示此类型是其成员之一的类型。

  • Type getRawType():返回 Type对象,表示声明此类型的类或接口。

   我们可以通过这些方法来获取泛型信息,如下代码展示:

package itheimareview;import java.lang.reflect.Method;import java.lang.reflect.ParameterizedType;import java.lang.reflect.Type;import java.util.Map;public class GenericTest{    public static void main(String[] args) throws Exception    {        // TODO Auto-generated method stub        Method applyMethod = GenericTest.class.getMethod("applyMap", Map.class);        Type[] types = applyMethod.getGenericParameterTypes();        for (Type type : types)        {            if (type instanceof ParameterizedType)            {                ParameterizedType pType = (ParameterizedType)type;                //下面将打印:java.util.Map
System.out.println(pType); //下面将打印:sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl System.out.println(pType.getClass().getName()); Type[] types2 = pType.getActualTypeArguments(); /*下面循环将打印如下两个泛型的类型: class java.lang.String class java.lang.Integer*/ for (Type type2 : types2) { System.out.println(type2); } } else { System.out.println("获取泛型类型出错!"); } } } public static void applyMap(Map
map) { System.out.println("用方法获取泛型的类型"); } }

②方法二:

   通过字段来获取,过程雷同:

package itheimareview;import java.lang.reflect.Field;import java.lang.reflect.ParameterizedType;import java.lang.reflect.Type;import java.util.Map;public class GenericTest2{    //定义一个私有的成员变量    private Map
value; public static void main(String[] args) throws Exception { //先获取当前类的Class,以便获取字段Field Class
clazz = GenericTest2.class; //获取字段 Field field = clazz.getDeclaredField("value"); //因为私有化了,所以不能用下面这种方法 //Field field = clazz.getField("value"); //获取超类Type Type gType = field.getGenericType(); if (gType instanceof ParameterizedType) { ParameterizedType pType = (ParameterizedType)gType; //下面将打印:java.util.Map
System.out.println(pType); //下面将打印:sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl System.out.println(pType.getClass().getName()); Type[] types = pType.getActualTypeArguments(); /*下面循环将打印如下两个泛型的类型: class java.lang.String class java.lang.Integer*/ for (Type type : types) { System.out.println(type); } } }}

--------- 、期待与您交流! ----------