2013-01-24 13 views
8

Tôi đang viết một lớp sẽ kết nối với máy chủ và dựa trên một số đối số, truy xuất chuỗi json sẽ được phân tích bằng GSON đến lớp được chỉ định (qua Generics) .Hành vi lạ khi deserializing lồng nhau, các lớp chung với GSON

Một tước xuống phiên bản của lớp phụ trách trông như thế này:

class Executor<T> { 

    private Response<T> response; 

    public void execute() { 
     Type responseType = new TypeToken<Response<T>>() {}.getType(); 
     this.response = new Gson().fromJson(json, responseType); 
    } 

    public Response<T> getResponse() { return this.response; } 

} 

(các JSON -variable looks like this.)

Lớp mà các cửa hàng dữ liệu một lần vẻ như thế này serialized de-:

class Response<T> { 

    private List<T> data = null; 

    public List<T> getData() { return this.data; } 

} 

lớp đó các dữ liệu đang cố gắng để được de-đăng để:

public class Language { 
    public String alias; 
    public String label; 
} 

Và mã mà chạy sử dụng các lớp trên:

Executor<Language> executor = new Executor<Language(); 
List<Language> languages = executor.execute().getResponse().getData(); 
System.out.println(languages.get(0).alias); // exception occurs here 

Những kết quả trong các ngoại lệ sau đây

ClassCastException: com.google.gson.internal.StringMap không thể được đúc để sunnerberg.skolbibliotek.book.Language

Bất kỳ trợ giúp hoặc đề xuất nào được đánh giá cao!

+0

Bạn có chắc chắn ngoại lệ xảy ra ở dòng này không? Tôi không thể thấy nơi execute() được gọi trong mã mẫu mà bạn cung cấp. –

+0

@SamuelEUSTACHI Ồ, tệ của tôi. Câu hỏi chỉnh sửa, nó trượt ra khi tôi dán mã (nó là một chút đơn giản). Có, tôi chắc chắn 100%, nó xảy ra chính xác tại dòng đó (ít nhất theo stacktrace). – Zar

+0

@Zar executor = new Executor Visruth

Trả lời

19

Câu trả lời ngắn gọn là những gì bạn cần để di chuyển tạo ra TypeToken ra khỏi Executor, ràng buộc T trong Response<T> khi bạn tạo các token (new TypeToken<Response<Language>>() {}), và vượt qua trong các loại thẻ để các nhà xây dựng Executor.

Câu trả lời dài là:

Generics trên một loại thường được xoá hoàn toàn trong thời gian chạy, trừ khi loại là biên soạn với tham số chung ràng buộc. Trong trường hợp đó, trình biên dịch sẽ chèn thông tin kiểu chung vào lớp được biên dịch. Trong các trường hợp khác, điều đó là không thể.

Vì vậy, ví dụ, hãy xem xét:

List<Integer> foo = new ArrayList<Integer>(); 

class IntegerList extends ArrayList<Integer> { ... } 
List<Integer> bar = new IntegerList(); 

Khi chạy, Java biếtbar chứa số nguyên vì loại Integer là ràng buộc để ArrayList tại thời gian biên dịch, vì vậy những thông tin kiểu generic được lưu trong lớp IntegerList tập tin. Tuy nhiên, thông tin loại chung cho foo bị xóa, do đó, trong thời gian chạy nó không thực sự có thể xác định rằng foo được cho là chứa Integer s. Vì vậy, thường xuất hiện rằng chúng ta cần thông tin kiểu chung trong một tình huống mà thông thường sẽ bị xóa trước khi chạy, chẳng hạn như ở đây trong trường hợp phân tích cú pháp dữ liệu JSON trong GSON.Trong những tình huống này, chúng ta có thể tận dụng thông tin loại được giữ nguyên khi biên dịch tại thời điểm biên dịch (như trong ví dụ IntegerList ở trên) bằng cách sử dụng type tokens, đây thực sự là các lớp ẩn danh nhỏ, thuận tiện lưu trữ thông tin loại chung.

Bây giờ để mã của bạn:

Type responseType = new TypeToken<Response<T>>() {}.getType(); 

Trong dòng này của lớp Executor bạn, chúng tôi tạo ra một lớp vô danh (kế thừa từ TypeToken) trong đó có các loại Response<T> cứng mã hoá (ràng buộc) tại thời gian biên dịch. Vì vậy, trong thời gian chạy, GSON có thể xác định rằng bạn muốn một đối tượng của Response<T>. Nhưng nó không biết những gì T là, bởi vì bạn đã không xác định nó tại thời gian biên dịch! Vì vậy, GSON không thể xác định loại nào sẽ ở trong các đối tượng List của đối tượng Response mà nó tạo và thay vào đó, chỉ cần tạo một số StringMap.

Đạo đức của câu chuyện là bạn cần chỉ định rằng T lúc biên dịch. Nếu Executor có nghĩa là được sử dụng chung, bạn có thể cần phải tạo mã thông báo loại bên ngoài lớp đó, trong mã máy khách của bạn. Một cái gì đó như:

class Executor<T> { 

    private TypeToken<Response<T>> responseType; 
    private Response<T> response; 

    public Executor(TypeToken<Response<T>> responseType) { 
     this.responseType = responseType; 
    } 

    public void execute() { 
     this.response = new Gson().fromJson(json, responseType.getType()); 
    } 

    public Response<T> getResponse() { return this.response; } 

} 

// client code: 
Executor<Language> executor = new Executor<Language>(new TypeToken<Response<Language>>() { }); 
executor.execute(); 
List<Language> languages = executor.getResponse().getData(); 
System.out.println(languages.get(0).alias); // prints "be" 

Nhân tiện, tôi đã thử nghiệm ở trên trên máy tính của mình.

Xin lỗi nếu quá lâu!

+3

Trước hết, tôi phải nói rằng người dùng StackOverflow không bao giờ ngừng làm tôi ngạc nhiên. Wow, câu trả lời là gì. Chính xác những gì tôi đang tìm kiếm (và cần thiết!), Cảm ơn nhiều bạn! Giải pháp của bạn đã giải quyết vấn đề của tôi một cách hoàn hảo. Nhân tiện, có lý do gì khiến JVM xóa thông tin kiểu không? – Zar

+0

Vâng, cảm ơn bạn đã đưa ra một câu hỏi chi tiết và đầy đủ! Đối với loại tẩy xoá, tôi tin rằng Java làm theo cách này để tương thích ngược. Mã được biên dịch sử dụng Generics trông rất giống với mã được biên dịch không sử dụng Generics. Vì vậy, ví dụ, nếu bạn có một số mã cũ có sử dụng * SomeLibrary 1.0 * bạn có thể chuyển sang * SomeLibrary 2.0 (Now With Generics) * mà không biên dịch lại mã của bạn. Hãy xem [bài đăng blog] này (http://blog.adaptivesoftware.biz/2009/02/what-is-type-erasure-in-java.html) để được giải thích khác. – matts

+0

Tôi đoán nó có ý nghĩa, cảm ơn một lần nữa! Thú vị liên kết, bằng cách này. – Zar

0

Sự cố của bạn được liên kết với java Loại xóa. Generics chỉ được biết tại thời gian biên dịch.

Rất tiếc, không có cách nào để GSON biết lớp nào cần deserialize, sử dụng sự phản chiếu.

+0

Hmm, được rồi. Có cách nào để vượt qua điều này không? Tôi đoán tôi đang mở cho một cấu trúc lớp thay thế, miễn là tôi không phải tạo một lớp mới cho mỗi cặp Executor. – Zar

2

Bạn chưa gọi response.execute(); sau Executor<Language> executor = new Executor<Language>(); tuyên bố này. Bạn không thể sử dụng các Generics java ở đây, nhưng bạn có thể có được hiệu ứng tương tự với đoạn mã sau.

Response.java

import java.io.Serializable; 
import java.util.List; 

/** 
* 
* @author visruth 
*/ 
public class Response<T> implements Serializable { 

    private List<T> data = null; 

    public List<T> getData() { 
     return this.data; 
    } 

    public void setData(List<T> data) { 
     this.data = data; 
    } 

} 

Language.java

import java.io.Serializable; 

/** 
* 
* @author visruth 
*/ 
public class Language implements Serializable { 
    private String alias; 
    private String label; 

    public String getAlias() { 
     return alias; 
    } 

    public void setAlias(String alias) { 
     this.alias = alias; 
    } 

    public String getLabel() { 
     return label; 
    } 

    public void setLabel(String label) { 
     this.label = label; 
    } 

} 

Cuối cùng, Executor.java

import com.google.gson.Gson; 
import java.util.*; 

/** 
* 
* @author visruth 
*/ 
public class Executor<T> { 
private Response<T> response; 

    public Response<T> getResponse() { 
     return response; 
    } 

    /** 
    * @param args the command line arguments 
    */ 
    public void executor() { 
     //sample data 
     Response<Language> response=new Response<Language>(); 
     Language lan1=new Language(); 
     lan1.setAlias("alias1"); 
     lan1.setLabel("label1"); 
     Language lan2=new Language(); 
     lan2.setAlias("alias2"); 
     lan2.setLabel("label2"); 
     List<Language> listOfLangauges=new ArrayList<Language>(); 
     listOfLangauges.add(lan1); 
     listOfLangauges.add(lan2); 
     response.setData(listOfLangauges); 
     Gson gson=new Gson(); 
     String json = gson.toJson(response); 

     System.out.println(json); 
     Response<Language> jsonResponse = gson.fromJson(json, Response.class); 
     List list=jsonResponse.getData(); 

     List<Language> langs=new ArrayList<Language>();    
     for(int i=0; i<list.size(); i++) {    
     Language lan=gson.fromJson(list.get(i).toString(), Language.class); 
     langs.add(lan); 
     //System.out.println(lan.getAlias()); 
     } 
     Response<Language> responseMade=new Response<Language>(); 
     responseMade.setData(langs); 
     this.response=(Response<T>) responseMade; 

    } 
} 

Bạn có thể kiểm tra nó như sau

Executor<Language> executor = new Executor<Language>(); 
executor.executor(); 
List<Language> data = executor.getResponse().getData(); 
for(Language langu: data) { 
    System.out.println(langu.getAlias()); 
    System.out.println(langu.getLabel()); 
} 
+0

Cảm ơn rất nhiều câu trả lời của bạn, nó đã cho tôi một số ý tưởng tốt (hy vọng)! – Zar