Java 反射
标签 : Java基础
动态语言
动态语言,是指程序在运行时可以改变其结构:新的函数可以被引进,已有的函数可以被删除等在结构上的变化。比如众所周知的ECMAScript(JavaScript)便是一个动态语言。除此之外如Ruby、Python等也都属于动态语言,而C、C++等语言则不属于动态语言。(引自: 百度百科)
var execString = "alert(Math.floor(Math.random()*10));";
eval(execString);
Class
反射机制
- 指的是可以于运行时加载,探知和使用编译期间完全未知的类.
- 程序在运行状态中, 可以动态加载一个只有名称的类, 对于任意一个已经加载的类,都能够知道这个类的所有属性和方法; 对于任意一个对象,都能调用他的任意一个方法和属性;
- 加载完类之后, 在堆内存中会产生一个
Class
类型的对象(一个类只有一个Class对象), 这个对象包含了完整的类的结构信息,而且这个Class
对象就像一面镜子,透过这个镜子看到类的结构,所以被称之为:反射
.
Instances of the class Class
represent classes and interfaces in a running Java application. An enum is a kind of class and an annotation is a kind of interface. Every array also belongs to a class that is reflected as a Class
object that is shared by all arrays with the same element type and number of dimensions(维度). The primitive Java types (boolean
, byte
, char
, short
, int
, long
, float
, and double
), and the keyword void
are also represented as Class
objects.
Class对象的获取
从Class中获取信息
Class
类提供了大量的实例方法来获取该Class对象所对应的详细信息,Class类大致包含如下方法,其中每个方法都包含多个重载版本,因此我们只是做简单的介绍,详细请参考JDK文档
获取内容 |
方法签名 |
构造器 |
Constructor<T> getConstructor(Class<?>... parameterTypes) |
包含的方法 |
Method getMethod(String name, Class<?>... parameterTypes) |
包含的属性 |
Field getField(String name) |
包含的Annotation
|
<A extends Annotation> A getAnnotation(Class<A> annotationClass) |
内部类 |
Class<?>[] getDeclaredClasses() |
外部类 |
Class<?> getDeclaringClass() |
所实现的接口 |
Class<?>[] getInterfaces() |
修饰符 |
int getModifiers() |
所在包 |
Package getPackage() |
类名 |
String getName() |
简称 |
String getSimpleName() |
判断内容 |
方法签名 |
注解类型? |
boolean isAnnotation() |
使用了该Annotation 修饰? |
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) |
匿名类? |
boolean isAnonymousClass() |
数组? |
boolean isArray() |
枚举? |
boolean isEnum() |
原始类型? |
boolean isPrimitive() |
接口? |
boolean isInterface() |
obj 是否是该Class 的实例 |
boolean isInstance(Object obj) |
- 使用反射生成并操作对象:
Method
Constructor
Field
这些类都实现了java.lang.reflect.Member
接口,程序可以通过Method
对象来执行相应的方法,通过Constructor
对象来调用对应的构造器创建实例,通过Filed
对象直接访问和修改对象的成员变量值.
创建对象
通过反射来生成对象的方式有两种:
- 使用
Class
对象的newInstance()
方法来创建该Class
对象对应类的实例(这种方式要求该Class对象的对应类有默认构造器).
- 先使用
Class
对象获取指定的Constructor
对象, 再调用Constructor对象的newInstance()
方法来创建该Class对象对应类的实例(通过这种方式可以选择指定的构造器来创建实例).
通过第一种方式来创建对象比较常见, 像Spring这种框架都需要根据配置文件(如applicationContext.xml
)信息来创建Java对象,从配置文件中读取的只是某个类的全限定名字符串,程序需要根据该字符串来创建对应的实例,就必须使用默认的构造器来反射对象.
下面我们就模拟Spring实现一个简单的对象池, 该对象池会根据文件读取key-value对, 然后创建这些对象, 并放入Map
中.
{
"objects": [
{
"id": "id1",
"class": "com.fq.domain.User"
},
{
"id": "id2",
"class": "com.fq.domain.Bean"
}
]
}
/**
* Created by jifang on 15/12/31.
*/
public class ObjectPool {
private Map<String, Object> pool;
private ObjectPool(Map<String, Object> pool) {
this.pool = pool;
}
private static Object getInstance(String className) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
return Class.forName(className).newInstance();
}
private static JSONArray getObjects(String config) throws IOException {
Reader reader = new InputStreamReader(ClassLoader.getSystemResourceAsStream(config));
return JSONObject.parseObject(CharStreams.toString(reader)).getJSONArray("objects");
}
public static ObjectPool init(String config) {
try {
JSONArray objects = getObjects(config);
ObjectPool pool = new ObjectPool(new HashMap<String, Object>());
if (objects != null && objects.size() != 0) {
for (int i = 0; i < objects.size(); ++i) {
JSONObject object = objects.getJSONObject(i);
if (object == null || object.size() == 0) {
continue;
}
String id = object.getString("id");
String className = object.getString("class");
pool.putObject(id, getInstance(className));
}
}
return pool;
} catch (IOException | ClassNotFoundException | InstantiationException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
public Object getObject(String id) {
return pool.get(id);
}
public void putObject(String id, Object object) {
pool.put(id, object);
}
public void clear() {
pool.clear();
}
}
public class Client {
@Test
public void client() {
ObjectPool pool = ObjectPool.init("config.json");
User user = (User) pool.getObject("id1");
System.out.println(user);
Bean bean = (Bean) pool.getObject("id2");
System.out.println(bean);
}
}
public class User {
private int id;
private String name;
private String password;
public int getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", password='" + password + '\'' +
'}';
}
}
public class Bean {
private Boolean usefull;
private BigDecimal rate;
private String name;
public Boolean getUsefull() {
return usefull;
}
public void setUsefull(Boolean usefull) {
this.usefull = usefull;
}
public BigDecimal getRate() {
return rate;
}
public void setRate(BigDecimal rate) {
this.rate = rate;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Bean{" +
"usefull=" + usefull +
", rate=" + rate +
", name='" + name + '\'' +
'}';
}
}
注意: 需要在pom.xml中添加如下依赖:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.7</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>18.0</version>
</dependency>
调用方法
当获取到某个类对应的Class对象之后, 就可以通过该Class对象的getMethod
来获取一个Method数组或Method对象.每个Method对象对应一个方法,在获得Method对象之后,就可以通过调用invoke方法来调用该Method对象对应的方法.
@CallerSensitive
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
{
...
}
下面我们对上面的对象池加强:可以看到Client
获取到的对象的成员变量全都是默认值,既然我们已经使用了JSON这么优秀的工具,我们又学习了动态调用对象的方法,那么我们就通过配置文件来给对象设置值(在对象创建时), 新的配置文件形式如下:
{
"objects": [
{
"id": "id1",
"class": "com.fq.domain.User",
"fields": [
{
"name": "id",
"value": 101
},
{
"name": "name",
"value": "feiqing"
},
{
"name": "password",
"value": "ICy5YqxZB1uWSwcVLSNLcA=="
}
]
},
{
"id": "id2",
"class": "com.fq.domain.Bean",
"fields": [
{
"name": "usefull",
"value": true
},
{
"name": "rate",
"value": 3.14
},
{
"name": "name",
"value": "bean-name"
}
]
},
{
"id": "id3",
"class": "com.fq.domain.ComplexBean",
"fields": [
{
"name": "name",
"value": "complex-bean-name"
},
{
"name": "refBean",
"ref": "id2"
}
]
}
]
}
其中fields
代表该Bean
所包含的属性, name
为属性名称, value
为属性值(属性类型为JSON支持的类型), ref
代表引用一个对象(也就是属性类型为Object
,但是一定要引用一个已经存在了的对象)
/**
* @author jifang
* @since 15/12/31下午4:00
*/
public class ObjectPool {
private Map<String, Object> pool;
private ObjectPool(Map<String, Object> pool) {
this.pool = pool;
}
private static JSONArray getObjects(String config) throws IOException {
Reader reader = new InputStreamReader(ClassLoader.getSystemResourceAsStream(config));
return JSONObject.parseObject(CharStreams.toString(reader)).getJSONArray("objects");
}
private static Object getInstance(String className, JSONArray fields)
throws ClassNotFoundException, NoSuchMethodException,
IllegalAccessException, InstantiationException, InvocationTargetException {
Class<?> clazz = Class.forName(className);
Object targetObject = clazz.newInstance();
if (fields != null && fields.size() != 0) {
for (int i = 0; i < fields.size(); ++i) {
JSONObject field = fields.getJSONObject(i);
String fieldName = field.getString("name");
Object fieldValue;
if (field.containsKey("value")) {
fieldValue = field.get("value");
} else if (field.containsKey("ref")) {
String refBeanId = field.getString("ref");
fieldValue = OBJECTPOOL.getObject(refBeanId);
} else {
throw new RuntimeException("neither value nor ref");
}
String setterName = "set" +
fieldName.substring(0, 1).toUpperCase() +
fieldName.substring(1);
Method setterMethod = clazz.getMethod(setterName, fieldValue.getClass());
setterMethod.invoke(targetObject, fieldValue);
}
}
return targetObject;
}
private static ObjectPool OBJECTPOOL;
private static void initSingletonPool() {
if (OBJECTPOOL == null) {
synchronized (ObjectPool.class) {
if (OBJECTPOOL == null) {
OBJECTPOOL = new ObjectPool(new ConcurrentHashMap<String, Object>());
}
}
}
}
public static ObjectPool init(String config) {
initSingletonPool();
try {
JSONArray objects = getObjects(config);
for (int i = 0; objects != null && i < objects.size(); ++i) {
JSONObject object = objects.getJSONObject(i);
if (object == null || object.size() == 0) {
continue;
}
String id = object.getString("id");
String className = object.getString("class");
OBJECTPOOL.putObject(id, getInstance(className, object.getJSONArray("fields")));
}
return OBJECTPOOL;
} catch (IOException | ClassNotFoundException |
InstantiationException | IllegalAccessException |
NoSuchMethodException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
public Object getObject(String id) {
return pool.get(id);
}
public void putObject(String id, Object object) {
pool.put(id, object);
}
public void clear() {
pool.clear();
}
}
public class Client {
@Test
public void client() {
ObjectPool pool = ObjectPool.init("config.json");
User user = (User) pool.getObject("id1");
System.out.println(user);
Bean bean = (Bean) pool.getObject("id2");
System.out.println(bean);
ComplexBean complexBean = (ComplexBean) pool.getObject("id3");
System.out.println(complexBean);
}
}
public class ComplexBean {
private String name;
private Bean refBean;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Bean getRefBean() {
return refBean;
}
public void setRefBean(Bean refBean) {
this.refBean = refBean;
}
@Override
public String toString() {
return "ComplexBean{" +
"name='" + name + '\'' +
", refBean=" + refBean +
'}';
}
}
Spring框架就是通过这种方式将成员变量值以及依赖对象等都放在配置文件中进行管理的,从而实现了较好地解耦(不过Spring是通过XML作为配置文件).
访问成员变量
通过Class
对象的的getField()
方法可以获取该类所包含的全部或指定的成员变量Field
,Filed
提供了如下两组方法来读取和设置成员变量值.
-
getXxx(Object obj)
: 获取obj对象的该成员变量的值, 此处的Xxx对应8中基本类型,如果该成员变量的类型是引用类型, 则取消get后面的Xxx;
-
setXxx(Object obj, Xxx val)
: 将obj对象的该成员变量值设置成val值.此处的Xxx对应8种基本类型, 如果该成员类型是引用类型, 则取消set后面的Xxx;
注: getDeclaredXxx方法可以获取所有的成员变量,无论private/public;
/**
* @author jifang
* @since 16/1/2下午1:00.
*/
public class Client {
@Test
public void client() throws NoSuchFieldException, IllegalAccessException {
User user = new User();
Field idFiled = User.class.getDeclaredField("id");
setAccessible(idFiled);
idFiled.setInt(user, 46);
Field nameFiled = User.class.getDeclaredField("name");
setAccessible(nameFiled);
nameFiled.set(user, "feiqing");
Field passwordField = User.class.getDeclaredField("password");
setAccessible(passwordField);
passwordField.set(user, "ICy5YqxZB1uWSwcVLSNLcA==");
System.out.println(user);
}
private void setAccessible(AccessibleObject object) {
object.setAccessible(true);
}
}
使用反射获取泛型信息
为了通过反射操作泛型以迎合实际开发的需要, Java新增了java.lang.reflect.ParameterizedType
java.lang.reflect.GenericArrayType
java.lang.reflect.TypeVariable
java.lang.reflect.WildcardType
几种类型来代表不能归一到Class类型但是又和原始类型同样重要的类型.
类型 |
含义 |
ParameterizedType |
一种参数化类型, 比如Collection<String>
|
GenericArrayType |
一种元素类型是参数化类型或者类型变量的数组类型 |
TypeVariable |
各种类型变量的公共接口 |
WildcardType |
一种通配符类型表达式, 如? ? extends Number ? super Integer
|
其中, 我们可以使用ParameterizedType
来获取泛型信息.
public class Client {
private Map<String, Object> objectMap;
public void test(Map<String, User> map, String string) {
}
public Map<User, Bean> test() {
return null;
}
/**
* 测试属性类型
*
* @throws NoSuchFieldException
*/
@Test
public void testFieldType() throws NoSuchFieldException {
Field field = Client.class.getDeclaredField("objectMap");
Type gType = field.getGenericType();
System.out.println(field.getType());
System.out.println(gType);
System.out.println("**************");
if (gType instanceof ParameterizedType) {
ParameterizedType pType = (ParameterizedType) gType;
Type[] types = pType.getActualTypeArguments();
for (Type type : types) {
System.out.println(type.toString());
}
}
}
/**
* 测试参数类型
*
* @throws NoSuchMethodException
*/
@Test
public void testParamType() throws NoSuchMethodException {
Method testMethod = Client.class.getMethod("test", Map.class, String.class);
Type[] parameterTypes = testMethod.getGenericParameterTypes();
for (Type type : parameterTypes) {
System.out.println("type -> " + type);
if (type instanceof ParameterizedType) {
Type[] actualTypes = ((ParameterizedType) type).getActualTypeArguments();
for (Type actualType : actualTypes) {
System.out.println("\tactual type -> " + actualType);
}
}
}
}
/**
* 测试返回值类型
*
* @throws NoSuchMethodException
*/
@Test
public void testReturnType() throws NoSuchMethodException {
Method testMethod = Client.class.getMethod("test");
Type returnType = testMethod.getGenericReturnType();
System.out.println("return type -> " + returnType);
if (returnType instanceof ParameterizedType) {
Type[] actualTypes = ((ParameterizedType) returnType).getActualTypeArguments();
for (Type actualType : actualTypes) {
System.out.println("\tactual type -> " + actualType);
}
}
}
}
使用反射获取注解
使用反射获取注解信息的相关介绍, 请参看我的博客Java注解实践
反射性能测试
Method/Constructor/Field/Element
都继承了AccessibleObject
,AccessibleObject
类中有一个setAccessible
方法:
public void setAccessible(boolean flag) throws SecurityException {
...
}
该方法有两个作用:
1. 启用/禁用访问安全检查开关:值为true,则指示反射的对象在使用时取消Java语言访问检查;值为false,则指示应该实施Java语言的访问检查;
2. 可以禁止安全检查, 提高反射的运行效率.
/**
* @author jifang
* @since 15/12/31下午4:53.
*/
public class TestReflect {
@Before
public void testNoneReflect() {
User user = new User();
long start = System.currentTimeMillis();
for (long i = 0; i < Integer.MAX_VALUE; ++i) {
user.getName();
}
long count = System.currentTimeMillis() - start;
System.out.println("没有反射, 共消耗 <" + count + "> 毫秒");
}
@Test
public void testNotAccess() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
User user = new User();
Method method = Class.forName("com.fq.domain.User").getMethod("getName");
long start = System.currentTimeMillis();
for (long i = 0; i < Integer.MAX_VALUE; ++i) {
method.invoke(user, null);
}
long count = System.currentTimeMillis() - start;
System.out.println("没有访问权限, 共消耗 <" + count + "> 毫秒");
}
@After
public void testUseAccess() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
User user = new User();
Method method = Class.forName("com.fq.domain.User").getMethod("getName");
method.setAccessible(true);
long start = System.currentTimeMillis();
for (long i = 0; i < Integer.MAX_VALUE; ++i) {
method.invoke(user, null);
}
long count = System.currentTimeMillis() - start;
System.out.println("有访问权限, 共消耗 <" + count + "> 毫秒");
}
}
执行上面程序,在我的机器上可以看到使用反射会比直接调用慢3000毫秒
,但是前提是该方法会执行20E+次(而且服务器的性能也肯定比我的机器要高),因此在我们的实际开发中,其实是不用担心反射机制带来的性能消耗的,而且禁用访问权限检查,也会有性能的提升.
附-机器配置信息

- 扩展阅读:
- 为什么动态类型编程语言会如此流行?
- 动态语言和静态语言的比较
- 反射是否真的会让你的程序性能降低?