Я занимаюсь разработкой веб-приложения на Java для школьного проекта и следую шаблону архитектуры MVC настолько хорошо, насколько могу;Для этого я создал набор классов JavaBean, а для сериализации и десериализации их экземпляров я использую Gson.Возникшая у меня проблема показывает, что я не до конца понял, как наследование и дженерики работают в Java, поэтому я надеюсь пролить свет на эти аргументы с помощью этого вопроса.
У меня есть два абстрактных класса Item
иItemVariant
, которые расширяются двумя классами бинов каждый, соответственно CatalogItem
, ArchivedItem
и CatalogItemVariant
, ArchivedItemVariant
.Istiance Item
может ссылаться на коллекцию ItemVariant
, и наоборот, istance ItemVariant
может ссылаться на свой родительский элемент (я знаю, что это может вызвать циклы в процессе сериализации, но это не проблемаЯ испытываю).Поэтому в идеале экземпляр CatalogItem
всегда должен ссылаться на коллекцию CatalogItemVariant
, а экземпляр CatalogItemVariant
всегда должен ссылаться на один из CatalogItem
, и то же самое относится к ArchivedItem
и ArchivedItemVariant
.Однако я не заинтересован в навязывании этих отношений;например, допускается, чтобы экземпляр CatalogItemVariant
ссылался на экземпляр ArchivedItem
.
В настоящее время код для этих двух абстрактных классов выглядит следующим образом:
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;
}
...
}
Этовыдача непроверенных кастовых предупреждений, и я знаю, что это, вероятно, не самое элегантное решение;однако, это не главная проблема, с которой я сталкиваюсь.
Классы бинов, которые расширяют эти два, просто добавляют свойства пары каждый вместе со своими получателями и установщиками, и экземпляры этих конкретных классов являются темифактически используется во всем приложении.Теперь возникает реальная проблема: рассмотрим в качестве примера следующую строку JSON, которая должна быть десериализована в экземпляр CatalogItemVariant
.
{"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
Чтобы обойти это, я подумалсделать два метода setVariants
в Item
и setParentItem
в ItemVariant
абстрактными, заставив их два подкласса переопределить их.Примерно так:
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
недействительной.
Обходной путь этоотправлять сервлету, который десериализует объект две отдельные строки JSON, одну для варианта элемента и одну для его родительского элемента, десериализовать их оба в два разных объекта, используя правильные классы (CatalogItem
для первого и CatalogItemVariant
для второго), а затем вручную установите атрибут 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);
Дляпояснение, я полностью понимаю, что ошибка вызвана тем, как эти классы разработаны, и я не ожидаю, что программа будет вести себя иначе;Я просто хочу понять, есть ли способ использовать дженерики и наследование для перепроектирования этих классов, и если да, то каков этот путь.