一、动态语言
动态语言,是指程序在运行时可以改变其结构:新的函数可以被引进,已有的函数可以被删除等在结构上的变化。比如众所周知的ECMAScript(JavaScript)便是一个动态语言。除此之外如Ruby、Python等也都属于动态语言,而C、C++等语言则不属于动态语言。(引自: 百度百科)
二、Class
反射机制
(1)指的是可以于运行时加载,探知和使用编译期间完全未知的类.
(2)程序在运行状态中, 可以动态加载一个只有名称的类, 对于任意一个已经加载的类,都能够知道这个类的所有属性和方法; 对于任意一个对象,都能调用他的任意一个方法和属性;(3)加载完类之后, 在堆内存中会产生一个Class
类型的对象(一个类只有一个Class对象), 这个对象包含了完整的类的结构信息,而且这个Class
对象就像一面镜子,透过这个镜子看到类的结构,所以被称之为:反射。
(4)每个类被加载进入内存之后,系统就会为该类生成一个对应的java.lang.Class
对象,通过该Class
对象就可以访问到JVM中的这个类。
三、Class对象的获取
(1)对象的getClass()
方法;
(2)类的.class
最安全/性能最好)属性;
(3)运用Class.forName(String className)
动态加载类,className
需要是类的全限定名(最常用).
四、从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
中.
- 配置文件
1
2
3
4
5
6
7
8
9
10
11
12
|
{
"objects": [
{
"id": "id1",
"class": "com.fq.domain.User"
},
{
"id": "id2",
"class": "com.fq.domain.Bean"
}
]
}
|
- ObjectPool
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
|
/**
* 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"
);
}
// 根据指定的JSON配置文件来初始化对象池
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();
}
}
|
- Client
1
2
3
4
5
6
7
8
9
10
11
12
|
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);
}
}
|
- User
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
|
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 + '\
''
+
'}'
;
}
}
|
- Bean
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
|
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中添加如下依赖:
1
2
3
4
5
6
7
8
9
10
11
|
<
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对象对应的方法.
1
2
3
4
5
6
7
|
@CallerSensitive
public
Object invoke(Object obj, Object... args)
throws
IllegalAccessException, IllegalArgumentException,
InvocationTargetException
{
...
}
|
下面我们对上面的对象池加强:可以看到Client
获取到的对象的成员变量全都是默认值,既然我们已经使用了JSON这么优秀的工具,我们又学习了动态调用对象的方法,那么我们就通过配置文件来给对象设置值(在对象创建时), 新的配置文件形式如下:
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
|
{
"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
,但是一定要引用一个已经存在了的对象)
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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
|
/**
* @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
Class<?> clazz = Class.forName(className);
// 目标Class的实例对象
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
);
// 需要设置的成员变量的setter方法
Method setterMethod = clazz.getMethod(setterName, fieldValue.getClass());
// 调用setter方法将值设置进去
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>());
}
}
}
}
// 根据指定的JSON配置文件来初始化对象池
public
static
ObjectPool init(String config) {
// 初始化pool
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"
);
// 初始化bean并放入池中
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();
}
}
|
- Client
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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);
}
}
|
- ComplexBean
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
|
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;
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
|
/**
* @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
来获取泛型信息.
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
64
65
66
67
68
69
70
71
72
|
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();
// 打印type与generic type的区别
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
方法:
1
2
3
|
public
void
setAccessible(
boolean
flag)
throws
SecurityException {
...
}
|
该方法有两个作用:
1. 启用/禁用访问安全检查开关:值为true,则指示反射的对象在使用时取消Java语言访问检查;值为false,则指示应该实施Java语言的访问检查;
2. 可以禁止安全检查, 提高反射的运行效率.
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
|
/**
* @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+次(而且服务器的性能也肯定比我的机器要高),因此在我们的实际开发中,其实是不用担心反射机制带来的性能消耗的,而且禁用访问权限检查,也会有性能的提升.