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