反射基础
p.s: 本文需要读者对反射机制的API有一定程度的了解,如果之前没有接触过的话,建议先看一下官方文档的Quick Start。
在应用反射机制之前,首先我们先来看一下如何获取一个对象对应的反射类Class
,在Java中我们有三种方法可以获取一个对象的反射类。
通过getClass方法
在Java中,每一个Object
都有一个getClass()
方法,通过getClass方法我们可以获取到这个对象对应的反射类:
1
2
|
String s =
"ziwenxie"
;
Class<?> c = s.getClass();
|
通过forName方法
我们也可以调用Class
类的静态方法forName()
:
1
|
Class<?> c = Class.forName(
"java.lang.String"
);
|
使用.class
或者我们也可以直接使用.class
:
1
|
Class<?> c = String.
class
;
|
获取类型信息
在文章开头我们就提到反射的一大好处就是可以允许我们在运行期间获取对象的类型信息,下面我们通过一个例子来具体看一下。
首先我们在typeinfo.interfacea
包下面新建一个接口A
:
1
2
|
package
typeinfo.interfacea;
public
interface
A {
void
f(); }
|
接着我们在typeinfo.packageaccess
包下面新建一个接口C
,接口C
继承自接口A
,并且我们还另外创建了几个用于测试的方法,注意下面几个方法的权限都是不同的。
1
2
3
4
5
6
7
8
9
10
11
12
|
package
typeinfo.packageaccess;
import
typeinfo.interfacea.A;
class
C
implements
A {
public
void
f() { System.out.println(
"public C.f()"
); }
public
void
g() { System.out.println(
"public C.g()"
); }
protected
void
v () { System.out.println(
"protected C.v()"
); }
void
u() { System.out.println(
"package C.u()"
); }
private
void
w() { System.out.println(
"private C.w()"
); }
}
public
class
HiddenC {
public
static
A makeA() {
return
new
C(); }
}
|
在callHiddenMethod()
方法中我们用到了几个新的API,其中getDeclaredMethod()
根据方法名用于获取Class类指代对象自己声明的某个方法,然后我们通过调用invoke()
方法就可以触发对象的相关方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
package
typeinfo;
import
typeinfo.interfacea.A;
import
typeinfo.packageaccess.HiddenC;
import
java.lang.reflect.Method;
public
class
HiddenImplementation {
public
static
void
main(String[] args)
throws
Exception {
A a = HiddenC.makeA();
a.f();
System.out.println(a.getClass().getName());
// Oops! Reflection still allows us to call g():
callHiddenMethod(a,
"g"
);
// And even methods that are less accessible!
callHiddenMethod(a,
"u"
);
callHiddenMethod(a,
"v"
);
callHiddenMethod(a,
"w"
);
}
static
void
callHiddenMethod(Object a, String methodName)
throws
Exception {
Method g = a.getClass().getDeclaredMethod(methodName);
g.setAccessible(
true
);
g.invoke(a);
}
}
|
从输出结果我们可以看出来,不管是public
,default
,protect
还是pricate
方法,通过反射类我们都可以自由调用。当然这里我们只是为了显示反射的强大威力,在实际开发中这种技巧还是不提倡。
1
2
3
4
5
6
|
public
C.f()
typeinfo.packageaccess.C
public
C.g()
package
C.u()
protected
C.v()
private
C.w()
|
上面我们只是测试了Method
对象,感兴趣的读者在熟悉了反射的API之后,不妨测试一下Filed
,这里我们就不重复了。
与注解相结合
在单元测试框架比如Junit
中反射机制也得到了广泛的应用,即通过注解的方式。下面我们简单地来了解一下如何通过反射机制来获取相关方法的注解信息,比如说我们有下面这样一个业务场景,当用户在修改自己密码的时候,为了保证密码的安全性,我们要求用户的新密码要满足一些条件,比如说至少要包含一个非数字字符,不能与以前的密码相同之类的条件等。
1
2
3
4
5
6
7
|
import
java.lang.annotation.*
@Target
(ElementType.METHOD)
@Retention
(RetentionPolicy.RUNTIME)
public
@interface
UserCase {
public
int
id();
public
String description()
default
"no description"
;
}
|
下面是我们检测密码的工具类的实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public
class
PasswordUtils {
@UserCase
(id=
47
, description=
"Password must contain at least one numeric"
)
public
boolean
validatePassword(String password) {
return
(password.matches(
"\\w*\\d\\w*"
));
}
@UserCase
(id=
48
)
public
String encryptPassword(String password) {
return
new
StringBuilder(password).reverse().toString();
}
@UserCase
(id=
49
, description=
"New passwords can't equal previously used ones"
)
public
boolean
checkForNewPassword(List<String> prevPasswords, String password) {
return
!prevPasswords.contains(password);
}
}
|
利用反射我们可以写出更加清晰的测试代码,其中getDeclaredMethods()
方法可以获取相关对象自己声明的相关方法,而getAnnotation()
则可以获取Method
对象的指定注解。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public
class
UseCaseTracker {
public
static
void
trackUseCases(List<Integer> useCases, Class<?> cl) {
for
(Method m : cl.getDeclaredMethods()) {
UseCase uc = m.getAnnotation(UseCase.
class
);
if
(uc !=
null
) {
System.out.println(
"Found Use Case: "
+ uc.id() +
" "
+ uc.description());
useCases.remove(
new
Integer(uc.id()));
}
}
for
(
int
i : useCases) {
System.out.println(
"Warning: Missing use case-"
+ i);
}
}
public
static
void
main(String[] args) {
List<Integer> useCases =
new
ArrayList<Integer>();
Collections.addAll(useCases,
47
,
48
,
49
,
50
);
trackUseCases(userCases, PasswordUtils.
class
);
}
}
|
解决泛型擦除
现在有下面这样一个业务场景,我们有一个泛型集合类List<Class<? extends Pet>>
,我们需要统计出这个集合类中每种具体的Pet
有多少个。由于Java的泛型擦除,注意类似List<? extends Pet>
的做法肯定是不行的,因为编译器做了静态类型检查之后,到了运行期间JVM会将集合中的对象都视为Pet
,但是并不会知道Pet
代表的究竟是Cat
还是Dog
,所以到了运行期间对象的类型信息其实全部丢失了。p.s: 关于泛型擦除:我在上一篇文章里面有详细解释,感兴趣的朋友可以看一看。
为了实现我们上面的例子,我们先来定义几个类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public
class
Pet
extends
Individual {
public
Pet(String name) {
super
(name); }
public
Pet() {
super
(); }
}
public
class
Cat
extends
Pet {
public
Cat(String name) {
super
(name); }
public
Cat() {
super
(); }
}
public
class
Dog
extends
Pet {
public
Dog(String name) {
super
(name); }
public
Dog() {
super
(); }
}
public
class
EgyptianMau
extends
Cat {
public
EgyptianMau(String name) {
super
(name); }
public
EgyptianMau() {
super
(); }
}
public
class
Mutt
extends
Dog {
public
Mutt(String name) {
super
(name); }
public
Mutt() {
super
(); }
}
|
上面的Pet
类继承自Individual
,Individual
类的的实现稍微复杂一点,我们实现了Comparable
接口,重新自定义了类的比较规则,如果不是很明白的话,也没有关系,我们已经将它抽象出来了,所以不理解实现原理也没有关系。
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
Individual
implements
Comparable<Individual> {
private
static
long
counter =
0
;
private
final
long
id = counter++;
private
String name;
// name is optional
public
Individual(String name) {
this
.name = name; }
public
Individual() {}
public
String toString() {
return
getClass().getSimpleName() + (name ==
null
?
""
:
" "
+ name);
}
public
long
id() {
return
id; }
public
boolean
equals(Object o) {
return
o
instanceof
Individual && id == ((Individual)o).id;
}
public
int
hashCode() {
int
result =
17
;
if
(name !=
null
) {
result =
37
* result + name.hashCode();
}
result =
37
* result + (
int
) id;
return
result;
}
public
int
compareTo(Individual arg) {
// Compare by class name first:
String first = getClass().getSimpleName();
String argFirst = arg.getClass().getSimpleName();
int
firstCompare = first.compareTo(argFirst);
if
(firstCompare !=
0
) {
return
firstCompare;
}
if
(name !=
null
&& arg.name !=
null
) {
int
secendCompare = name.compareTo(arg.name);
if
(secendCompare !=
0
) {
return
secendCompare;
}
}
return
(arg.id < id ? -
1
: (arg.id == id ?
0
:
1
));
}
}
|
下面创建了一个抽象类PetCreator
,以后我们通过调用arrayList()
方法便可以直接获取相关Pet
类的集合。这里使用到了我们上面没有提及的newInstance()
方法,它会返回Class类所真正指代的类的实例,这是什么意思呢?比如说声明new Dog().getClass().newInstance()
和直接new Dog()
是等价的。
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
|
public
abstract
class
PetCreator {
private
Random rand =
new
Random(
47
);
// The List of the different getTypes of Pet to create:
public
abstract
List<Class<?
extends
Pet>> getTypes();
public
Pet randomPet() {
// Create one random Pet
int
n = rand.nextInt(getTypes().size());
try
{
return
getTypes().get(n).newInstance();
}
catch
(InstantiationException e) {
throw
new
RuntimeException(e);
}
catch
(IllegalAccessException e) {
throw
new
RuntimeException(e);
}
}
public
Pet[] createArray(
int
size) {
Pet[] result =
new
Pet[size];
for
(
int
i =
0
; i < size; i++) {
result[i] = randomPet();
}
return
result;
}
public
ArrayList<Pet> arrayList(
int
size) {
ArrayList<Pet> result =
new
ArrayList<Pet>();
Collections.addAll(result, createArray(size));
return
result;
}
}
|
接下来我们来实现上面这一个抽象类,解释一下下面的代码,在下面的代码中,我们声明了两个集合类,allTypes
和types
,其中allTypes
中包含了我们呢上面所声明的所有类,但是我们具体的类型实际上只有两种即Mutt
和EgypianMau
,所以我们真正需要new
出来的宠物只是types
中所包含的类型,以后我们通过调用getTypes()
便可以得到types
中所包含的所有类型。
1
2
3
4
5
6
7
8
9
10
|
public
class
LiteralPetCreator
extends
PetCreator {
@SuppressWarnings
(
"unchecked"
)
public
static
final
List<Class<?
extends
Pet>> allTypes = Collections.unmodifiableList(
Arrays.asList(Pet.
class
, Dog.
class
, Cat.
class
, Mutt.
class
, EgyptianMau.
class
));
private
static
final
List<Class<?
extends
Pet>> types = allTypes.subList(
allTypes.indexOf(Mutt.
class
), allTypes.size());
public
List<Class<?
extends
Pet>> getTypes() {
return
types;
}
}
|
总体的逻辑已经完成了,最后我们实现用来统计集合中相关Pet
类个数的TypeCounter
类。解释一下isAssignalbeFrom()
方法,它可以判断一个反射类是某个反射类的子类或者间接子类。而getSuperclass()
顾名思义就是得到某个反射类的父类了。
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
|
public
class
TypeCounter
extends
HashMap<Class<?>, Integer> {
private
Class<?> baseType;
public
TypeCounter(Class<?> baseType) {
this
.baseType = baseType;
}
public
void
count(Object obj) {
Class<?> type = obj.getClass();
if
(!baseType.isAssignableFrom(type)) {
throw
new
RuntimeException(
obj +
" incorrect type "
+ type +
", should be type or subtype of "
+ baseType);
}
countClass(type);
}
private
void
countClass(Class<?> type) {
Integer quantity = get(type);
put(type, quantity ==
null
?
1
: quantity +
1
);
Class<?> superClass = type.getSuperclass();
if
(superClass !=
null
&& baseType.isAssignableFrom(superClass)) {
countClass(superClass);
}
}
@Override
public
String toString() {
StringBuilder result =
new
StringBuilder(
"{"
);
for
(Map.Entry<Class<?>, Integer> pair : entrySet()) {
result.append(pair.getKey().getSimpleName());
result.append(
"="
);
result.append(pair.getValue());
result.append(
", "
);
}
result.delete(result.length() -
2
, result.length());
result.append(
"} "
);
return
result.toString();
}
}
|