java - 如何对具有多个值的 Enum 进行编码和解码

标签 java enums

我有 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/

相关文章:

java - 使用 URLConnection 加载 xml 内容时出现垃圾

c# - 将枚举视为通用吗?

c# - 使用 Flags 枚举时如何减少 ASP.NET MVC View 中的代码重复

Java CLI 应用程序 - 防止输入向上滚动

java - catelina.properties 中的 ${catalina.base} 未被 CATALINA_BASE 替换

java - 重新部署spring boot服务不重启?

asp.net-mvc-3 - 当我只有一个简短的显示名称时如何获取枚举?

enums - 如何在 Yaskawa MotionWorks IEC 3 中声明数据类型 ENUM?

enums - 将字符串转换为枚举

java - 使用两个单例时如何避免递归?