所谓反射,是指在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
反射有如下的功能:
- 在运行时判断任意一个对象所属的类;
- 在运行时构造任意一个类的对象;
- 在运行时判断任意一个类所具有的成员变量和方法;
- 在运行时调用任意一个对象的方法;
- 生成动态代理。
这里的反射主要通过Class
类来实现。
一、Class类的使用
Class
类的实例表示java 应用运行时的类或者接口,包括数组、基本类型(int/long/Indeger/String/...
)、关键字(void/...
)等。没有共有构造方法,在使用new创建对象或者加载ClassLoader时,由JVM自动调用。
每个java类运行时都在JVM里表现为一个class对象,可通过类名.class,类型.getClass(),Class.forName(“类名”)等方法获取class对象。
可以通过下面三种方式获得Class对象:
1.通过 类名.class 获取,可以认为每个类都有一个静态的Class对象。
1
| Class c1 = Person.class;
|
2.通过类的对象获取
1 2
| Person person = new Person(); Class c2 = person.getClass();
|
3.通过类名获取,动态加载(这个再很多地方都有使用,如:加载数据库驱动)
1 2 3 4 5 6
| Class c3 = null; try { c3 = Class.forName("Person"); } catch (ClassNotFoundException e) { e.printStackTrace(); }
|
上面的c1/c2是Person的类类型,Person是Class类的对象。可以理解为一切类都是Class的对象。
打印上面三种方法获取的Class对象:
1 2 3
| System.out.println(c1 == c2);//true System.out.println(c1 == c3);//true System.out.println(c2 == c3);//true
|
上面三种方式获取的Class对象,两两均相等。可见,可以理解为每个对象都有一个静态的Class变量。因为在java 中,Object 是一切类的父类,在Object中有一个获取Class对象的native(由C++或C语言实现,通过java 调用,具体的可参见jni相关的知识)方法。如下部分Object开头源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class Object { // ... /** * Returns the runtime class of this {@code Object}. The returned * {@code Class} object is the object that is locked by {@code * static synchronized} methods of the represented class. * * <p><b>The actual result type is {@code Class<? extends |X|>} * where {@code |X|} is the erasure of the static type of the * expression on which {@code getClass} is called.</b> For * example, no cast is required in this code fragment:</p> * @return The {@code Class} object that represents the runtime * class of this object. * */ public final native Class<?> getClass(); // ...
|
通过上面 getClass 方法前的部分注释,可以清楚的明白他的作用。
二、动态加载类
在java 中,使用new创建的对象都是静态加载的,这些类必须存在,不管在实际中是否使用,否则编译不通过。可以通过动态加载类来解决该问题。
通过Class类的forName方法进行加载,传入相应的名加载具体的类,可以使程序的扩展性更好,避免编译出错。
该方法的原型为:
1
| public static Class<?> forName(String className)
|
三、使用Class获取类信息
Class类提供的部分获取类信息的方法如下:
方法名 |
说明 |
getFields() |
获取所有public类型的成员变量 |
getDeclaredFields() |
获取该类自己声明的所有变量 |
getConstructors() |
获取所所有public类型的构造方法 |
getDeclaredConstructors() |
获取所有的构造方法 |
getMethods() |
获取所有的public函数,包括父类继承来的 |
getDeclaredMethods |
获取该类所有声明的方法 |
上面分别为获取成员变量、构造方法和成员方法。
获取成员参数
1 2 3 4 5 6 7 8 9 10
| public class ClassUtils { public static void getClassField(Object obj) { Class c = obj.getClass(); Field[] fs = c.getDeclaredFields();// c.getMethods(); for (Field f : fs) { Class fieldType = f.getType(); System.out.println(fieldType.getName() + " " + f.getName()); } } }
|
上述代码是获取所有成员变量的,获取的每个成员方法都是 Fidld
类型的对象。
如下测试代码获取String类中的成员变量:
1
| ClassUtils.getClassField("reflect");
|
运行结果:
1 2 3 4 5
| [C value int hash long serialVersionUID [Ljava.io.ObjectStreamField; serialPersistentFields java.util.Comparator CASE_INSENSITIVE_ORDER
|
其中第一个和第三个表示的是数组。查看String中的源码,成员变量如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public final class String implements java.io.Serializable, Comparable<String>, CharSequence { private final char value[]; private int hash; // Default to 0 private static final long serialVersionUID = -6849794470754667710L; private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0]; //... public static final Comparator<String> CASE_INSENSITIVE_ORDER = new CaseInsensitiveComparator(); //... }
|
同样的可以调用该方法查看其他类中的成员变量。
获取构造方法和成员方法
类似于上面获取成员变量的方法,如下获取构造函数和成员方法则很好理解了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| public class ClassUtils { //... /** * 获取所有的成员方法 * * @param obj */ public static void getClassMethods(Object obj) { Class c = obj.getClass(); System.out.println("类名为:" + c.getName()); Method[] ms = c.getDeclaredMethods(); for (Method m : ms) { Class returnType = m.getReturnType(); //返回值的类类型,如String-->String.class System.out.print(returnType.getName() + " "); //方法名称 System.out.print(m.getName() + "("); //获取参数--参数列表的类类型 Class[] paramTypes = m.getParameterTypes(); int paramsNum = paramTypes.length; Class paramType; for (int i = 0; i < paramsNum; i++) { paramType = paramTypes[i]; if (i == 1 || (i == paramsNum - 1)) System.out.print(paramType.getName()); else System.out.print(paramType.getName() + " ,"); } System.out.println(")"); } } /** * 获取所有的构造函数 * * @param obj */ public static void printConMessage(Object obj) { Class c = obj.getClass(); Constructor[] cs = c.getDeclaredConstructors(); for (Constructor constructor : cs) { System.out.print(constructor.getName() + " ("); Class[] paramTypes = constructor.getParameterTypes(); int paramsNum = paramTypes.length; Class paramType; for (int i = 0; i < paramsNum; i++) { paramType = paramTypes[i]; if (i == 1 || (i == paramsNum - 1)) System.out.print(paramType.getName()); else System.out.print(paramType.getName() + " ,"); } System.out.println(")"); } } }
|
此处省略测试。
四、方法反射的基本操作
对于方法的获取,可以通过方法名称和参数列表来确定某个方法。主要使用了 Method.invoke(对象,参数列表);
操作。
首先需要获取类信息,然后指定方法名和参数获取方法对象,再通过Method.invoke()
进行反射获取方法执行结果。若该方法无返回值,则invoke方法最后返回null,否则返回相应的结果。
如下示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| public class TestDemo { public static void main(String[] args) { //1.获取类信息 A a = new A(); Class aClass = a.getClass(); //2.获取方法名称和参数 try { // Method method = aClass.getMethod("print",new Class[]{int.class,int.class}); Method method = aClass.getMethod("print", int.class, int.class); //方法返回结果:若无返回结果则o为null,否则为返回结果,这里返回为30 Object o = method.invoke(a, 10, 20); System.out.println(o);//30 } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } } //测试反射方法 class A { public int print(int a, int b) { System.out.println(a + b); return a + b; } public void print(String a, String b) { System.out.println(a.toUpperCase() + "-----" + b.toUpperCase()); } }
|
其中 Method()和invoke()方法第二个参数为可变数组,可有可无,如果有接写。可以通过new数组的方式或者直接一个个写出。
五、通过反射了解集合泛型的本质
我们都知道,在java中,定义集合指定什么泛型,就只能添加该类型的数据到集合中,否则就会报错。
通过反射可以往集合中添加不同类型的数据,这样虽没有多大意义,不便于遍历。这里主要说明了集合在编译后是去泛型化的,集合的泛型可以防止错误输入,在编译极前有效。
如下测试代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class TestDemo04 { public static void main(String[] args) { ArrayList<String> l1 = new ArrayList<>(); ArrayList l2 = new ArrayList(); l1.add("java reflect"); // datas.add(100);//报错 Class c1 = l1.getClass(); Class c2 = l2.getClass(); System.out.println(c1 == c2);//true try { Method method = c2.getMethod("add", Object.class); method.invoke(l1, 100); System.out.println("数组大小:"+l1.size()); System.out.println("数组内容:"+l1.toString()); } catch (Exception e) { e.printStackTrace(); } } }
|
输出结果:
1 2 3
| true 数组大小:2 数组内容:[java reflect, 100]
|
上面的代码,通过反射往String类型的集合中添加了int类型的100。
通过对反射的学习,发现反射是从.class到.java的过程,感觉像反编译字节码。以 Class c1 = Person.class;
为例,Person.java类在编译后生成的字节码文件为Person.class,这里获取的Class正式这样的。
上一篇:java 知识之 注解的使用和解析
下一篇:java 知识 之 内存管理