0%

Java获得范型类型Class

Java很多框架或库中都会提供具有范型的回调接口或抽象类。当我们在使用时,代码类似于:

1
2
3
4
5
6
7
8
9
10
11
MyClient.getInstance().handleEvent(new MyCallback<SomeEntity>() {
@Override
public void onSuccess(SomeEntity entity) {
System.out.println("Entity is: " + s);
}

@Override
public void onFailure(String errorMsg) {
System.out.println(errorMsg);
}
});

在匿名的回调类中,传入了范型类型,回调的接口中直接给出了相应类型的入参实例。
那么这样的功能是如何实现的呢?
实际是java的范型机制中,并没有把所有的范型信息擦除,通过一些反射相关的API,可以将父类的范型参数获取到。

首先假定我们的Callback是抽象类方式提供的:

1
2
3
4
5
public abstract class MyAbstractCallback<ENTITY> {

abstract void onSuccess(ENTITY entity);
abstract void onFailure(String errorMsg);
}

在这里引入了范型,当我们实现这个抽象类,就是本文最开始的形式。虽然我们的onSuccess入参类型已经是相应类型实例了,
但我们的“库”在回调onSuccess函数时,并没有相应的实例。因此我们需要获取到“ENTITY”对应类的class对象,并使用反射的方法生成实例,回调给上层。
那么handleEvent的内部应该是这样的:

1
2
3
4
5
public <ENTITY> void handleEvent(MyCallback<ENTITY> callback) {
Object obj = Class.forName(ENTITY.class.getName()).newInstance();
ENTITY result = (ENTITY) obj;
callback.onSuccess(result);
}

很显然这样的写法是无法编译通过的,因为我们无法通过范型类型ENTITY直接获取到它的class,在编译期java已经将范型擦除。
但由于callback对象必然是抽象类的一个实现子类,因此我们虽然不能直接获取到callback实现类中的范型信息,但可以获取到父类中的范型参数来达到同样的效果,这个信息是不会被擦除的。
java的反射中提供了getGenericSuperclass()方法可以实现这个需求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public <ENTITY> void handleEvent(MyAbstractCallback<ENTITY> callback) {
// 单继承,父类只有一个
Type superclass = callback.getClass().getGenericSuperclass();

// 如果实现类没有范型信息,直接返回
if (!(superclass instanceof ParameterizedTypeImpl)) {
return;
}

try {
// 找到声明的具体类型
ParameterizedType parameterized = (ParameterizedType) superclass;
Type actualType = parameterized.getActualTypeArguments()[0];

Object obj = Class.forName(actualType.getTypeName()).newInstance();
ENTITY result = (ENTITY) obj;
onSuccessB(callback, result);
} catch (Exception e) {
e.printStackTrace();
onFailureB(callback, e.getLocalizedMessage());
}
}

整个流程可以归纳为:
1.通过getGenericSuperclass()方法获取到带有范型参数的父类信息,为Type的实例。
2.如果这个实例包含范型参数,那么获取到的是ParameterizedTypeImpl实例。
3.将这个Type类型实例向下强转为ParameterizedType类型,通过getActualTypeArguments方法获取到实际的类型信息(这个方法返回的是数组,因为我们定义的抽象类仅有一个范型参数,因此数组第一个元素就是我们实现时传入的类型)。
4.获取到类型后就可以通过反射方法生成实例,调用回调方法返回。

对于接口类型的回调也同样适用,与获取父类范型参数类似,java提供了getGenericInterfaces方法,返回所有实现的接口。
如果接口是带有范型参数的,那么它同样是ParameterizedTypeImpl的实例。代码逻辑稍有增加,主要是因为一个类可以实现多个接口,需要在getGenericInterfaces返回的数组里先找到相应的接口类型。

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
public <ENTITY> void handleEvent(MyCallback<ENTITY> callback) {
Type[] interfaces = callback.getClass().getGenericInterfaces();
Type target = null;

// 实际的对象可能实现了多个接口,需要从其中找到MyCallback
for (int i = 0; i < interfaces.length; i++) {
// 范型接口在数组中是ParameterizedTypeImpl类型
if (interfaces[i] instanceof ParameterizedTypeImpl) {
ParameterizedTypeImpl impl = (ParameterizedTypeImpl) interfaces[i];

if (impl.getRawType().getName().equals(MyCallback.class.getName())) {
// target就是实现了MyCallback的范型接口
target = interfaces[i];
}
}
}

if (target != null) {
try {
// 找到声明的具体类型
ParameterizedType parameterized = (ParameterizedType) target;
Type actualType = parameterized.getActualTypeArguments()[0];

Object obj = Class.forName(actualType.getTypeName()).newInstance();
ENTITY result = (ENTITY) obj;
onSuccess(callback, result);
} catch (Exception e) {
e.printStackTrace();
onFailure(callback, e.getLocalizedMessage());
}

}
}

找到接口类型后适用同样的方法拿到具体类型并反射生成实例。
将这两个方法封装到一个类中:

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
public class MyClient {

private static MyClient singleInstance = new MyClient();

public static MyClient getInstance() {
return singleInstance;
}

/**
* 处理接口类型的回调函数
* @param callback
* @param <ENTITY>
*/
public <ENTITY> void handleEvent(MyCallback<ENTITY> callback) {
Type[] interfaces = callback.getClass().getGenericInterfaces();
Type target = null;

// 实际的对象可能实现了多个接口,需要从其中找到MyCallback
for (int i = 0; i < interfaces.length; i++) {
// 范型接口在数组中是ParameterizedTypeImpl类型
if (interfaces[i] instanceof ParameterizedTypeImpl) {
ParameterizedTypeImpl impl = (ParameterizedTypeImpl) interfaces[i];

if (impl.getRawType().getName().equals(MyCallback.class.getName())) {
// target就是实现了MyCallback的范型接口
target = interfaces[i];
}
}
}

if (target != null) {
try {
// 找到声明的具体类型
ParameterizedType parameterized = (ParameterizedType) target;
Type actualType = parameterized.getActualTypeArguments()[0];

Object obj = Class.forName(actualType.getTypeName()).newInstance();
ENTITY result = (ENTITY) obj;
onSuccess(callback, result);
} catch (Exception e) {
e.printStackTrace();
onFailure(callback, e.getLocalizedMessage());
}

}
}

private <ENTITY> void onSuccess(MyCallback<ENTITY> callback, ENTITY entity) {
System.out.println("log success");
callback.onSuccess(entity);
}

private <ENTITY> void onFailure(MyCallback<ENTITY> callback, String msg) {
System.out.println("log failure");
callback.onFailure(msg);
}

// --------------------------------------------

/**
* 处理类形式的回调函数
* @param callback
* @param <ENTITY>
*/
public <ENTITY> void handleEvent(MyAbstractCallback<ENTITY> callback) {
// 单继承,父类只有一个
Type superclass = callback.getClass().getGenericSuperclass();

// 如果实现类没有范型信息,直接返回
if (!(superclass instanceof ParameterizedTypeImpl)) {
return;
}

try {
// 找到声明的具体类型
ParameterizedType parameterized = (ParameterizedType) superclass;
Type actualType = parameterized.getActualTypeArguments()[0];

Object obj = Class.forName(actualType.getTypeName()).newInstance();
ENTITY result = (ENTITY) obj;
onSuccessB(callback, result);
} catch (Exception e) {
e.printStackTrace();
onFailureB(callback, e.getLocalizedMessage());
}
}

private <ENTITY> void onSuccessB(MyAbstractCallback<ENTITY> callback, ENTITY entity) {
System.out.println("log success");
callback.onSuccess(entity);
}

private <ENTITY> void onFailureB(MyAbstractCallback<ENTITY> callback, String msg) {
System.out.println("log failure");
callback.onFailure(msg);
}
}

接下来可以写一些测试代码,可以通过修改构造函数参数数量,触发反射生成实例报错走onFailure的逻辑。这里由于反射仅生成了实例没有设置参数,所有的打印都是空值:

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
public class TestMain {

public String name;

// 打开用来测试newInstance报错走onFailure
/*public TestMain(String name) {
this.name = name;
}*/

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public void printTestMainName() {
System.out.println("Name of TestMain is: " + this.name);
}

public static void main(String[] args) {

MyClient.getInstance().handleEvent(new MyTestCallback());

MyClient.getInstance().handleEvent(new MyCallback<String>() {
@Override
public void onSuccess(String s) {
System.out.println("String is: " + s);
}

@Override
public void onFailure(String errorMsg) {
System.out.println(errorMsg);
}
});

MyClient.getInstance().handleEvent(new AnotherCallback());

MyClient.getInstance().handleEvent(new MyAbstractCallback<String>() {
@Override
void onSuccess(String s) {
System.out.println("String is: " + s);
}

@Override
void onFailure(String errorMsg) {
System.out.println(errorMsg);
}
});

}
}

前两个方法是接口实现类的调用,后两个方法是抽象类的实现类调用。同时第一个测试方法传入的是事先定义好的实现类,第二次传入了匿名实现类。

接口与抽象类的定义:

1
2
3
4
5
6
7
8
9
10
11
public interface MyCallback<ENTITY> {

void onSuccess(ENTITY entity);
void onFailure(String errorMsg);
}

public abstract class MyAbstractCallback<ENTITY> {

abstract void onSuccess(ENTITY entity);
abstract void onFailure(String errorMsg);
}

接口实现类与抽象类的实现类(这里主要测试了实现多个接口时的处理逻辑):

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
public class MyTestCallback implements MyCallback<TestMain>, FooInterface {
@Override
public void foo() {

}

@Override
public void onSuccess(TestMain o) {
o.printTestMainName();
}

@Override
public void onFailure(String errorMsg) {
System.out.println(errorMsg);
}
}

public class AnotherCallback extends MyAbstractCallback<TestMain> {

@Override
void onSuccess(TestMain testMain) {
testMain.printTestMainName();
}

@Override
void onFailure(String errorMsg) {
System.out.println(errorMsg);
}
}