私は学校のプロジェクト用にJavaWebアプリケーションを開発しており、MVCアーキテクチャパターンにできる限り従っています。これを実現するために、JavaBeanクラスのセットを作成し、Gsonを使用してこれらのインスタンスをシリアル化および逆シリアル化します。私が抱えている問題は、Javaで継承とジェネリックがどのように機能するかを完全に理解していないことを示しているので、この質問でそれらの議論に光を当てたいと思います。
私は2つの抽象クラス持つItem
とItemVariant
それぞれ二つのBeanクラスごと、によって拡張され、CatalogItem
、ArchivedItem
とCatalogItemVariant
、ArchivedItemVariant
。のインスタンスはItem
のコレクションを参照できItemVariant
、その逆も同様です。のインスタンスはItemVariant
その親アイテムを参照できます(これによりシリアル化プロセスでループが発生する可能性があることはわかっていますが、これは私が経験している問題ではありません)。したがって、理想的には、のインスタンスはCatalogItem
常にコレクションCatalogItemVariant
を参照し、のインスタンスはCatalogItemVariant
常にの1つを参照する必要がCatalogItem
あり、同じことがArchivedItem
とにも当てはまりますArchivedItemVariant
。しかし、私はこの関係を強制することに興味がありません。たとえば、のインスタンスがのインスタンスをCatalogItemVariant
参照できるようにするArchivedItem
ことは許可されています。
現在、これら2つの抽象クラスのコードは次のようになっています。
public abstract class Item implements Serializable {
...
protected Collection<ItemVariant> variants;
...
public <T extends ItemVariant> Collection<T> getVariants() {
return (Collection<T>) variants;
}
...
public <T extends ItemVariant> void setVariants(Collection<T> variants) {
this.variants = (Collection<ItemVariant>) variants;
}
}
public abstract class ItemVariant implements Serializable {
...
protected Item parentItem;
...
public <T extends Item> T getParentItem() {
return (T) parentItem;
}
...
public <T extends Item> void setParentItem(T parentItem) {
this.parentItem = parentItem;
}
...
}
これはチェックされていないキャスト警告を生成しており、おそらく最も洗練された解決策ではないことを私は知っています。しかし、これは私が経験している大きな問題ではありません。
これら2つを拡張するBeanクラスは、ゲッターとセッターとともにそれぞれ2つのプロパティを追加するだけであり、これらの具象クラスのインスタンスは、アプリケーション全体で実際に使用されるものです。ここで本当の問題が発生しますCatalogItemVariant
。例として、のインスタンスに逆シリアル化する必要がある次のJSON文字列について考えてみます。
{"parentItem":{"name":"Sample name","category":"Sample category","color":"Sample color"},"size":"Sample size"}
理想的にparentItem
は、のインスタンスに逆シリアル化する必要がありますがCatalogItem
、これらのクラスが現在設計されている方法を考えると、Gsonはのインスタンスを作成しようとしますItem
。これは抽象的であるため、次の例外が発生します。
java.lang.RuntimeException: Failed to invoke public model.transfer.catalog.Item() with no args
この問題を回避するには、私は2つの方法を作ることを考えsetVariants
中Item
とsetParentItem
におけるItemVariant
ので、2オーバーライドしてそれらのサブクラスを強制的、抽象的に。このようなもの:
public abstract class ItemVariant implements Serializable {
...
protected Item parentItem;
...
public abstract <T extends Item> void setParentItem(T parentItem);
...
}
public class CatalogItemVariant extends ItemVariant {
...
@Override
public void setParentItem(CatalogItem parentItem) {
this.parentItem = parentItem;
}
...
}
ただし、タイプCatalogItem
が一致せずT
、@Override
注釈が無効になるため、これは機能しません。
この回避策は、正しいクラス(使用して2つの異なるオブジェクトにそれらの両方をデシリアライズ、オブジェクト二つの別々のJSON文字列、項目バリアント用とその親項目のための1つをデシリアライズサーブレットに送信されるCatalogItem
最初のおよびCatalogItemVariant
のための2番目)、次にを使用しparentItem
て新しいCatalogItemVariant
インスタンスの属性を手動で設定しますsetParentItem()
。もちろん、このソリューションを適用する場合、アイテムバリアントのJSON文字列はそのparentItem
フィールドを見逃す必要があります。これに対するより良い解決策はありますか?もしそうなら、この問題の影響を受けるクラスとメソッドをどのように再設計する必要がありますか?
編集:実際のコードを提供するように求められたので、これが私が使用しているクラスの簡略化されたバージョンです:
public abstract class Item implements Serializable {
private static final long serialVersionUID = -6170402744115745097L;
protected String name;
protected String category;
protected String color;
protected Collection<ItemVariant> variants;
public String getName() {
return this.name;
}
public String getCategory() {
return this.category;
}
public String getColor() {
return this.color;
}
public <T extends ItemVariant> Collection<T> getVariants() {
return (Collection<T>) variants;
}
public void setName(String name) {
this.name = name;
}
public void setCategory(String category) {
this.category = category;
}
public void setColor(String color) {
this.color = color;
}
public <T extends ItemVariant> void setVariants(Collection<T> variants) {
this.variants = (Collection<ItemVariant>) variants;
}
}
public abstract class ItemVariant implements Serializable {
private static final long serialVersionUID = 4245549003952140725L;
protected Item parentItem;
protected String size;
public <T extends Item> T getParentItem() {
return (T) parentItem;
}
public String getSize() {
return size;
}
public <T extends Item> void setParentItem(T parentItem) {
this.parentItem = parentItem;
}
public void setSize(String size) {
this.size = size;
}
}
public class CatalogItem extends Item implements Serializable {
private static final long serialVersionUID = 993286101083002293L;
protected String description;
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
public class CatalogItemVariant extends ItemVariant implements Serializable {
private static final long serialVersionUID = -2266390484210707778L;
protected Integer availability;
public Integer getAvailability() {
return availability;
}
public void setAvailability(Integer availability) {
this.availability = availability;
}
}
私が経験したエラーは、次のコードスニペットを実行することでシミュレートでき、最後の行にスローされます。
Gson gson = new GsonBuilder().create();
CatalogItem catalogItem;
CatalogItemVariant catalogItemVariant;
CatalogItemVariant deserializedCatalogItemVariant;
catalogItem = new CatalogItem();
catalogItem.setName("Sample name");
catalogItem.setCategory("Sample category");
catalogItem.setColor("Sample color");
catalogItemVariant = new CatalogItemVariant();
catalogItemVariant.setSize("Sample size");
catalogItemVariant.setParentItem(catalogItem);
String serializedCatalogItemVariant = gson.toJson(catalogItemVariant); // Builds a JSON string of the object to serialize
System.out.println(serializedCatalogItemVariant); // Prints the JSON string of the serialized object which is fed for deserialization in the next instruction
deserializedCatalogItemVariant = gson.fromJson(serializedCatalogItemVariant, CatalogItemVariant.class);
明確にするために、これらのクラスの設計方法が原因でエラーが発生することを完全に理解しており、プログラムの動作が異なるとは思わない。ジェネリックスと継承を使用してそれらのクラスを再設計できる方法があるかどうか、もしそうなら、その方法は何かを理解したいだけです。
この質問に遭遇した人は、この非常に単純な解決策を見てください!
この記事はインターネットから収集されたものであり、転載の際にはソースを示してください。
侵害の場合は、連絡してください[email protected]
コメントを追加