您的位置:首页 > 编程语言 > Java开发

干货:Java反射机制应用实践

2018-03-05 00:00 344 查看

反射基础

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 tocall 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
"nodescription"
;

}

下面是我们检测密码的工具类的实现:

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(Individualarg) {

// 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 Pettocreate:

public
abstract
List<Class<?
extends
Pet>> getTypes();

public
PetrandomPet() {

// 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();

}

}

喜欢的朋友可以点赞关注,一起学习进步
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  JAVA 程序员 后端