1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > 深入探索Java泛型

深入探索Java泛型

时间:2019-05-28 00:43:52

相关推荐

深入探索Java泛型

聂振宇,加入去哪儿网技术团队,目前在平台事业部/基础架构部,对并发编程,构建高并发系统很感兴趣。

引子

在我们平常写 Java 代码的过程中,总会或多或少的使用到 Java 泛型。但 Java 泛型有着各种各样的限制,运行时还会进行类型擦除,在需要重度使用泛型编程的场合,那酸爽,真是一言难尽。 作为曾经在 C++ 模版元编程中翱翔(挣扎)过的人,曾经多次痛苦发出“这也不行?”,“这样都不行?”,“到底还能干啥?”的感慨。不过工作还是要继续,虽然 Java 泛型不够完美,我们还是要尽量寻找到它能够发光发热的地方。 这篇文章将分享一些在 Java 泛型上探索(折腾)的经验,希望能对大家使用 Java 泛型起到一些帮助,有什么问题也欢迎指正。

场景

考虑这样一个简化的场景,应用会不断接收到 byte 数组,每个数组第一个 byte 表示数据类型,剩下的部分是 json 序列化后的数据,我们需要根据数据类型的不同来做一些对应的处理。

解决方案

初始方案

通过对场景的一顿分析,我们写出了如下的代码(考虑到版面问题,省去了异常处理和一些显而易见的代码):

@Service

public class ProcessorService {

@Resource

private List<CodeProcessor> codeProcessors;

private Map<Integer, CodeProcessor> processorMapping;

@PostConstruct

public void init() {

this.processorMapping = map(codeProcessors);

}

public void process(byte[] bytes) throws Exception {

int code = bytes[0];

ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes, 1, bytes.length - 1);

processorMapping.get(code).process(inputStream);

}

}

public interface CodeProcessor {

int code();

void process(InputStream inputStream) throws Exception;

}

public abstract class AbstractCodeProcessor<T> {

private static final ObjectMapper MAPPER = new ObjectMapper();

private final int code;

private final TypeReference<T> typeReference;

protected AbstractCodeProcessor(int code, TypeReference<T> typeReference) {

this.code = code;

this.typeReference = typeReference;

}

@Override

public int code() {

return code;

}

@Override

public final void process(InputStream inputStream) throws Exception {

T data = MAPPER.readValue(inputStream, typeReference);

doProcess(data);

}

protected abstract void doProcess(T data);

}

@Service

public class ListStringProcessor extends AbstractCodeProcessor<List<String>> {

private static final int LIST_STRING_CODE = 0;

public ListStringProcessor() {

super(0, new TypeReference<List<String>>() {});

}

@Override

protected void doProcess(List<String> data) {

// do something

}

}

乍一眼看上去还行,每次如果有新的解析类型需要支持,不需要修改任何原有代码,只用照着 ListStringProcessor 的写法添加一个继承自 AbstractCodeProcessor 的解析类,比如 class PersonProcessor extends AbstractCode Processor<Person>,系统就自动能支持新的 Person 类型,似乎到这里为止也还不错。 但是,ListStringProcessor 里面的 new TypeReference<List<String>> 有些刺眼,泛型 List<String>在类定义上就有,这里又多写了一遍,有那么点重复代码的感觉。 出于对代码更高的追求(瞎折腾),我们决定除掉它。

第一次改进

想要去掉new TypeReference<list<String>>,就需要通过代码获取 ListStringProcessor 的泛型信息。我们无奈的发现,Java 的泛型参数信息对于类本身而言是没办法进行获取的。 不过我们没有放弃,再仔细查看api发现,有两个叫getGenericSuperclass和getGenericInterfaces的方法,如果父类或者接口是泛型的,那么可以把它们的返回值转成 ParameterizedType 对象,然后通过ParameterizedType的getActualTypeArguments方法获取泛型参数(那对于没有定义父类的类,我们就不能获取到泛型参数信息吗?没错,好像只能放弃治疗)。 泛型参数获取到之后,我们会发现 getActualTypeArguments() 返回的类型是 Type[],通过查找 jackson api,我们看到这个 Type 对象也可以起到类似 TypeReference 的作用,重写后代码如下:

public abstract class TypeCapture<T> {

protected final Type captureType() {

Type superclass = getClass().getGenericSuperclass();

return ((ParameterizedType) superclass).getActualTypeArguments()[0];

}

}

public abstract class AbstractCodeProcessor<T> extends TypeCapture {

private static final ObjectMapper MAPPER = new ObjectMapper();

private final int code;

private final JavaType type;

protected AbstractCodeProcessor(int code) {

this.code = code;

this.type = MAPPER.constructType(captureType());

}

@Override

public int code() {

return code;

}

@Override

public final void process(InputStream inputStream) throws Exception {

T data = MAPPER.readValue(inputStream, type);

doProcess(data);

}

protected abstract void doProcess(T data);

}

public class ListStringProcessor extends AbstractCodeProcessor<List<String>> {

public ListStringProcessor() {

super(0);

}

@Override

protected void doProcess(List<String> data) {

// do something

}

}

经过这次修改,我们去掉了碍眼的 new TypeReference<List<String>>,可以看到,现在一点儿重复代码都没有了。 但是,我们又发现了一个新的问题,如果像下面这么写的话,是没有办法获取到泛型参数的。

public class WrongProcessor<T> extends AbstractCodeProcessor<T> {}

CodeProcessor processor = new WrongProcessor<String>();

对于上面的 WrongProcessor<String>类型对象来说,由于泛型擦除,它相当于是 WrongProcessor<Object> 对象,type Capture 方法的结果会是通配符 T。而且这个问题在应用启动时不会出现,并且显而易见会在序列化时出现问题。我们能不能在程序启动时就发现这个问题呢?

第二次改进

通过 debug captureType 方法返回的通配符 T,我们发现它属于 TypeVariable 接口,这个接口专门用来描述通配符。如果我们想要检测的话,貌似在 captureType 函数里面判断一下就可以了。这样子应用启动过程中实例化对象,构造函数就会抛出异常,防止错误代码启动。代码如下:

public abstract class TypeCapture<T> {

protected final Type captureType() {

Type superclass = getClass().getGenericSuperclass();

Type type = ((ParameterizedType) superclass).getActualTypeArguments()[0];

if (type instanceof TypeVariable) {

throw new IllegalStateException("illegal type: " + type);

}

return type;

}

}

到这里似乎终于结束了,但是我们还要继续精益求精(瞎折腾)。

最终版

仔细查看 jdk 源码和文档,可以发现,getActualTypeArguments 方法返回的类型 Type 有这么几个继承它的接口或类。而刚刚出问题的 TypeVariable 只是其中的一种。 Class:具体类 ParameterizedType:带有泛型参数的类型 GenericArrayType:泛型数组 TypeVariable:通配符 WildcardType:泛型参数带有?的情况 想要检查出各种各样的问题,那么就得把这几种情况全都考虑到: 对于 Class 类型,代表检查通过; 对于 TypeVariable 和 WildcardType,代表检查失败; 对于 ParameterizedType,我们可以用 getRawType 获取它的不带泛型参数的类型(List<T>中的List),用 getActualTypeArguments 获取它的泛型参数类型,需要注意的是,getRawType和getActualTypeArguments返回的也是 Type,需要做递归处理; 对于 GenericArrayType,我们可以用 getGenericComponentType 获取它的数组类型,和 ParameterizedType 一样,这也是个 Type,需要做递归处理。 经过仔细分析后,最终版本如下:

public abstract class TypeCapture<T> {

protected final Type captureType() {

Type superclass = getClass().getGenericSuperclass();

Type type = ((ParameterizedType) superclass).getActualTypeArguments()[0];

check(type);

return type;

}

private void check(Type type) {

if (type instanceof Class) {

return;

} else if (type instanceof ParameterizedType) {

checkParameterizedType((ParameterizedType) type);

} else if (type instanceof GenericArrayType) {

check(((GenericArrayType) type).getGenericComponentType());

} else {

throw new IllegalStateException("illegal type: " + type);

}

}

private void checkParameterizedType(ParameterizedType type) {

check(type.getRawType());

Type[] actualTypeArguments = type.getActualTypeArguments();

if (actualTypeArguments != null) {

for (Type actualTypeArgument : actualTypeArguments) {

check(actualTypeArgument);

}

}

}

}

结束语

折腾到这里,总算有了一个较为完善且优雅的解决方案。Java 泛型的限制很多,但只要我们愿意精益求精(折腾),还是能做出一些有用的东西来。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。