上一篇《java注解学习笔记》中最后说到了注解的实现主要依赖java的反射机制,那么这一篇主要介绍一下java的反射机制。
什么是java反射机制
java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
具体的api
主要的api都在java.lang.reflect.*这个包中(除了注解外)。
各种获取注解、构造器、方法、属性的示例:
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 58 59 60 61 62 63
| // 获取所有注解 Annotation[] allAnnotations = studentClass.getAnnotations(); Annotation[] annotations = studentClass.getDeclaredAnnotations(); // 获取特定的注解 Annotation annotation = studentClass.getAnnotation(Deprecated.class); // 获取所有构造器 Constructor[] allConstructors = studentClass.getConstructors(); // 获取特定构造器 Constructor allConstructor; try { allConstructor = studentClass.getConstructor(String.class, int.class, int.class); } catch (NoSuchMethodException e) { throw e; } // 获取所有构造器 Constructor[] constructors = studentClass.getDeclaredConstructors(); // 获取特定构造器 Constructor constructor; try { constructor = studentClass.getDeclaredConstructor(String.class); } catch (NoSuchMethodException e) { throw e; } // 获取所有方法 Method[] allMethods = studentClass.getMethods(); Method allMethod; // 获取特定方法 try { allMethod = studentClass.getMethod("answer"); } catch (NoSuchMethodException e) { throw e; } // 获取所有本类定义的方法 Method[] methods = studentClass.getDeclaredMethods(); Method method; try { method = studentClass.getDeclaredMethod("answer"); } catch (NoSuchMethodException e) { throw e; } // 获取所有字段 Field[] allFields = studentClass.getFields(); Field allField; try { allField = studentClass.getField("name"); } catch (NoSuchFieldException e) { throw e; } // 获取本类定义的字段 Field[] fields = studentClass.getDeclaredFields(); Field field; try { field = studentClass.getDeclaredField("age"); } catch (NoSuchFieldException e) { throw e; }
|
通过反射调用方法:
1 2 3 4
| constructor.setAccessible(true); Object obj = constructor.newInstance("lkxiaolou"); // 执行方法 String str = (String) method.invoke(obj);
|
总结一下:
- 可以获取到的内容有:构造器、方法(定义的方法)、属性(定义的属性),静态的属性和方法都能拿到。
- getDeclaredXXX(getDeclaredConstructor(s),getDeclaredMethod(s),getDeclaredField(s))是获取定义的构造器,方法,属性,定义是指写在那个类中的,继承的不算。相似地getXXX(getConstructor(s),getMethod(s),getField(s))是获取所有的构造器、方法、属性,包括继承的。
- 通过第二点拿到的构造器、方法、属性包含了public到private的各种修饰。
- 如果取到的构造器、方法、属性是不可访问的(如public,protected),在用Method.invoke访问这些时会抛出一个java.lang.IllegalAccessException异常,除非在访问前设置可见属性为true。
反射的用途
根据配置运行相对应的方法
这个很好理解,可以通过配置的方法名来改变运行时执行的方法。
动态代理
动态代理是代理的一种,代理是一种设计模式,一般有动态代理与静态代理,动态代理主要依赖InvocationHandler和Proxy,我们这里给一个简单的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| package reflect; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class MyInvocationHandler implements InvocationHandler { private Object targetObject; public MyInvocationHandler(Object targetObject) { this.targetObject = targetObject; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("代理处理..." + method.getName()); return method.invoke(targetObject, args); } }
|
1 2 3 4
| package reflect; public interface Person { public String answer(); }
|
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
| package reflect; public class Student implements Person { public String name; public Integer age; public Student(String name, int age) { this.name = name; this.age = age; } private Student(String name) { this.name = name; } @Override public String answer() { return "I am " + name + ", and " + age + " years old"; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } }
|
1 2 3 4 5
| Student student = new Student("lk", 18); MyInvocationHandler h = new MyInvocationHandler(student); Person person = (Person) Proxy.newProxyInstance(h.getClass().getClassLoader(), student.getClass().getInterfaces(), h); String s = person.answer(); System.out.println(s);
|
运行一下可以看到
调试中出现一个小插曲
我在调试的时候出现了这样的结果
每次我鼠标放在person上,控制台就多一次输出,这是为啥,我把method打印出来,发现执行了toString方法,所以这里应该是IDE执行toString方法触发了动态代理
绕过泛型检查
严格来说这并不是一个有用的特性,就像我们可以用反射机制来强行执行private方法,只是提供一种思路,但不是很好。
这是后加的一段,第二天发现在给private方法做单元测试的时候,以前我都是把private改成public,测完再改回去,现在发现可以用发射来做,这样就不用改代码啦。
泛型检查是在编译期做的检查,如果要这么写:
1 2 3 4
| List<String> list = new ArrayList<>(); list.add("1"); list.add("2"); list.add(3); // 编译不通过
|
这样是通过不了编译的。但是我们可以这样做:
1 2 3 4 5 6 7 8
| List<String> list = new ArrayList<>(); list.add("1"); list.add("2"); Method listAddMethod = ArrayList.class.getMethod("add", Object.class); listAddMethod.invoke(list, 3); for (Object object : list) { System.out.println(object); }
|