1. 引言
在本教程中,我們將探討如何使用 Gson 管理多態性。我們還將探索一些處理多態方式序列化和反序列化的技術。
2. JSON 中的多態性
在 Java 中,多態性得到了很好的理解。我們有一個類層次結構,使得在適當的情況下,我們可以以某種方式將不同的相關類型視為相同。
例如,我們可能有一些各種二維形狀的定義。不同的形狀以不同的方式定義,但它們都具有一些共同的功能——例如,它們都可以計算面積。
因此,我們可以定義一些用於定義形狀的類,如下所示:
interface Shape {
double getArea();
}
class Circle implements Shape {
private final double radius;
private final double area;
Circle(double radius) {
this.radius = radius;
this.area = Math.PI * radius * radius;
}
@Override
public double getArea() {
return area;
}
}
class Square implements Shape {
private final double side;
private final double area;
Square(double side) {
this.side = side;
this.area = side * side;
}
@Override
public double getArea() {
return area;
}
}每個這些形狀都有各自的關注點,但如果只關心它們都是 Shape,並且能夠計算它們的面積,我們就可以將它們都視為相同的事物。
但這與 JSON 有什麼關係?我們顯然不能在 JSON 文檔中包含任何功能,但我們可以包含重疊的數據,並且我們可能希望在 JSON 中以合理的方式表示多態類。
例如,上述形狀可能被表示為:
[
{
"shape": "circle",
"radius": 4,
"area": 50.26548245743669
}, {
"shape": "square",
"side": 5,
"area": 25
}
]如果我們僅僅將它們視為 Shape 實例,我們已經有 area 可供使用。但是,如果我們想知道它們究竟是什麼形狀,那麼我們可以識別並從它們中提取額外信息。
3. 使用包裝對象
最簡單的方法是使用包裝對象以及為每種類型定義不同的字段:
class Wrapper {
private final Circle circle;
private final Square square;
Wrapper(Circle circle) {
this.circle = circle;
this.square = null;
}
Wrapper(Square square) {
this.square = square;
this.circle = null;
}
}使用類似這樣的內容,我們的JSON將看起來像這樣:
[
{
"circle": {
"radius": 4,
"area": 50.26548245743669
}
}, {
"square": {
"side": 5,
"area": 25
}
}
]這並非嚴格意義上的多態,而且這意味着 JSON 的結構與我們之前見到的有所不同,但實現起來非常簡單。特別是,我們只需要編寫我們的包裝類型,Gson 就會自動完成所有工作:
List<Wrapper> shapes = Arrays.asList(
new Wrapper(new Circle(4d)),
new Wrapper(new Square(5d))
);
Gson gson = new Gson();
String json = gson.toJson(shapes);
反序列化這同樣也完全符合我們的預期:
Gson gson = new Gson();
Type collectionType = new TypeToken<List<Wrapper>>(){}.getType();
List<Wrapper> shapes = gson.fromJson(json, collectionType);
請注意,這裏需要使用 TypeToken,因為我們正在將數據反序列化到泛型列表。這與我們的包裝類型和多態結構無關。
但是,這意味着我們的 Wrapper 類型需要支持所有可能的子類型。 添加新的子類型意味着要付出更多的工作來達到我們想要的結果。
4. 為對象添加類型字段
如果我們僅對對象進行序列化感興趣,可以簡單地向它們添加一個字段來指示類型:
public class Square implements Shape {
private final String type = "square"; // Added field
private final double side;
private final double area;
public Square(double side) {
this.side = side;
this.area = side * side;
}
}這樣做將會導致新的 類型 字段出現在序列化的 JSON 中,從而讓客户端知道每個形狀的類型是:
{
"type": "square",
"radius": 5,
"area": 25
}現在更接近我們之前看到的內容。 但是,我們無法使用這種技術輕鬆地反序列化這個 JSON,因此它僅在不需要執行此操作的情況下才可行。
5. 自定義類型適配器
我們將探索的最終方法是編寫自定義類型適配器。這是一種我們可以在 Gson 實例中貢獻的代碼,它將處理我們類型序列化和反序列化,從而為我們完成任務。
5.1. 自定義序列化器
我們首先要實現的功能是正確地序列化我們的類型。這意味着使用標準的邏輯序列化它們,然後添加一個額外的 類型 字段,指示對象的類型。
我們通過為我們的類型編寫自定義的 JsonSerializer 實現來完成這個任務:
public class ShapeTypeAdapter implements JsonSerializer<Shape> {
@Override
public JsonElement serialize(Shape shape, Type type, JsonSerializationContext context) {
JsonElement elem = new Gson().toJsonTree(shape);
elem.getAsJsonObject().addProperty("type", shape.getClass().getName());
return elem;
}
}請注意,為了序列化自身的值,我們需要使用一個新的Gson實例。如果使用原始實例(通過JsonSerializationContext)則會陷入無限循環,序列化器會不斷調用自身。
這裏我們使用完整的類名作為類型,但我們也可以使用任何我們想要支持的類型。我們只需要某種方式來唯一地將此字符串與類名進行轉換。
使用此方法,生成的JSON將是:
[
{
"radius": 4,
"area": 50.26548245743669,
"type": "com.baeldung.gson.polymorphic.TypeAdapterUnitTest$Circle"
},
{
"side": 5,
"area": 25,
"type": "com.baeldung.gson.polymorphic.TypeAdapterUnitTest$Square"
}
]5.2. 自定義反序列器
現在我們能夠將我們的類型序列化為 JSON,我們也需要能夠對其進行反序列化。這意味着我們需要理解 type 字段,然後使用它將其反序列化為正確的類。
我們通過為我們的類型編寫自定義的 JsonDeserializer 實現來實現這一點:
public class ShapeTypeAdapter implements JsonDeserializer<Shape> {
@Override
public Shape deserialize(JsonElement json, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
JsonObject jsonObject = json.getAsJsonObject();
String typeName = jsonObject.get("type").getAsString();
try {
Class<? extends Shape> cls = (Class<? extends Shape>) Class.forName(typeName);
return new Gson().fromJson(json, cls);
} catch (ClassNotFoundException e) {
throw new JsonParseException(e);
}
}
}
我們可以通過簡單地實現這兩個接口,將我們的序列化器和反序列器都放在同一個類中實現。 這樣做有助於保持邏輯的一致性,確保這兩個組件之間是兼容的。
正如之前所述,實際進行反序列化需要使用一個新的 Gson 實例。 否則,我們將陷入無限循環。
5.3. 將類型適配器連接到 Gson
現在我們已經擁有一個可以用於序列化和反序列化我們的多態類型,並且可以被使用,那麼我們需要創建一個 Gson 實例並將其連接上。
GsonBuilder builder = new GsonBuilder();
builder.registerTypeHierarchyAdapter(Shape.class, new ShapeTypeAdapter());
Gson gson = builder.create();
我們使用 registerTypeHierarchyAdapter> 調用來完成這項工作,因為這意味着它將被用於我們的 Shape 類及其所有實現者。 這將導致該 Gson 實例在嘗試將任何實現我們的 Shape 接口的內容序列化為 JSON 或嘗試將 JSON 序列化為任何實現 Shape 接口的內容時,使用該適配器。
List<Shape> shapes = List.of(new Circle(4d), new Square(5d));
String json = gson.toJson(shapes);
Type collectionType = new TypeToken<List<Shape>>(){}.getType();
List<Shape> result = gson.fromJson(json, collectionType);
assertEquals(shapes, result);
6. 總結
在這裏,我們學習了使用 Gson 管理多態類型的一些技術,包括將它們序列化為 JSON 以及將它們從 JSON 中反序列化回來。
下次您處理 JSON 和多態類型時,不妨嘗試一下這些技術。