前几天调试公司项目的时候遇见了一个very奇怪的问题,今天在这里研究一下
场景
由于公司这边新增加了一个PDA的接口用来查询历史的停车流水数据,我首先从数据库里查除符合要求的数据,并用Stream流循环出流水数据的list,对循环出来的对象进行处理后面put到JSONObject,实际上put流水时,我并没有设置mMap这个键,但是测试时候的返回结果的每一层都给我包裹了一个mMap键,下面是一部分的代码
dayTraceParkPage = dayTraceParkService.selectByHashMap(queryMap,initPage);
List<DayTracePark> dayTraceParkList = dayTraceParkPage.getData();
if (null != dayTraceParkList && dayTraceParkList.size() > 0) {
dayTraceParkList.stream().peek(e -> {
JSONObject res = new JSONObject();
// ... 这里省略一部分代码
//
res.put("traceParkTraceDay", e);
jsonArray.add(res);
}).collect(Collectors.toList());
}
return AjaxUtils.printJson(new JSONResult<Object>(jsonArray,"未关联历史流水表,获取在场流水成功", true));
// 返回结果 控制器我并没有设置mMap节点
{
"data": [
{
"mMap": {
"traceParkTraceDay": {
// ... 省略一部分数据
},
"traceTimeView": "1月9天"
}
},
],
"message": "未关联历史流水表,获取在场流水成功",
"statusCode": 0,
"success": true
}
开始我以为是jdk8(代码中的dayTraceParkList.stream().peek)语法的问题,我就又改用普通的加强for循环,但是结果还是一样的,开始我百思不得其解,后来,我一看发现是引错了包,我用的JSONObject是com.alibaba.dubbo.common 下面的,实际上我应该用 fastjson下面的包(菜得卑微😂),知道真相的我眼泪掉下来,不过我还是决定看一下dubbo包下面的JSONObject为什么会自动添加一个mMap节点, 基于上面这一个,我写了一个简单的main方法模拟一下,代码如下:
// ps:我没有创建maven,直接导入的jar, dubbo-2.5.3 and gson-2.6.1
import com.alibaba.dubbo.common.json.JSONObject;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public class Test {
public static void main(String[] args) {
System.out.println(test());
}
private static String test() {
JSONObject jsonObj = new JSONObject();
jsonObj.put("name", "谭婧杰");
Gson gson = new GsonBuilder().serializeNulls().create();
return gson.toJson(jsonObj);
}
}
// 输出结果:
{"mMap":{"name":"谭婧杰"}}
开始
因为我们先new了一个JSONObject之后,调用了他的put方法,我们追入看一下他的源码(我这里看见了一个mMap,我有点怀疑我开始的返回值就是这个mMap)
// mMap 的本质就是一个HashMap, private Map<String, Object> mMap = new HashMap();
// 也就是说我向jsonObj中put一个值,相当于是put一个值到map中
public void put(String name, Object value)
{
this.mMap.put(name, value);
}
随后我调用了new GsonBuilder().serializeNulls().create(),new 一个GsonBuilder,这里我寻思应该调用了建造者模式创建对象,其实还可以直接用new Gson对象来创建一个Gson,使用GsonBuilder是因为他可以设置各种组件来创建特殊的Gson对象,其实也推荐用GsonBuilder创建,这种点点点的方式就叫链式调用,使用Lombok里的注解@Builder也可以生成相对略微复杂的构建器API
// new GsonBuilder().serializeNulls()只是设置了serializeNulls为true,并且直接返回了一个可以配置的Gson
// Gson在默认情况下序列化的时候是不导出值是null的属性的,配置了serializeNulls以后就会导出值为null的对象,主要应该是方便调试之类的
public GsonBuilder serializeNulls()
{
this.serializeNulls = true;
return this;
}
继续调用create()
public Gson create()
{
List factories = new ArrayList();
// 此this.factories 不是刚刚new出来的factories,而是全局变量 private final List<TypeAdapterFactory> factories = new ArrayList();
// 把GsonBuilder的factories传入到新定义的factories中,并交给新创建的Gson对象。
factories.addAll(this.factories);
// 使用Collections.reverse结合一定方法可以实现对list集合降序排序,但是不能直接使用Collections.reverse(list)这种方式来降序,下面我也写了一个demo
//
Collections.reverse(factories);
factories.addAll(this.hierarchyFactories);
addTypeAdaptersForDate(this.datePattern, this.dateStyle, this.timeStyle, factories);
return new Gson(this.excluder, this.fieldNamingPolicy, this.instanceCreators, this.serializeNulls, this.complexMapKeySerialization, this.generateNonExecutableJson, this.escapeHtmlChars, this.prettyPrinting, this.lenient, this.serializeSpecialFloatingPointValues, this.longSerializationPolicy, factories);
}
其中获取了实例对象之后,我们看一下 gson.toJson(jsonObj);
public String toJson(Object src)
{
if (src == null) {
// 如果src为空 返回一个JsonNull()对象
return toJson(JsonNull.INSTANCE);
}
// 否则调用带两个入参的toJson()方法,传人src值和类型
// 如果任何对象字段都是泛型类型,只是对象本身不应该是泛型类型,则可以用toJson(Object)。
如果对象是泛型类型,则使用toJson(Object,Type)来代替
return toJson(src, src.getClass());
}
public String toJson(Object src, Type typeOfSrc)
{
StringWriter writer = new StringWriter();
toJson(src, typeOfSrc, writer);
return writer.toString();
}
// 继续
public void toJson(Object src, Type typeOfSrc, Appendable writer)
throws JsonIOException
{
try
{
JsonWriter jsonWriter = newJsonWriter(Streams.writerForAppendable(writer));
toJson(src, typeOfSrc, jsonWriter);
} catch (IOException e) {
throw new JsonIOException(e);
}
}
// 上面都是toJson重载方法,最后调用的是toJson(Object src, Type typeOfSrc, JsonWriter writer) JsonWriter是Gson序列化的主体
public void toJson(Object src, Type typeOfSrc, JsonWriter writer)
throws JsonIOException
{
// 根据传入的Type得到对应的TypeAdapter,TypeToken主要是获取泛型信息的
TypeAdapter adapter = getAdapter(TypeToken.get(typeOfSrc));
boolean oldLenient = writer.isLenient();
// 设置宽松的容错性(顶级值可以不是为object/array,数字可以为无穷)
writer.setLenient(true);
boolean oldHtmlSafe = writer.isHtmlSafe();
// html 转义
writer.setHtmlSafe(this.htmlSafe);
boolean oldSerializeNulls = writer.getSerializeNulls();
// 序列化
writer.setSerializeNulls(this.serializeNulls);
try {
//输出,完成整个序列化过程
adapter.write(writer, src);
} catch (IOException e) {
throw new JsonIOException(e);
} finally {
writer.setLenient(oldLenient);
writer.setHtmlSafe(oldHtmlSafe);
writer.setSerializeNulls(oldSerializeNulls);
}
}
public <T> TypeAdapter<T> getAdapter(TypeToken<T> type)
{
TypeAdapter cached = (TypeAdapter)this.typeTokenCache.get(type);
if (cached != null) {
return cached;
}
Map threadCalls = (Map)this.calls.get();
boolean requiresThreadLocalCleanup = false;
if (threadCalls == null) {
threadCalls = new HashMap();
this.calls.set(threadCalls);
requiresThreadLocalCleanup = true;
}
FutureTypeAdapter ongoingCall = (FutureTypeAdapter)threadCalls.get(type);
if (ongoingCall != null) {
return ongoingCall;
}
try
{
FutureTypeAdapter call = new FutureTypeAdapter();
threadCalls.put(type, call);
//遍历Gson的factories集合,其中Adapter封装后的TypeAdapterFacotry也在里面。
for (TypeAdapterFactory factory : this.factories) {
//重新从你封装过后的TypeAdapterFacotry获取到之前封装过后的Adapter
TypeAdapter candidate = factory.create(this, type);
if (candidate != null) {
// 设置委托
call.setDelegate(candidate);
// 放入缓存
this.typeTokenCache.put(type, candidate);
return candidate;
}
}
throw new IllegalArgumentException("GSON cannot handle " + type);
} finally {
threadCalls.remove(type);
if (requiresThreadLocalCleanup)
this.calls.remove();
}
}
// TypeAdapter抽象类有两个方法 toJson里面主要是调用write adapter.write(writer, src);
public abstract class TypeAdapter<T> {
// 类型对象转json
public abstract void write(JsonWriter out, T value) throws IOException;
// 让读取的json转换成指定类型的对象T
public abstract T read(JsonReader in) throws IOException;
}
其实,上面这一段代码中最重要的应该是adapter.write(writer, src),我debugg看了一下他的入参如下图,看来我谭某人猜对了,那个莫名其妙的节点mMap就是这个
总而言之,序列化过程差不多就是
传入对象ObjectType →解析ObjectType ,生成TypeAdapter→ 调用adapter.write(writer, src)
就上面的Collections.reverse(factories)我也测试了一把
long[] data = {1506326821000l, 1506327060000l, 1506326880000l, 1506327000000l, 1506326940000l, 1506326760000l, 1506326700000l};
List list = new ArrayList<>();
for (long key : data) {
list.add(key);
}
System.out.println("原list的值"+list);
//再反转
Collections.reverse(list);
System.out.println("reverse之后的"+list);
// -> 输出结果:
//原list的值[1506326821000, 1506327060000, 1506326880000, 1506327000000, 1506326940000, 1506326760000, 1506326700000]
//reverse之后的值;[1506326700000, 1506326760000, 1506326940000, 1506327000000, 1506326880000, 1506327060000, 1506326821000]
没有变化,要想成功反转需要先排序,必须改进
long[] data = {1506326821000l, 1506327060000l, 1506326880000l, 1506327000000l, 1506326940000l, 1506326760000l, 1506326700000l};
List list = new ArrayList<>();
for (long key : data) {
list.add(key);
}
System.out.println("原list的值"+list);
//先升序
Collections.sort(list);
System.out.println("sort的值"+list);
//再反转
Collections.reverse(list);
System.out.println("reverse之后"+list);
// -> 输出结果
// 原list的值[1506326821000, 1506327060000, 1506326880000, 1506327000000, 1506326940000, 1506326760000, 1506326700000]
// sort的值[1506326700000, 1506326760000, 1506326821000, 1506326880000, 1506326940000, 1506327000000, 1506327060000]
// reverse之后[1506327060000, 1506327000000, 1506326940000, 1506326880000, 1506326821000, 1506326760000, 1506326700000]
TypeToken
泛型擦除
Java的泛型只在编译时有效,到了运行时这个泛型类型就会被擦除掉所以说List<String>
和List<Integer>
在运行时其实都是List<Object>
类型。
为什么选择这种实现机制?不擦除不行么?
在算法中,我们只能根据应用场景选择两个方式的设计,即牺牲时间换空间,或是牺牲空间换时间这两中折中的设计,“类型擦除”也是的,在Java诞生10年后,程序员想实现类似于C++模板的概念(泛型)。但是Java的类库是Java生态中非常宝贵的财富,必须保证向后兼容(即现有的代码和类文件依旧合法)和迁移兼容(泛化的代码和非泛化的代码可互相调用)基于上面这两个背景和考虑。我们才使用类型擦除这一个概念,所以说只要思想不滑坡,办法总比困难多😂
我上次看到网上一篇文章是这么描述规律的: 位于声明一侧的,源码里写了什么到运行时就能看到什么; 位于使用一侧的,源码里写什么到运行时都没了。
而TypeToke主要是获取泛型的类型信息,他的主要思想就是如果List<String>
这样中的泛型会被擦除掉,那么我用一个子类SubList extends List<String>
这样的话,在JVM内部中会把父类泛型的类型保存
这里TypeToke使用场景一般有以下几种:
- 泛型父类需要获取其子类定义的泛型类型
final TypeToken<V> typeToken = new TypeToken<V>(getClass()) {};
classType = (Class<V>) typeToken.getRawType(); //获得子类的泛型类型
- 需要在方法或者局部变量中获取泛型类型时,需要获取类型的泛型类作为TypeToken的泛型参数构造一个匿名的子类,就可以通过getType()方法获取到我们使用的泛型类的泛型参数类型
// 三级警戒:注意注意注意!!! {}是用来定义匿名类,这个匿名类是继承了TypeToken类,它是TypeToken的子类
final TypeToken typeToken = new TypeToken<List<Integer>>() {};
final Type type = typeToken.getType();
在上面的源码分析中,我们只用的了TypeToken.get(typeOfSrc)
final Class<? super T> rawType;
// DK1.5引入了泛型之 后,Java中所有的Class都实现了Type接口Type接口作为Class和ParameterizedType, TypeVariable<D>, GenericArrayType, WildcardType这几种类型的总的父接口。这样可以用Type类型的参数来接受以上五种子类的实参或者返回值类型就是Type类型的参数。统一了与泛型有关的类型和原始类型Class
final Type type;
final int hashCode;
public static TypeToken<?> get(Type type) {
return new TypeToken(type);
}
TypeToken(Type type) {
// (Type)Preconditions.checkNotNull(type)判断空,如果不为空则返回type
this.type = Types.canonicalize((Type)Preconditions.checkNotNull(type));
// 该方法的作用是返回当前的ParameterizedType的类型。如一个List,返回的是List的Type,即返回当前参数化类型本身的Type。
this.rawType = Types.getRawType(this.type);
this.hashCode = this.type.hashCode();
}
static Type getSuperclassTypeParameter(Class<?> subclass) {
Type superclass = subclass.getGenericSuperclass();
if (superclass instanceof Class) {
throw new RuntimeException("Missing type parameter.");
} else {
// ParameterizedType是表示带有泛型参数的类型的Java类型
ParameterizedType parameterized = (ParameterizedType)superclass;
return Types.canonicalize(parameterized.getActualTypeArguments()[0]);
}
}
JsonWriter
在Gson中,Java对象与JSON字符串之间的转换是通过字符流来进行操作的。JsonReader继承于Reader用来读取字符,JsonWriter继承于Writer用来写入字符
/* <h3>例子</h3>
* 假设我们想编码一个消息流,例如: <pre> {@code
* [
* {
* "id": 912345678901,
* "text": "How do I stream JSON in Java?",
* "geo": null,
* "user": {
* "name": "json_newb",
* "followers_count": 41
* }
* },
* {
* "id": 912345678902,
* "text": "@json_newb just use JsonWriter!",
* "geo": [50.454722, -104.606667],
* "user": {
* "name": "jesse",
* "followers_count": 2
* }
* }
* ]}</pre>
* 此代码对上述结构进行编码: <pre> {@code
* public void writeJsonStream(OutputStream out, List<Message> messages) throws IOException {
* JsonWriter writer = new JsonWriter(new OutputStreamWriter(out, "UTF-8"));
* writer.setIndent(" ");
* writeMessagesArray(writer, messages);
* writer.close();
* }
*
* public void writeMessagesArray(JsonWriter writer, List<Message> messages) throws IOException {
* writer.beginArray();
* for (Message message : messages) {
* writeMessage(writer, message);
* }
* writer.endArray();
* }
*
* public void writeMessage(JsonWriter writer, Message message) throws IOException {
* writer.beginObject();
* writer.name("id").value(message.getId());
* writer.name("text").value(message.getText());
* if (message.getGeo() != null) {
* writer.name("geo");
* writeDoublesArray(writer, message.getGeo());
* } else {
* writer.name("geo").nullValue();
* }
* writer.name("user");
* writeUser(writer, message.getUser());
* writer.endObject();
* }
*
* public void writeUser(JsonWriter writer, User user) throws IOException {
* writer.beginObject();
* writer.name("name").value(user.getName());
* writer.name("followers_count").value(user.getFollowersCount());
* writer.endObject();
* }
*
* public void writeDoublesArray(JsonWriter writer, List<Double> doubles) throws IOException {
* writer.beginArray();
* for (Double value : doubles) {
* writer.value(value);
* }
* writer.endArray();
* }}</pre>
*
* <p>每一个JonWriter可能编写一些简单的JSON stream.(JSON流)
* 这个实例是线程不安全的. 调用失败时可能得到一个IllegalStateException异常
*/
// 实现了 AutoCloseable接口对JDK7新添加的带资源的try语句提供了支持,这种try语句可以自动执行资源关闭过程,Flushable强制将缓存的输出写入到与对象关联的流中
public class JsonWriter implements Closeable, Flushable {
// REPLACEMENT_CHARS还是HTML_SAFE_REPLACEMENT_CHARS的关键在于boolean htmlSafe 的取值
private static final String[] REPLACEMENT_CHARS;
private static final String[] HTML_SAFE_REPLACEMENT_CHARS;
/**
*名称和值的分隔符 ":" or ": ".
*/
private String separator = ":";
private boolean lenient;
/*
是否配置为发出可以安全地直接包含在HTML和XML文档中的Json。这将在将HTML字符<、>,&,=写入流之前对它们进行转义。如果没有此设置,XML/HTML编码器应该将这些字符替换为相应的转义序列。
*/
private boolean htmlSafe;
private String deferredName;
/* 是否序列化null
*/
private boolean serializeNulls = true;
}
总结
感觉在Gson的源码中,链式调用这一个写法很常见,上次看《Effective Java》这一本书的时候,上面就说当构造方法参数过多时使用builder模式,当有很多参数时,很难编写客户端代码,而且很难读懂它。程序员不知道这些值是什么意思,并且必须仔细地计算参数才能找到答案。一长串相同类型的参数可能会导致一些细微的bug。如果客户端意外地反转了两个这样的参数,编译器并不会出现错误,但是运行时候会报错,现在在jdk1.8的流操作中,也都使用了链式操作
问题??
如果我没有记错的话在《深入理解计算机系统(原书第三版)》中说(PS:本来想求证一下的,可是忘记是哪一节了,找了半天没有找到,还是纸质书好一点)调用一个方法,入参超过6个的话,会产生寄存器的不足,其他多出来的参数会在栈分配,那么使用链式编程会不会在一定程度上提高了jvm效率了