GSON LinkedHashMap 的通用 Class
Generic Class for a GSON LinkedHashMap
几个月来我一直在研究这个解决方案,我得出的结论是没有干净的方法来实现我想要实现的目标。我觉得好像我在多态性方面的教育让我失望了,所以我来 Whosebug 征求第二意见。对不起,如果这看起来很长而且令人费解。在过去的几个月里,这一直是我的大脑,而在这一点上,我已经失去了想法。我希望有人可以看一下,看看我是否可以通过其他方式避免所有这些混乱。
我想要实现的是两个通用的 classes:一个可以表示任何“可保存”对象,一个可以表示可保存对象列表(或者我称之为“存储”的对象) ).可保存对象可以使用 GSON 保存自己,商店也可以使用 GSON 将自己保存到 JSON 文件中。不同之处在于,可保存对象通常表示可以保存的任何 GSON 对象,而商店则从可保存对象扩展为通过 ID 成为对象的可保存哈希映射。
我正在寻找的示例输出如下:
假设我有一个带有 uuid 字符串字段和名称字符串字段的对象。我希望能够为这些对象创建一个 Store,它是一个 LinkedHashMap,而且还扩展一个 Saveable 以允许将对象保存为:
test.json
{"dbf39199209e466ebed0061a3491ed9e":{"uuid":"dbf39199209e466ebed0061a3491ed9e","name":"Example Name"}}
我还希望能够通过 Store 的加载方法将此 JSON 加载回对象。
示例代码用法如下:
Store<User> users = new Store<>();
users.load();
users.add(new User("dbf39199209e466ebed0061a3491ed9e", "Example Name"));
users.save();
我的尝试
可储蓄
我希望“可保存”对象能够执行的操作如下:提供非参数化的保存方法和非参数化的加载方法。可保存对象表示可以通过 GSON 保存的任何对象。它包含两个字段:一个 Gson gson 对象和一个 Path 位置。我在我的可保存对象的构造函数中提供了这些。然后我想提供两个方法:一个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 class 用户仍然必须通过通用程序才能加载:
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();
}
}
}
商店
商店是可保存物品的延伸。 store 是一个 LinkedHashMap,它将快速轻松地将其中的所有对象保存为 GSON 中的地图。不幸的是,我什至不确定从哪里开始。我不能扩展两个对象(这两个是 LinkedHashMap 和一个 Saveable),但我也不能使用 Saveable 对象的接口。
我之前尝试过以下使用 IStorable 和 ISaveable classes 作为抽象 Saveable 的替代方案 class 我已经在上面向您展示了,但这导致了另一个非常丑陋和非我的问题的可靠解决方案。
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;
}
}
}
我Saveable.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();
在我收到任何评论说我不应该在 Whosebug 上发布这个之前:如果不在这里,还有什么地方?请帮我指明正确的方向,我不想被蒙在鼓里。
谢谢,如果有人能够提供帮助,如果不能,我理解。无论如何,这都不是最简单的问题。
我非常接近正确的解决方案,但我的逻辑不一致。
固定加载方法如下:
default <T extends ISaveable> T load() throws IOException {
if (!Files.exists(getLocation())) {
return save();
} else {
InstanceCreator<?> creator = type -> (T) this;
Gson newGson = getGson().newBuilder().registerTypeAdapter(getType(), creator).create();
return newGson.fromJson(Files.newBufferedReader(getLocation()), getType());
}
}
不是试图防止类型擦除,也不是每次我们调用该方法时都传递class,我们只是...在构造函数中传递它.真的就这么简单。我不关心通过构造函数发送类型,只要 .load() 和 .save() 不会导致数百行重复代码。
我不敢相信我一直都离解决方案这么近。这对我来说太简单了。猜猜这就是编程的生命,对吧?
这是完整的 class,我认为它作为一个名为 ISaveable.java:
的界面更好
public interface ISaveable {
Type getType();
Gson getGson();
Path getLocation();
/**
* Saves this object.
*
* @param <T> The extended object to cast to.
* @return The object after having been saved.
* @throws IOException Thrown if there was an exception while trying to save.
*/
@SuppressWarnings("unchecked")
default <T extends ISaveable> T save() throws IOException {
Path location = getLocation().toAbsolutePath();
if (location.getParent() != null) {
Files.createDirectories(location.getParent());
}
Files.write(getLocation(), getGson().toJson(this).getBytes(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, LinkOption.NOFOLLOW_LINKS);
return (T) this;
}
/**
* Loads this object.
*
* @param <T> The extended object to cast to.
* @return The object after loading the new values.
* @throws IOException Thrown if there was an exception while trying to load.
*/
@SuppressWarnings("unchecked")
default <T extends ISaveable> T load() throws IOException {
if (!Files.exists(getLocation())) {
return save();
} else {
InstanceCreator<?> creator = type -> (T) this;
Gson newGson = getGson().newBuilder().registerTypeAdapter(getType(), creator).create();
return newGson.fromJson(Files.newBufferedReader(getLocation()), getType());
}
}
}
示例实现:
public class ExampleSaveable implements ISaveable {
private boolean testBoolean = false;
private String myString;
public ExampleSaveable(String myString) {
this.myString = myString;
}
@Override
public Gson getGson() {
return new Gson();
}
@Override
public Type getType() {
return TypeToken.get(ExampleSaveable.class).getType();
}
@Override
public Path getLocation() {
return Path.of("test.json");
}
}
示例用法如下:
ExampleSaveable saveable = new ExampleSaveable("My Data!").load();
saveable.myString = "This is a replacement string!";
saveable.save();
在第一个 运行 上,输出是“我的数据!”,在第二个上,输出是“这是一个替换字符串!”
对应的输出JSON为:
{"testBoolean":false,"myString":"This is a replacement string!"}
这使我能够随后扩展 class 以创建我的商店。
IStorable.java
public interface IStorable {
String getUuid();
}
Store.java
public class Store<T extends IStorable> extends LinkedHashMap<String, T> implements ISaveable {
// GSON & Location
private transient Gson gson;
private transient Path location;
private transient Type type;
/**
* Constructs a new store.
*
* @param gson The gson to use for saving and loading.
* @param location The location of the JSON file.
* @param generic The generic that this instance of this class is using (due to type erasure).
*/
public Store(Gson gson, Path location, Class<T> generic) {
this.gson = gson;
this.location = location;
this.type = TypeToken.getParameterized(Store.class, generic).getType();
}
// Putting
public Store<T> put(T value) {
this.put(value.getUuid(), value);
return this;
}
public Store<T> putAll(T... values) {
for (T value : values) {
this.put(value.getUuid(), value);
}
return this;
}
// Replacing
public Store<T> replace(T value) {
this.replace(value.getUuid(), value);
return this;
}
// Removing
public Store<T> remove(T value) {
this.remove(value.getUuid());
return this;
}
// Implement ISaveable
@Override
public Gson getGson() {
return gson;
}
@Override
public Path getLocation() {
return location;
}
@Override
public Type getType() {
return type;
}
// Setters
public void setLocation(Path location) {
this.location = location;
}
}
几个月来我一直在研究这个解决方案,我得出的结论是没有干净的方法来实现我想要实现的目标。我觉得好像我在多态性方面的教育让我失望了,所以我来 Whosebug 征求第二意见。对不起,如果这看起来很长而且令人费解。在过去的几个月里,这一直是我的大脑,而在这一点上,我已经失去了想法。我希望有人可以看一下,看看我是否可以通过其他方式避免所有这些混乱。
我想要实现的是两个通用的 classes:一个可以表示任何“可保存”对象,一个可以表示可保存对象列表(或者我称之为“存储”的对象) ).可保存对象可以使用 GSON 保存自己,商店也可以使用 GSON 将自己保存到 JSON 文件中。不同之处在于,可保存对象通常表示可以保存的任何 GSON 对象,而商店则从可保存对象扩展为通过 ID 成为对象的可保存哈希映射。
我正在寻找的示例输出如下:
假设我有一个带有 uuid 字符串字段和名称字符串字段的对象。我希望能够为这些对象创建一个 Store,它是一个 LinkedHashMap,而且还扩展一个 Saveable 以允许将对象保存为:
test.json
{"dbf39199209e466ebed0061a3491ed9e":{"uuid":"dbf39199209e466ebed0061a3491ed9e","name":"Example Name"}}
我还希望能够通过 Store 的加载方法将此 JSON 加载回对象。
示例代码用法如下:
Store<User> users = new Store<>();
users.load();
users.add(new User("dbf39199209e466ebed0061a3491ed9e", "Example Name"));
users.save();
我的尝试
可储蓄
我希望“可保存”对象能够执行的操作如下:提供非参数化的保存方法和非参数化的加载方法。可保存对象表示可以通过 GSON 保存的任何对象。它包含两个字段:一个 Gson gson 对象和一个 Path 位置。我在我的可保存对象的构造函数中提供了这些。然后我想提供两个方法:一个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 class 用户仍然必须通过通用程序才能加载:
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();
}
}
}
商店
商店是可保存物品的延伸。 store 是一个 LinkedHashMap,它将快速轻松地将其中的所有对象保存为 GSON 中的地图。不幸的是,我什至不确定从哪里开始。我不能扩展两个对象(这两个是 LinkedHashMap
我之前尝试过以下使用 IStorable 和 ISaveable classes 作为抽象 Saveable 的替代方案 class 我已经在上面向您展示了,但这导致了另一个非常丑陋和非我的问题的可靠解决方案。
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;
}
}
}
我Saveable.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();
在我收到任何评论说我不应该在 Whosebug 上发布这个之前:如果不在这里,还有什么地方?请帮我指明正确的方向,我不想被蒙在鼓里。
谢谢,如果有人能够提供帮助,如果不能,我理解。无论如何,这都不是最简单的问题。
我非常接近正确的解决方案,但我的逻辑不一致。
固定加载方法如下:
default <T extends ISaveable> T load() throws IOException {
if (!Files.exists(getLocation())) {
return save();
} else {
InstanceCreator<?> creator = type -> (T) this;
Gson newGson = getGson().newBuilder().registerTypeAdapter(getType(), creator).create();
return newGson.fromJson(Files.newBufferedReader(getLocation()), getType());
}
}
不是试图防止类型擦除,也不是每次我们调用该方法时都传递class,我们只是...在构造函数中传递它.真的就这么简单。我不关心通过构造函数发送类型,只要 .load() 和 .save() 不会导致数百行重复代码。
我不敢相信我一直都离解决方案这么近。这对我来说太简单了。猜猜这就是编程的生命,对吧?
这是完整的 class,我认为它作为一个名为 ISaveable.java:
的界面更好public interface ISaveable {
Type getType();
Gson getGson();
Path getLocation();
/**
* Saves this object.
*
* @param <T> The extended object to cast to.
* @return The object after having been saved.
* @throws IOException Thrown if there was an exception while trying to save.
*/
@SuppressWarnings("unchecked")
default <T extends ISaveable> T save() throws IOException {
Path location = getLocation().toAbsolutePath();
if (location.getParent() != null) {
Files.createDirectories(location.getParent());
}
Files.write(getLocation(), getGson().toJson(this).getBytes(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, LinkOption.NOFOLLOW_LINKS);
return (T) this;
}
/**
* Loads this object.
*
* @param <T> The extended object to cast to.
* @return The object after loading the new values.
* @throws IOException Thrown if there was an exception while trying to load.
*/
@SuppressWarnings("unchecked")
default <T extends ISaveable> T load() throws IOException {
if (!Files.exists(getLocation())) {
return save();
} else {
InstanceCreator<?> creator = type -> (T) this;
Gson newGson = getGson().newBuilder().registerTypeAdapter(getType(), creator).create();
return newGson.fromJson(Files.newBufferedReader(getLocation()), getType());
}
}
}
示例实现:
public class ExampleSaveable implements ISaveable {
private boolean testBoolean = false;
private String myString;
public ExampleSaveable(String myString) {
this.myString = myString;
}
@Override
public Gson getGson() {
return new Gson();
}
@Override
public Type getType() {
return TypeToken.get(ExampleSaveable.class).getType();
}
@Override
public Path getLocation() {
return Path.of("test.json");
}
}
示例用法如下:
ExampleSaveable saveable = new ExampleSaveable("My Data!").load();
saveable.myString = "This is a replacement string!";
saveable.save();
在第一个 运行 上,输出是“我的数据!”,在第二个上,输出是“这是一个替换字符串!”
对应的输出JSON为:
{"testBoolean":false,"myString":"This is a replacement string!"}
这使我能够随后扩展 class 以创建我的商店。
IStorable.java
public interface IStorable {
String getUuid();
}
Store.java
public class Store<T extends IStorable> extends LinkedHashMap<String, T> implements ISaveable {
// GSON & Location
private transient Gson gson;
private transient Path location;
private transient Type type;
/**
* Constructs a new store.
*
* @param gson The gson to use for saving and loading.
* @param location The location of the JSON file.
* @param generic The generic that this instance of this class is using (due to type erasure).
*/
public Store(Gson gson, Path location, Class<T> generic) {
this.gson = gson;
this.location = location;
this.type = TypeToken.getParameterized(Store.class, generic).getType();
}
// Putting
public Store<T> put(T value) {
this.put(value.getUuid(), value);
return this;
}
public Store<T> putAll(T... values) {
for (T value : values) {
this.put(value.getUuid(), value);
}
return this;
}
// Replacing
public Store<T> replace(T value) {
this.replace(value.getUuid(), value);
return this;
}
// Removing
public Store<T> remove(T value) {
this.remove(value.getUuid());
return this;
}
// Implement ISaveable
@Override
public Gson getGson() {
return gson;
}
@Override
public Path getLocation() {
return location;
}
@Override
public Type getType() {
return type;
}
// Setters
public void setLocation(Path location) {
this.location = location;
}
}