我有 2 个类,一个具有 int 值和一个 String 值的枚举,以及一个常规类。
枚举类:
public enum LettersEnum{
A(0, “Letter A”),
B(1, “Letter B”),
C(2, “Letter C”);
private final id;
private final letter;
LettersEnum(int id, String letter) {
this.id = id;
this.letter = letter;
}
public int getId() {
return this.id;
}
public String getLetter() {
return this.letter;
}
}
普通类:
public class LettersClass {
private LettersEnum letter = LettersEnum.A;
public void setLetter(LettersEnum letter) {
this.letter = letter;
}
public LettersEnum getLetter() {
return this.letter;
}
}
我有一个具有客户端和服务器端的视频游戏。在两端,A
是默认字母。如果我在客户端将 A
更改为 B
(反之亦然),它不会更改服务器端的字母,除非我运行一个将整个枚举编码为的数据包单一数据类型,并在服务器端解码回枚举。
问题是,我该怎么做?我猜是 toString()
,但是我如何将每个值转换回正确的数据类型?
最佳答案
这称为序列化:将任何随机信息单元(在本例中为枚举值之一)转换为可序列化数据(通常为字节,但取决于底层传输/存储机制,有时你必须有字符)。
有很多方法可以完成这项工作。特别是对于枚举,一种特别明显的方法是序列化序数或名称。或者,您可以手动管理自己的序列化“ key ”。
序数词
“序数”只是枚举定义中的索引位置。在您的例子中,A
的序数是 0,因为 A
是您列出的第一个。 (不是因为 id
字段包含 0,这只是巧合)。
通过序号进行序列化的一个主要问题是,如果您重新排序枚举值或在除末尾之外的任何位置插入新值,一切都会中断。如果您这样做,您绝对应该在代码中添加一条注释,表明永远不能弄乱现有值,您只能在列表末尾添加值。
序数的优点是发送和接收都很简单,而且效率很高:它只是一个 int; 32 位。
将枚举值转换为序数
LettersEnum letter = LettersEnum.A;
int o = letter.ordinal();
assert o == 0;
将序数转换回枚举
int o = 0;
LettersEnum letter = LettersEnum.values()[o];
assert letter == LettersEnum.A;
values()
方法很“神奇”,所有枚举都只有这些,你不需要编写它。
名称
您还可以选择通过枚举名称进行序列化;例如,在本例中为 A
。这确实允许您在代码中重新组织枚举,但如果这样做,您将无法重命名任何枚举。
第二个问题是序列化字符串稍微复杂一些;为了“安全”地做到这一点,您需要首先将字符串转换为字节(因为绝大多数传输/存储机制都以字节为单位),并且您需要确保在执行此操作时使用 UTF-8 编码,否则您当您遇到问题时,例如开始添加字母 ß,它是一个有效的 java 标识符(enum Foo { A, B, ß; }
是有效的 java!)。
将枚举值转换为名称,然后将名称转换为字节
LettersEnum letter = LettersEnum.A;
String name = letter.name();
assert name.equals("A");
byte[] data = name.getBytes(StandardCharsets.UTF_8);
assert data.length == 1 && data[0] == 'A';
将字节转换为字符串,然后转换为枚举
byte[] data = new byte[] {'A'};
String name = new String(data, StandardCharsets.UTF_8);
LettersEnum v = LettersEnum.valueOf(name);
assert v == LettersEnum.A;
name()
和 valueOf()
方法都存在,您不必编写它们。
您自己的 ID
上述两种策略都会默默地“链接”方面,我们 Java 程序员通常认为这些方面不会对程序的运行时行为产生任何影响(为基于序数的序列化编写字段的顺序,以及基于名称的值名称),因此您或开发团队的其他成员可能会重命名事物,但没有意识到这意味着旧代码现在无法再检索/接收由旧代码“保存”或“发送”的任何内容代码。一种简单的方法是使用您自己的 ID。如果您只是对现有概念进行建模(例如,某些第三方 API 已经定义了一堆具有整数值的命令,但没有人喜欢 .sendCmd(182, ..)
,那么这也是明智的, .sendCmd(Command.LIST_USERS)
更好。
您已经有了这个 - id
字段。
无论在哪个方向,都没有对此的内置支持;你必须自己写。但这并不是特别复杂:
public enum LettersEnum{
A(0, “A”),
B(1, “B”),
C(2, “C”);
private final id;
private final letter;
LettersEnum(int id, String letter) {
this.id = id;
this.letter = letter;
}
public int getId() {
return this.id;
}
public String getLetter() {
return this.letter;
}
private static final Map<Integer, LettersEnum> ID_TO_LETTER;
static {
var map = new HashMap<Integer, LettersEnum>();
for (LettersEnum letter : values()) map.put(letter.getId(), letter);
ID_TO_LETTER = Collections.unmodifiableMap(map);
}
public static LettersEnum ofId(int id) {
LettersEnum v = ID_TO_LETTER.get(id);
if (v != null) return v;
throw new IllegalArgumentException(id + " is not a known LettersEnum id");
}
}
并使用:
LettersEnum letter = LettersEnum.A;
int i = letter.getId();
// store i someplace...
// retrieve o someplace...
int o = 0;
LettersEnum letter = LettersEnum.ofId(o);
assert letter == LettersEnum.A;
关于java - 如何对具有多个值的 Enum 进行编码和解码,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69208171/