
Gson(又稱(chēng)Google Gson)是Google公司發(fā)布的一個(gè)開(kāi)放源代碼的Java庫(kù),主要用途為序列化Java對(duì)象為JSON字符串,或反序列化JSON字符串成Java對(duì)象。而JSON(JavaScript Object Notation) 是一種輕量級(jí)的數(shù)據(jù)交換格式,易于人閱讀和編寫(xiě),同時(shí)也易于機(jī)器解析和生成,廣泛應(yīng)用于各種數(shù)據(jù)的交互中,尤其是服務(wù)器與客戶(hù)端的交互。
一. Gson處理對(duì)象的幾個(gè)重要點(diǎn)
推薦把成員變量都聲明稱(chēng)private的
沒(méi)有必要用注解(@Expose 注解)指明某個(gè)字段是否會(huì)被序列化或者反序列化,所有包含在當(dāng)前類(lèi)(包括父類(lèi))中的字段都應(yīng)該默認(rèn)被序列化或者反序列化
如果某個(gè)字段被 transient 這個(gè)Java關(guān)鍵詞修飾,就不會(huì)被序列化或者反序列化
-
下面的實(shí)現(xiàn)方式能夠正確的處理null
當(dāng)序列化的時(shí)候,如果對(duì)象的某個(gè)字段為null,是不會(huì)輸出到Json字符串中的。
當(dāng)反序列化的時(shí)候,某個(gè)字段在Json字符串中找不到對(duì)應(yīng)的值,就會(huì)被賦值為null
如果一個(gè)字段是 synthetic的,他會(huì)被忽視,也即是不應(yīng)該被序列化或者反序列化
內(nèi)部類(lèi)(或者anonymous class(匿名類(lèi)),或者local class(局部類(lèi),可以理解為在方法內(nèi)部聲明的類(lèi)))的某個(gè)字段和外部類(lèi)的某個(gè)字段一樣的話(huà),就會(huì)被忽視,不會(huì)被序列化或者反序列化
二. Gson中的一些注解
@SerializedName注解
該注解能指定該字段在JSON中對(duì)應(yīng)的字段名稱(chēng)
public class Box {
@SerializedName("w")
private int width;
@SerializedName("h")
private int height;
@SerializedName("d")
private int depth;
// Methods removed for brevity
}
也就是說(shuō){"w":10,"h":20,"d":30} 這個(gè)JSON 字符串能夠被解析到上面的width,height和depth字段中。
@Expose注解
該注解能夠指定該字段是否能夠序列化或者反序列化,默認(rèn)的是都支持(true)。
public class Account {
@Expose(deserialize = false)
private String accountNumber;
@Expose
private String iban;
@Expose(serialize = false)
private String owner;
@Expose(serialize = false, deserialize = false)
private String address;
private String pin;
}
需要注意的通過(guò) builder.excludeFieldsWithoutExposeAnnotation()方法是該注解生效。
final GsonBuilder builder = new GsonBuilder();
builder.excludeFieldsWithoutExposeAnnotation();
final Gson gson = builder.create();
@Since和@Until注解
Since代表“自從”,Until 代表”一直到”。它們都是針對(duì)該字段生效的版本。比如說(shuō) @Since(1.2)代表從版本1.2之后才生效,@Until(0.9)代表著在0.9版本之前都是生效的。
public class SoccerPlayer {
private String name;
@Since(1.2)
private int shirtNumber;
@Until(0.9)
private String country;
private String teamName;
// Methods removed for brevity
}
也就是說(shuō)我們利用方法builder.setVersion(1.0)定義版本1.0,如下:
final GsonBuilder builder = new GsonBuilder();
builder.setVersion(1.0);
final Gson gson = builder.create();
final SoccerPlayer account = new SoccerPlayer();
account.setName("Albert Attard");
account.setShirtNumber(10); // Since version 1.2
account.setTeamName("Zejtun Corinthians");
account.setCountry("Malta"); // Until version 0.9
final String json = gson.toJson(account);
System.out.printf("Serialised (version 1.0)%n %s%n", json);
由于shirtNumber和country作用版本分別是1.2之后,和0.9之前,所以在這里都不會(huì)得到序列化,所以輸出結(jié)果是:
Serialised (version 1.0)
{"name":"Albert Attard","teamName":"Zejtun Corinthians"}
三. Gson 序列化
序列化基本類(lèi)型
Gson gson = new Gson();
gson.toJson(1); ==> prints 1
gson.toJson("abcd"); ==> prints "abcd"
gson.toJson(new Long(10)); ==> prints 10
int[] values = { 1 };
gson.toJson(values); ==> prints [1]
序列化對(duì)象
class BagOfPrimitives {
private int value1 = 1;
private String value2 = "abc";
private transient int value3 = 3;
BagOfPrimitives() {
// no-args constructor
}
}
將上邊的pojo序列化
BagOfPrimitives obj = new BagOfPrimitives();
Gson gson = new Gson();
String json = gson.toJson(obj);
==> json is {"value1":1,"value2":"abc"}
transient 關(guān)鍵字防止序列化.
序列化集合
Gson gson = new Gson();
Collection<Integer> ints = Lists.immutableList(1,2,3,4,5);
String json = gson.toJson(ints); ==> json is [1,2,3,4,5]
序列化集合很容易,但反序列化不易. 看之后的反序列化就知道了.
Gson解析器進(jìn)行序列化
英文Serialize和format都對(duì)應(yīng)序列化,這是一個(gè)Java對(duì)象到JSON字符串的過(guò)程。
接著看一個(gè)例子,下面分別是java類(lèi)和以及我們期望的JSON數(shù)據(jù):
public class Book {
private String[] authors;
private String isbn10;
private String isbn13;
private String title;
//為了代碼簡(jiǎn)潔,這里移除getter和setter方法等
}
{
"title": "Java Puzzlers: Traps, Pitfalls, and Corner Cases",
"isbn-10": "032133678X",
"isbn-13": "978-0321336781",
"authors": [
"Joshua Bloch",
"Neal Gafter"
]
}
你肯定能發(fā)現(xiàn)JSON數(shù)據(jù)中出現(xiàn)了isbn-10和isbn-13, 我們?cè)趺窗炎侄螖?shù)據(jù)isbn10和isbn13轉(zhuǎn)化為JSON數(shù)據(jù)需要的isbn-10和isbn-13,Gson當(dāng)然為我們提供了對(duì)應(yīng)的解決方案
采用上面提到的@SerializedName注解。
public class Book {
private String[] authors;
@SerializedName("isbn-10")
private String isbn10;
@SerializedName("isbn-13")
private String isbn13;
private String title;
//為了代碼簡(jiǎn)潔,這里移除getter和setter方法等
}
public class BookSerialiser implements JsonSerializer {
@Override
public JsonElement serialize(final Book book, final Type typeOfSrc, final JsonSerializationContext context) {
final JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("title", book.getTitle());
jsonObject.addProperty("isbn-10", book.getIsbn10());
jsonObject.addProperty("isbn-13", book.getIsbn13());
final JsonArray jsonAuthorsArray = new JsonArray();
for (final String author : book.getAuthors()) {
final JsonPrimitive jsonAuthor = new JsonPrimitive(author);
jsonAuthorsArray.add(jsonAuthor);
}
jsonObject.add("authors", jsonAuthorsArray);
return jsonObject;
}
}
下面對(duì)序列化過(guò)程進(jìn)行大致的分析:
- JsonSerializer是一個(gè)接口,我們需要提供自己的實(shí)現(xiàn),來(lái)滿(mǎn)足自己的序列化要求。
public interface JsonSerializer<T> {
/**
* Gson 會(huì)在解析指定類(lèi)型T數(shù)據(jù)的時(shí)候觸發(fā)當(dāng)前回調(diào)方法進(jìn)行序列化
*
* @param T 需要轉(zhuǎn)化為Json數(shù)據(jù)的類(lèi)型,對(duì)應(yīng)上面的Book
* @return 返回T指定的類(lèi)對(duì)應(yīng)JsonElement
*/
public JsonElement serialize(T src, Type typeOfSrc, JsonSerializationContext context);
}
- 首先在上面的代碼中,我們需要?jiǎng)?chuàng)建的是一個(gè)JsonElement對(duì)象,這里對(duì)應(yīng)Book是一個(gè)Json串,所以創(chuàng)建一個(gè)JsonObject類(lèi)型。
final JsonObject jsonObject = new JsonObject(); - 然后我們將相應(yīng)字段里面的數(shù)據(jù)填充到j(luò)sonObject里面。
jsonObject.addProperty...
jsonObject.add...
下面是jsonObject中的添加方法:

因?yàn)镴sonElement是JsonObject和JsonArray、JsonPrimitive..的抽象父類(lèi),所以最后返回的還是一個(gè)JsonElement 類(lèi)型,這里對(duì)應(yīng)的是jsonObject。完成了javaBean->JSON數(shù)據(jù)的轉(zhuǎn)化。
同樣需要配置
// Configure GSON
final GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Book.class, new BookSerialiser());
gsonBuilder.setPrettyPrinting();
final Gson gson = gsonBuilder.create();
final Book javaPuzzlers = new Book();
javaPuzzlers.setTitle("Java Puzzlers: Traps, Pitfalls, and Corner Cases");
javaPuzzlers.setIsbn10("032133678X");
javaPuzzlers.setIsbn13("978-0321336781");
javaPuzzlers.setAuthors(new String[] { "Joshua Bloch", "Neal Gafter" });
// Format to JSON
final String json = gson.toJson(javaPuzzlers);
System.out.println(json);
這里對(duì)應(yīng)的是gsonBuilder.registerTypeAdapter(Book.class, new BookSerialiser())方法進(jìn)行JsonSerializer的配置。在上面例子中,通過(guò)調(diào)用gsonBuilder.setPrettyPrinting();`方法還告訴了 Gson 對(duì)生成的 JSON 對(duì)象進(jìn)行格式化。
四. Gson 反序列化
反序列化基本類(lèi)型
int one = gson.fromJson("1", int.class);
Integer one = gson.fromJson("1", Integer.class);
Long one = gson.fromJson("1", Long.class);
Boolean false = gson.fromJson("false", Boolean.class);
String str = gson.fromJson("\"abc\"", String.class);
String anotherStr = gson.fromJson("[\"abc\"]", String.class);
反序列化對(duì)象
class BagOfPrimitives {
private int value1 = 1;
private String value2 = "abc";
private transient int value3 = 3;
BagOfPrimitives() {
// no-args constructor
}
}
BagOfPrimitives obj2 = gson.fromJson(json, BagOfPrimitives.class);
==> obj2 is just like obj
這樣就可以反序列化了.
反序列化集合
Gson gson = new Gson();
Collection<Integer> ints = Lists.immutableList(1,2,3,4,5);
Type collectionType = new TypeToken<Collection<Integer>>(){}.getType();
Collection<Integer> ints2 = gson.fromJson(json, collectionType);
ints2 is same as ints
這里使用TypeToken<T>去接收類(lèi)型,因?yàn)樗诜葱蛄谢臅r(shí)候并不知道反序列化的是什么類(lèi)型,所以通過(guò)
new TypeToken<傳入集合或其他的類(lèi)型>(){}.getType();
來(lái)取得類(lèi)型,然后再在
gson.fromJson(json, collectionType);
取得反序列化的數(shù)據(jù).
可以序列化任意對(duì)象的集合但不能反序列化.這是因?yàn)?strong>沒(méi)有途徑使得用戶(hù)可以去提示該對(duì)象的類(lèi)型。
反序列化過(guò)程中,集合必須制定特定的泛型
所有這些是有意義的,它使得你在遵循好的Java編碼實(shí)踐的過(guò)程中很少發(fā)生錯(cuò)誤。
Gson解析器進(jìn)行反序列化
英文parse和deserialise對(duì)應(yīng)反序列化,這是一個(gè)字符串轉(zhuǎn)換成Java對(duì)象的過(guò)程。
我們同樣采用上面一小節(jié)的代碼片段,只不過(guò)現(xiàn)在我們需要做的是將:
{
"title": "Java Puzzlers: Traps, Pitfalls, and Corner Cases",
"isbn-10": "032133678X",
"isbn-13": "978-0321336781",
"authors": [
"Joshua Bloch",
"Neal Gafter"
]
}
轉(zhuǎn)化為對(duì)應(yīng)的Book實(shí)體類(lèi)
利用@SerializedName 注解
也就是說(shuō)我們的實(shí)體類(lèi)Book.java可以這么寫(xiě):
public class Book {
private String[] authors;
@SerializedName("isbn-10")
private String isbn10;
@SerializedName(value = "isbn-13", alternate = {"isbn13","isbn.13"})
private String isbn13;
private String title;
//為了代碼簡(jiǎn)潔,這里移除getter和setter方法等
}

可以看到這里我們?cè)?code>@SerializedName 注解使用了一個(gè)
value,alternate字段,value也就是默認(rèn)的字段,對(duì)序列化和反序列化都有效,alternate只有反序列化才有效果。也就是說(shuō)一般服務(wù)器返回給我們JSON數(shù)據(jù)的時(shí)候可能同樣的一個(gè)圖片,表示"image","img","icon"等,我們利用@SerializedName中的alternate字段就能解決這個(gè)問(wèn)題,全部轉(zhuǎn)化為我們實(shí)體類(lèi)中的圖片字段。
我們?cè)谛蛄谢臅r(shí)候使用的是JsonSerialize ,這里對(duì)應(yīng)使用JsonDeserializer
我們將解析到的json數(shù)據(jù)傳遞給Book的setter方法即可。
public class BookDeserializer implements JsonDeserializer<Book> {
@Override
public Book deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context)
throws JsonParseException {
final JsonObject jsonObject = json.getAsJsonObject();
final JsonElement jsonTitle = jsonObject.get("title");
final String title = jsonTitle.getAsString();
final String isbn10 = jsonObject.get("isbn-10").getAsString();
final String isbn13 = jsonObject.get("isbn-13").getAsString();
final JsonArray jsonAuthorsArray = jsonObject.get("authors").getAsJsonArray();
final String[] authors = new String[jsonAuthorsArray.size()];
for (int i = 0; i < authors.length; i++) {
final JsonElement jsonAuthor = jsonAuthorsArray.get(i);
authors[i] = jsonAuthor.getAsString();
}
final Book book = new Book();
book.setTitle(title);
book.setIsbn10(isbn10);
book.setIsbn13(isbn13);
book.setAuthors(authors);
return book;
}
}
和Gson序列化章節(jié)一樣,我們這里接著分析我們是怎么將JSON數(shù)據(jù)解析(反序列化)為實(shí)體類(lèi)的:
- 因?yàn)槲覀兛梢园l(fā)現(xiàn)上面的JSON數(shù)據(jù)是一個(gè)
{}大括號(hào)包圍的,也就意味著這是一個(gè)Json對(duì)象。所以首先我們通過(guò)final JsonObject jsonObject = json.getAsJsonObject();`將我們的JsonElement轉(zhuǎn)化為JsonObject - 通過(guò)
jsonObject.get("xxx").getAsString()的形式獲取相應(yīng)String的值 - 通過(guò)
jsonObject.get("xx").getAsJsonArray();獲取相應(yīng)的json數(shù)組,并遍歷出其中的相應(yīng)字段值 - 通過(guò)setter方法,將獲取到的值設(shè)置給Book類(lèi)。
- 最終返回的是 Book的對(duì)象實(shí)例。完成了JSON->javaBean的轉(zhuǎn)化
- 同樣需要配置
- 關(guān)于從本地流中讀取Json數(shù)據(jù)可以使用
InputStreamReader完成
// Configure Gson
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Book.class, new BookDeserializer());
Gson gson = gsonBuilder.create();
// The JSON data
try(Reader reader = new InputStreamReader(Main.class.getResourceAsStream("/part1/sample.json"), "UTF-8")){
// Parse JSON to Java
Book book = gson.fromJson(reader, Book.class);
System.out.println(book);
}
五. TypeAdapter的使用
TypeAdapter介紹
除了運(yùn)用JsonSerializer和JsonDeserializer進(jìn)行JSON和java實(shí)體類(lèi)之間的相互轉(zhuǎn)化。我們還可以利用TypeAdapter更加高效的完成這個(gè)需求。
之前在上一篇文中提到的JsonSerializer和JsonDeserializer解析的時(shí)候都利用到了一個(gè)中間件-JsonElement,比如下方的序列化過(guò)程??梢钥吹轿覀?cè)诎袹ava對(duì)象轉(zhuǎn)化為JSON字符串的時(shí)候都會(huì)用到這個(gè)中間件JsonElement

而TypeAdapter的使用正是去掉了這個(gè)中間層,直接用流來(lái)解析數(shù)據(jù),極大程度上提高了解析效率。
New applications should prefer TypeAdapter, whose streaming API is more efficient than this interface’s tree API.
應(yīng)用中應(yīng)當(dāng)盡量使用
TypeAdapter,它流式的API相比于之前的樹(shù)形解析API將會(huì)更加高效。
TypeAdapter作為一個(gè)抽象類(lèi)提供兩個(gè)抽象方法。分別是write()和read()方法,也對(duì)應(yīng)著序列化和反序列化。如下圖所示:

下面就讓我們來(lái)一起使用和了解TypeAdapter吧。
TypeAdapter實(shí)例
Book.java實(shí)體類(lèi):
package com.javacreed.examples.gson.part1;
public class Book {
private String[] authors;
private String isbn;
private String title;
//為了代碼簡(jiǎn)潔,這里移除getter和setter方法等
}
直接貼代碼,具體序列化和反序列化的TypeAdapter類(lèi),這里是BookTypeAdapter.java:
public class BookTypeAdapter extends TypeAdapter {
@Override
public Book read(final JsonReader in) throws IOException {
final Book book = new Book();
in.beginObject();
while (in.hasNext()) {
switch (in.nextName()) {
case "isbn":
book.setIsbn(in.nextString());
break;
case "title":
book.setTitle(in.nextString());
break;
case "authors":
book.setAuthors(in.nextString().split(";"));
break;
}
}
in.endObject();
return book;
}
@Override
public void write(final JsonWriter out, final Book book) throws IOException {
out.beginObject();
out.name("isbn").value(book.getIsbn());
out.name("title").value(book.getTitle());
out.name("authors").value(StringUtils.join(book.getAuthors(), ";"));
out.endObject();
}
}
同樣這里設(shè)置TypeAdapter之后還是需要配置(注冊(cè)),可以注意到的是gsonBuilder.registerTypeAdapter(xxx)方法進(jìn)行注冊(cè)在我們之前的JsonSerializer和JsonDeserializer中也有使用:
final GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Book.class, new BookTypeAdapter());
final Gson gson = gsonBuilder.create();
下面對(duì)兩個(gè)write方法和read方法進(jìn)行分別的闡述:
TypeAdapter中的write方法
write()方法中會(huì)傳入JsonWriter,和需要被序列化的Book對(duì)象的實(shí)例,采用和PrintStream類(lèi)似的方式 寫(xiě)入到JsonWriter中。
@Override
public void write(final JsonWriter out, final Book book) throws IOException {
out.beginObject();
out.name("isbn").value(book.getIsbn());
out.name("title").value(book.getTitle());
out.name("authors").value(StringUtils.join(book.getAuthors(), ";"));
out.endObject();
}
下面是上面代碼的步驟:
-
out.beginObject()產(chǎn)生{,如果我們希望產(chǎn)生的是一個(gè)數(shù)組對(duì)象,對(duì)應(yīng)的使用beginArray() -
out.name("isbn").value(book.getIsbn()); out.name("title").value(book.getTitle());分別獲取book中的isbn和title字段并且設(shè)置給Json對(duì)象中的isbn和title。也就是說(shuō)上面這段代碼,會(huì)在json對(duì)象中產(chǎn)生:
"isbn": "978-0321336781",
"title": "Java Puzzlers: Traps, Pitfalls, and Corner Cases",
-
out.name("authors").value(StringUtils.join(book.getAuthors(), ";"));則會(huì)對(duì)應(yīng)著:
"authors": "Joshua Bloch;Neal Gafter"
- 同理
out.endObject()則對(duì)應(yīng)著} - 那么整個(gè)上面的代碼也就會(huì)產(chǎn)生JSON對(duì)象:
{
"isbn": "978-0321336781",
"title": "Java Puzzlers: Traps, Pitfalls, and Corner Cases",
"authors": "Joshua Bloch;Neal Gafter"
}
這里需要注意的是,如果沒(méi)有調(diào)用 out.endObject()產(chǎn)生},那么你的項(xiàng)目會(huì)報(bào)出 JsonSyntaxException錯(cuò)誤
Exception in thread "main" com.google.gson.JsonSyntaxException: java.io.EOFException: End of input at line 4 column 40
at com.google.gson.Gson.fromJson(Gson.java:813)
at com.google.gson.Gson.fromJson(Gson.java:768)
at com.google.gson.Gson.fromJson(Gson.java:717)
at com.google.gson.Gson.fromJson(Gson.java:689)
at com.javacreed.examples.gson.part1.Main.main(Main.java:41)
Caused by: java.io.EOFException: End of input at line 4 column 40
at com.google.gson.stream.JsonReader.nextNonWhitespace(JsonReader.java:1377)
at com.google.gson.stream.JsonReader.doPeek(JsonReader.java:471)
at com.google.gson.stream.JsonReader.hasNext(JsonReader.java:403)
at com.javacreed.examples.gson.part1.BookTypeAdapter.read(BookTypeAdapter.java:33)
at com.javacreed.examples.gson.part1.BookTypeAdapter.read(BookTypeAdapter.java:1)
at com.google.gson.Gson.fromJson(Gson.java:803)
... 4 more
TypeAdapter中的read方法
read()方法將會(huì)傳入一個(gè)JsonReader對(duì)象實(shí)例并返回反序列化的對(duì)象。
@Override
public Book read(final JsonReader in) throws IOException {
final Book book = new Book();
in.beginObject();
while (in.hasNext()) {
switch (in.nextName()) {
case "isbn":
book.setIsbn(in.nextString());
break;
case "title":
book.setTitle(in.nextString());
break;
case "authors":
book.setAuthors(in.nextString().split(";"));
break;
}
}
in.endObject();
return book;
}
下面是這段代碼的步驟:
- 同樣是通過(guò)
in.beginObject();和in.endObject();對(duì)應(yīng)解析{,} - 通過(guò)
while (in.hasNext()) {
switch (in.nextName()) {
}
}
來(lái)完成每個(gè)JsonElement的遍歷,并且通過(guò)switch...case的方法獲取Json對(duì)象中的鍵值對(duì)。并通過(guò)我們Book實(shí)體類(lèi)的Setter方法進(jìn)行設(shè)置。
while (in.hasNext()) {
switch (in.nextName()) {
case "isbn":
book.setIsbn(in.nextString());
break;
case "title":
book.setTitle(in.nextString());
break;
case "authors":
book.setAuthors(in.nextString().split(";"));
break;
}
}
同樣需要注意的是,如果沒(méi)有執(zhí)行in.endObject(),將會(huì)出現(xiàn)JsonIOException的錯(cuò)誤:
Exception in thread "main" com.google.gson.JsonIOException: JSON document was not fully consumed.
at com.google.gson.Gson.assertFullConsumption(Gson.java:776)
at com.google.gson.Gson.fromJson(Gson.java:769)
at com.google.gson.Gson.fromJson(Gson.java:717)
at com.google.gson.Gson.fromJson(Gson.java:689)
at com.javacreed.examples.gson.part1.Main.main(Main.java:41)
下面給出使用TypeAdapter的完整代碼:
public static void main(final String[] args) throws IOException {
final GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Book.class, new BookTypeAdapter());
gsonBuilder.setPrettyPrinting();
final Gson gson = gsonBuilder.create();
final Book book = new Book();
book.setAuthors(new String[] { "Joshua Bloch", "Neal Gafter" });
book.setTitle("Java Puzzlers: Traps, Pitfalls, and Corner Cases");
book.setIsbn("978-0321336781");
final String json = gson.toJson(book);
System.out.println("Serialised");
System.out.println(json);
final Book parsedBook = gson.fromJson(json, Book.class);
System.out.println("\nDeserialised");
System.out.println(parsedBook);
}
對(duì)應(yīng)的編譯結(jié)果為:
Serialised
{
"isbn": "978-0321336781",
"title": "Java Puzzlers: Traps, Pitfalls, and Corner Cases",
"authors": "Joshua Bloch;Neal Gafter"
}
Deserialised
Java Puzzlers: Traps, Pitfalls, and Corner Cases [978-0321336781]
Written by:
>> Joshua Bloch
>> Neal Gafter