我已经为这个解决方案工作了几个月,我得出的结论是,没有一种干净的方法来实现我正在尝试实现的目标。我觉得好像我在多态性方面的教育失败了,所以我来StackOverflow寻求第二个意见。如果这看起来又长又复杂的话,我很抱歉。这是我过去几个月的想法,现在我已经没有主意了。我我希望有人能看一看,看看我能不能用别的方法来避免这一切。
我尝试实现的是两个泛型类:一个可以表示任何“可保存”对象,另一个可以表示可保存对象的列表可保存对象可以使用GSON保存自身,存储也可以使用GSON将自身保存到JSON文件中,区别在于可保存对象一般表示任何可以保存的GSON对象,而存储则从可保存扩展为通过ID的对象的可保存散列Map。
我正在寻找的示例输出如下所示:
假设我有一个包含uuid字符串字段和name字符串字段的对象,我希望能够为这些对象创建一个Store,即一个LinkedHashMap,同时还扩展了一个Saveable,允许将这些对象保存为:
test.json
{"dbf39199209e466ebed0061a3491ed9e":{"uuid":"dbf39199209e466ebed0061a3491ed9e","name":"Example Name"}}
我还希望能够通过Store的load方法将此JSON加载回对象中。
代码用法示例如下:
Store<User> users = new Store<>();
users.load();
users.add(new User("dbf39199209e466ebed0061a3491ed9e", "Example Name"));
users.save();
我的尝试次数
可储存项目
我期望“可保存”对象能够做的事情如下:提供一个无参数的保存方法和一个无参数的加载方法。可保存对象表示任何可以通过GSON保存的对象。它包含两个字段:一个gson对象和一个路径位置。我在我的可保存对象的构造函数中提供了这些。然后我想提供两个方法:一个Saveable#save()
方法和一个Saveable#load()
方法(或者一个静态的Saveable#load()
方法,我不在乎)。使用Saveable对象的方法是将它扩展(因此它是抽象的)到另一个表示某个东西的对象,比如TestSaveable
,然后用法如下:
TestSaveable saveable = new TestSaveable(8);
saveable.save(); // Saves data
saveable.setData(4);
saveable = saveable.load(); // Loads old data
我还希望一个可保存的对象能够处理泛型,比如整数(想想最后一个例子,但是使用的是整数泛型)。
我的实现尝试如下:
public abstract class Saveable {
private transient Gson gson;
private transient Path location;
public Saveable(Gson gson, Path location) {
this.gson = gson;
this.location = location;
}
@SuppressWarnings("unchecked")
public <T extends Saveable> T save() throws IOException {
if (location.getParent() != null) {
Files.createDirectories(location.getParent());
}
Files.write(location, gson.toJson(this).getBytes(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, LinkOption.NOFOLLOW_LINKS);
return (T) this;
}
protected <T extends Saveable> T load(Class<T> clazz, @NotNull Class<?>... generics) throws IOException {
if (!Files.exists(location)) {
return this.save();
} else {
InstanceCreator<Saveable> creator = type -> this;
Type type = TypeToken.getParameterized(clazz, generics).getType();
Gson newGson = gson.newBuilder().registerTypeAdapter(type, creator).create();
return newGson.fromJson(Files.newBufferedReader(location), type);
}
}
}
不幸的是,这个尝试在我的目标中失败了,因为在创建我的TestSaveable类时,用户仍然必须传递泛型以便加载:
public class TestSaveable<T> extends Saveable {
public boolean testBool = false;
public T value;
public TestSaveable(T value) {
super(new Gson(), Path.of("test.json"));
this.value = value;
}
public final TestSaveable<T> load(Class<T> generic) throws IOException {
return super.load(TestSaveable.class, generic);
}
}
然而,通过这样做,我得到了一个相当干净的实现,除了很少或根本没有类型检查,并不断地为它添加抑制:
public class Test {
public static void main(String[] args) {
try {
TestSaveable<Integer> storeB4 = new TestSaveable<>(5).save();
storeB4.value = 10;
TestSaveable<Integer> store = storeB4.load(Integer.class);
System.out.println("STORE: " + store);
} catch (Exception e) {
e.printStackTrace();
}
}
}
家商店
存储是可保存对象的扩展。存储是一个LinkedHashMap,它可以快速方便地将其中的所有对象保存为GSON中的Map。不幸的是,我甚至不知道从哪里开始。我不能扩展两个对象(这两个对象是一个LinkedHashMap〈String,T〉和一个Saveable),但我也不能为Saveable对象使用接口。
我以前尝试过使用ISorable和ISaveable类作为上面所示的抽象Saveable类的替代品,但这导致了另一个非常丑陋和不健壮的问题解决方案。
Saveable.java
public class Saveable {
// Suppress default constructor
private Saveable() {}
// Save a class to the specified location using the specified gson
public static <T extends ISaveable> T save(T instance) throws IOException {
Files.createDirectories(instance.getLocation().getParent());
Files.write(instance.getLocation(), instance.getGson().toJson(instance).getBytes(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, LinkOption.NOFOLLOW_LINKS);
return instance;
}
// Load a file from the specified location using the specified gson and cast it to the specified class using the specified generic
public static <T extends ISaveable> ISaveable load(Path location, Gson gson, Class<T> clazz, Class<?> genericClazz) throws IOException {
if (!Files.exists(location)) {
return null;
} else {
TypeToken<?> type = genericClazz == null ? TypeToken.get(clazz) : TypeToken.getParameterized(clazz, genericClazz);
ISaveable saveable = gson.fromJson(Files.newBufferedReader(location), type.getType());
saveable.setGson(gson);
saveable.setLocation(location);
return saveable;
}
}
}
ISaveable.java
public interface ISaveable {
// Gson
Gson getGson();
void setGson(Gson gson);
// Location
Path getLocation();
void setLocation(Path location);
}
IStorable.java
public interface IStoreable {
String getUuid();
}
Store.java
public class Store<T extends IStoreable> extends LinkedHashMap<String, T> implements ISaveable {
private transient Path location;
private transient Gson gson;
public Store(Path location, Gson gson) {
this.location = location;
this.gson = gson;
}
public Store() {
this.location = null;
this.gson = null;
}
public Store<T> put(T value) {
this.put(value.getUuid(), value);
return this;
}
public Store<T> remove(T value) {
this.remove(value.getUuid());
return this;
}
public Store<T> save() throws IOException {
return Saveable.save(this);
}
@SuppressWarnings("unchecked")
public static <T extends IStoreable> Store<T> load(Path location, Gson gson, Class<T> genericClazz) throws IOException {
ISaveable saveable = Saveable.load(location, gson, Store.class, genericClazz);
if (saveable == null) {
return new Store<T>(location, gson).save();
} else {
return (Store<T>) saveable;
}
}
}
这个解决方案几乎达到了我所期望的结果,但是在加载过程中很快就失败了,而且不是一个健壮的解决方案,不包括我现在肯定已经破坏的数百个Java实践:
Store<ExampleStoreable> store = Store.load(Paths.get("storetest.json"), new Gson(), ExampleStoreable.class);
store.put(new ExampleStoreable("Example Name"));
store.save();
在我得到任何评论说我不应该张贴在StackOverflow:如果不是这里,还有哪里?请帮我指出正确的方向,我不想被蒙在鼓里。
如果有人能帮上忙,我会表示感谢,如果没有的话,我会理解的。无论如何,这都不是一个简单的问题。
1条答案
按热度按时间whlutmcx1#
我非常接近正确的解决方案,但我的逻辑就是不符合。
固定荷载法如下:
我们没有试图 * 防止 * 类型擦除,也没有在每次调用方法时传递类,我们只是......在构造函数中传递它。真的就这么简单。我不在乎通过构造函数发送类型,只要.load()和.保存()不会导致数百行重复代码。
我真不敢相信我就差这么一点就能找到答案了。对我来说,这是多么简单的事情。我想这就是编程的生命,对吗?
下面是完整的类,我认为作为一个名为www.example.com的接口更好ISaveable.java:
实施示例:
下面是一个例子:
第一次运行时,输出为“My Data!",第二次运行时,输出为“This is a replacement string!”
相应的输出JSON为:
这允许我随后扩展该类以创建我的Store。
IStorable.java
Store.java