Java中很多框架和应用都会使用反射,本文简单的介绍了Java的反射。
简介
Reflection和Introspection这两个机制都是作用在Java运行时。
每一个类在JVM运行时,都是一个Class实例,该对象描述了响应类的全部属性,包括Annotation、Field、Method等等。
Java的反射和内省都非常的使用,在比较流行的框架Spring和MyBatis中就使用了反射和内省。
先看一个简单的Demo
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
| import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Parameter; import java.util.HashMap; import java.util.Map; import java.util.Objects; public class StartDemo { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { Class<HashMap> mapClazz = (Class<HashMap>) Class.forName("java.util.HashMap"); System.out.println(mapClazz.getName()); System.out.println("------------------------------------------"); Method[] methods = mapClazz.getMethods(); for (Method method : methods) { System.out.print(" " + method.getName() + "("); Parameter[] parameters = method.getParameters(); for (Parameter parameter : parameters) { System.out.print(parameter.getType().getName() + ","); } System.out.println(");"); } System.out.println("------------------------------------------"); Field[] fields = mapClazz.getDeclaredFields(); for (Field field : fields) { System.out.println(" " + Modifier.toString(field.getModifiers()) + " " + field.getName()); } Map<String,String> map = new HashMap<>(); Method putMethod = mapClazz.getMethod("put", Object.class,Object.class); Method getMethod = mapClazz.getMethod("get",Object.class); putMethod.invoke(map,"hello","world"); String world = (String) getMethod.invoke(map,"hello"); System.out.println("------------------------------------------"); System.out.println(world); } }
|
这个示例中首先通过Class的描述符获取ClassLoader中对应的Class实例对象。
第二步,遍历该类可访问方法(包含父类继承的)。
第三步,遍历该类声明的属性(不包括父类声明的属性)。
第四步,获取Map接口的put
方法,获取Map接口的get
方法。通过反射得到的put方法将[“hello”:”world”]放入到map对象中,在通过反射得到的方法get,获得map中对应的value。
Java反射的机制的主要作用:
- Jvm运行时校验类的信息。
- Jvm运行时修改类或者对象的信息。
- Jvm运行时动态创建对象实例。
- Jvm运行时动态调用对象方法。
- 修改构造函数、方法、属性的访问权限。
不足
- 性能消耗
- 由于反射是动态解析类的结构,所有不能执行包含了虚拟机优化的动作。因此,反射操作会比未使用反射的部分性能要差,应当避免在频繁调用的、性能敏感的代码块中使用反射。
- 安全限制
- 反射需要运行时的权限,当一个安全管理器内运行时,肯能是不存在该权限的。当代码必须在受限的安全上下文中调用时,这是一个重要的考虑因素,例如Applet。
- 私有信息曝光
- 由于反射允许非反射代码执行代码操作,这可能导致非法操作,例如访问私有(private)属性和变量,使用反射可能导致意外的副作用,可能是使代码功能失调和破坏可移植性。
反射会打破抽象设计,因此升级平台后可能导致不同的执行动作。
类
在Java中,每个对象不是一个引用类型,就是一个原始类型。所有的引用类型都继承自java.lang.Object
。类、枚举、数组和接口都是引用类型。
原始类型的固定集合是boolean
、char
、byte
、short
、int
、long
、float
、double
。
对于每种类型,JVM都会实例化一个不可变的java.lang.Class
实例,该实例提供了一系列的方法,检查该对象运行时的属性(包括成员和类型信息)。
java.lang.Class
还提供了创建新类和新对象的能力。最重要的是,它是所有反射接口的入口。
Class的获取方式
所有反射操作的入口,都是java.lang.Class
。有多种获取java.lang.Class
对象的方法,使用何种,取决于代码是否有权访问该类的对象、或者该类的名称、或者一个类型、或者一个已有的java.lang.Class
对象。
可以获取java.lang.Class
的方法和描述如下:
- Object.getClass()
如果有一个该类的实例,获取Class
最简单的方法就是通过调用对象的getClass()
方法获取。这只适用于引用类型的对象。
示例:
Class stringClazz = “foo”.getClass()
.class
语法
如果不能访问对象实例,可以访问对象的类型,那么可以通过”Class.class”语法获取Class实例。这个语法可用于原始类型和引用数据类型。
示例:
Class intClazz = int.class;
Class mapClazz = java.util.Map.class;
java.lang.Class.forName
方法
如果类的全限定名称是可用的,则可以通过静态方法Class.forName
获取Class对象。该方法不适用于原始类型。
数组的全限定名称的语法可以通过”Class.getName()”方法获取。该语法适用于引用对象的数组和原始类型的数组。
示例:
Class c = Class.forName(“com.duke.MyLocaleServiceProvider”);
Class cDoubleArray = Class.forName(“[D”);
Class cStringArray = Class.forName(“[[Ljava.lang.String;”);
- 原始类型包装类的TYPE属性
.class
语法用于获取一个原始类型的Class对象是更为方便的方式;但还有其他获取该对象的方式。
每一个原始类型和void类型都有一个在java.lang
包中的包装类,用于原始类型和引用类型之间的装箱和拆箱操作。
每一个对应的包装类,都有一个命名为TYPE
的属性,这个属性的值就是被包装的原始类型的Class实例。
- Class.getSuperclass()
返回该类的超类。
- Class.getClasses()
返回该类声明的或者从超类声明继承的内部公共类、接口、枚举。
- Class.getDeclaredClasses()
返回该类声明的内部类、接口、枚举。
- Class、Field、Method、Constructor.getDeclaringClass()
返回声明该Class、Field、Method、Constructor的Class,如果Class不是被其他Class所声明的、或者是匿名内部类,则该接口返回null。
1 2 3 4
| import java.lang.reflect.Field; Field f = System.class.getField("out"); Class c = f.getDeclaringClass();
|
- Class.getEnclosingClass()
返回声明该内部类、或者匿名类的Class对象。
1 2 3 4
| static Object o = new Object() { public void m() {} }; static Class<?> c = o.getClass().getEnclosingClass();
|
Class的方法
自身非私有:自身声明的成员,包括public、protected、default
继承的公共:父类声明的public成员
自身私有:自身声明的private成员
Class接口 |
自身非私有 |
继承的公共 |
自身私有 |
getDeclaredField() |
Yes |
No |
Yes |
getField() |
Yes |
Yes |
No |
getDeclaredFields() |
Yes |
No |
Yes |
getFields() |
Yes |
Yes |
No |
Class接口 |
自身非私有 |
继承的公共 |
自身私有 |
getDeclaredMethod() |
Yes |
No |
Yes |
getMethod() |
Yes |
Yes |
No |
getDeclaredMethods() |
Yes |
No |
Yes |
getMethods() |
Yes |
Yes |
No |
Class接口 |
自身非私有 |
继承的 |
自身私有 |
getDeclaredConstructor() |
Yes |
N/A |
Yes |
getConstructor() |
Yes |
N/A |
No |
getDeclaredConstructors() |
Yes |
N/A |
Yes |
getConstructors() |
Yes |
N/A |
No |
获取父类
通过Class实例的getSuperClass()
方法可以获取class的父类,
如果该Class实例表示的是Object、一个接口、一个原始类型或者void类型,该方法会返回null。
1 2 3 4
| Class<?> superClass = HashMap.class.getSuperclass(); System.out.println(superClass); System.out.println(Object.class.getSuperclass()); System.out.println(String[][].class.getSuperclass());
|
获取Package
getPackage()
将返回该类的包信息。可以调用Package.getName()
获取包名称。
1
| System.out.println(HashMap.class.getPackage().getName());
|
Package 对象包含有关 Java 包的实现和规范的版本信息。
规范的版本号使用了一个由句点 “.” 分隔的十进制正整数组成的语法,例如 “2.0” 或 “1.2.3.4.5.6.7”。这允许使用可扩展的编号来表示主版本号、次版本号、缩微版本号,等等。
修饰符
Java中共有四种访问限制修饰符:private、protected、缺省、public
- private:私有,内部访问
- 缺省:是默认的,同一个package下可以访问。
- protected:同一个packege或者子类可以访问
- public:共有,都可以访问
每种修饰符,具体可以修饰的组件如下:
修饰符 |
类 |
成员方法 |
构造方法 |
成员变量 |
局部变量 |
abstract(抽象的) |
√ |
√ |
- |
- |
- |
static (静态的) |
√ |
√ |
- |
√ |
- |
public(公共的) |
√ |
√ |
√ |
√ |
- |
protected(受保护的) |
√ |
√ |
√ |
- |
- |
private(私有的) |
√ |
√ |
√ |
√ |
- |
synchronized(同步的) |
- |
√ |
- |
- |
- |
native(本地的) |
- |
√ |
- |
- |
- |
transient(暂时的) |
- |
- |
- |
√ |
- |
volatie(易失的) |
- |
- |
- |
√ |
- |
final(不要改变的) |
√ |
√ |
- |
√ |
√ |
1 2 3 4
| System.out.println(Modifier.toString(HashMap.class.getModifiers())); System.out.println(Modifier.toString(AbstractMap.class.getModifiers()));
|
泛型的参数
getTypeParameters()
方法按照顺序返回定义该类所使用泛型参数信息。
1 2 3 4 5 6
| Class<?> hashClass = HashMap.class.getSuperclass(); TypeVariable<?>[] typeParameters = hashClass.getTypeParameters(); for (TypeVariable<?> typeVariable : typeParameters) { System.out.print(typeVariable+","); }
|
获取所有实现的接口
getGenericInterfaces()
:该方法返回一个Type数组,携带了泛型信息。表示该class所有实现的接口集合。
getInterfaces()
:该方法返回一个Class数组。表示该class所有实现的接口集合。
1 2 3 4 5 6 7
| Class<?> listClass = ArrayList.class; Type[] typeInterfaces = listClass.getGenericInterfaces(); System.out.println(Arrays.toString(typeInterfaces)); Class[] interfaces = listClass.getInterfaces(); System.out.println(Arrays.toString(interfaces));
|
操作对象
首先声明一个Demo类
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
| public class Person implements Comparable<Person>{ private int age = 0; public String name = "demo"; * 无参公共构造函数 */ private Person() { super(); } public Person(int age, String name) { this.age = age; this.name = name; } @Override public int compareTo(Person o) { return this.age - o.age; } private int getAge() { return age; } private Person setAge(int age) { this.age = age; return this; } public String getName() { return name; } public Person setName(String name) { this.name = name; return this; } @Override public String toString() { return "Person{" + "age=" + age + ", name='" + name + '\'' + '}'; } }
|
获取/设置公共属性
get()
方法返回的是一个对象,所以原始类型的属性,会返回相应的包装类。
如果属性是static
的,那么方法的对象入参需要是null
。
有一系列的set*()
方法,用于设置不同的类型的属性,需要获取相应属性的类型,来确定使用正确的set
方法。
如果属性是final
的,则会抛出java.lang.IllegalAccessException
。
1 2 3 4 5
| Field field = Person.class.getField("name"); Person person = new Person(1, "demo-01"); System.out.println(field.get(person)); field.set(person, "demo-02"); System.out.println(field.get(person));
|
获取/设置私有成员变量
私有的属性在类的外部是不能被访问的,但是使用反射,我们可以关闭私有属性的访问限制。
1 2 3 4 5 6
| Field field = Person.class.getDeclaredField("age"); Person person = new Person(1, "demo-01"); field.setAccessible(true); System.out.println(field.get(person)); field.set(person, 2); System.out.println(field.get(person));
|
调用公共方法
我们可以通过调用Method.invoke()
方法来调用一个方法。
如果这个方法是一个static
的方法,则相应的对象入参,需要设置为null
。
1 2 3 4 5
| Method setMethod = Person.class.getMethod("setName", String.class); Person person = new Person(1, "demo-01"); System.out.println(setMethod.invoke(person, "demo-02")); Method getMethod = Person.class.getMethod("getName"); System.out.println(getMethod.invoke(person));
|
调用私有方法
调用私有方法需要使用setAccessible()
方法关闭访问限制。
1 2 3 4 5 6 7
| Method setMethod = Person.class.getDeclaredMethod("setAge", int.class); Person person = new Person(1, "demo-01"); setMethod.setAccessible(true); System.out.println(setMethod.invoke(person, 2)); Method getMethod = Person.class.getDeclaredMethod("getAge"); getMethod.setAccessible(true); System.out.println(getMethod.invoke(person));
|
构造函数
我们可以通过调用构造函数的newInstance()
1 2 3 4 5 6 7 8 9 10
| Constructor<Person> constructor = Person.class.getDeclaredConstructor(); constructor.setAccessible(true); Person person = constructor.newInstance(); System.out.println(person); constructor = Person.class.getConstructor(int.class, String.class); person = constructor.newInstance(1, "demo-2"); System.out.println(person);
|
参考文章:http://tutorials.jenkov.com/java-reflection/index.html
参考文章: http://docs.oracle.com/javase/tutorial/reflect/index.html
TODO:
注解、泛型、数组