【问题标题】:Performance of Java enumsJava 枚举的性能
【发布时间】:2010-12-20 14:52:51
【问题描述】:

我正在考虑在我正在开发的 Java 游戏中使用 enum 类型来管理 i18n,但我很好奇在使用包含大量元素(我认为是数千个)的枚举时可能出现的性能问题。

实际上我正在尝试类似:

public enum Text {
  STRING1,
  STRING2,
  STRING3;

  public String text() {
    return text;
  }

  public String setText() {
    this.text = text;
  }
}

然后加载它们,我可以填写字段:

static
{
  Text.STRING1.setText("My localized string1"); 
  Text.STRING2.setText("My localized string2"); 
  Text.STRING3.setText("My localized string3"); 
}

当然,当我必须管理多种语言时,我会从文件中加载它们。

我要问的是

  • 是否为每个元素分配了一个对象(除了字符串)? (我想是的,因为枚举是用对象实现的)
  • 如何从枚举中检索正确的元素?它在编译时是静态的吗? (我的意思是当我在某个地方使用Text.STRING1.text())。所以它应该是恒定的复杂性,或者它们可能只是在编译阶段被替换..
  • 总的来说,这是一个好方法还是我应该期待其他方法?

谢谢

【问题讨论】:

    标签: java internationalization enums


    【解决方案1】:

    找到并改编了枚举和 ResourceBundle 的完美组合:

    public enum Text {
      YELL, SWEAR, BEG, GREET /* and more */ ;
    
      /** Resources for the default locale */
      private static final ResourceBundle res =
        ResourceBundle.getBundle("com.example.Messages");
    
      /** @return the locale-dependent message */
      public String toString() {
        return res.getString(name() + ".string");
      }
    }
    

    # File com/example/Messages.properties
    # default language (english) resources
    YELL.string=HEY!
    SWEAR.string=§$%&
    BEG.string=Pleeeeeease!
    GREET.string=Hello player!
    

    # File com/example/Messages_de.properties
    # german language resources
    YELL.string=HEY!
    SWEAR.string=%&$§
    BEG.string=Biiiiiitte!
    GREET.string=Hallo Spieler!
    

    【讨论】:

    • 既然你还在,那么 4.5 年后你目前对这种方法的看法是什么? :)
    【解决方案2】:

    使用java.util.ResourceBundle 类可能会更好。它旨在解决这个问题。

    回答您的问题:

    1. 是的,每个枚举值只有一个实例。
    2. 是的,查找枚举值的复杂度恒定。
    3. 并非如此。改变枚举的内容/行为有点违背了首先拥有枚举的目的。它们应该表示具有类型安全性的固定范围常量。你可以做这种事情,但这不是他们的设计目的。

    【讨论】:

    • 是的,但是,OTH,枚举并没有被设计来取代单例模式——但他们做到了;-) 我同意 (1) 和 (2) 但需要更强有力的论据来支持 ( 3)
    • @Andreas:他们已经取代了 Singleton?不在我的代码中! 3更有力的论据?当然!只需使用资源包。问题解决了,容易多了。
    • @Cameron - 哇 - 不要告诉 SO Java 专家您仍在使用旧的 Java Singleton 模式 ;-)
    • @Andreas:我想它确实让你免费获得了很多。感觉很hacky,但经过反思,我认为它确实很有意义。也许我会成为大师呢:)
    • 你可以在某处拥有一大堆静态最终字符串。因此,Text 类可以有public final static String STRING1 = "STRING1",您可以使用getString(Text.STRING1) 查找资源包。您的代码现在看起来与您正在使用的 Enum 完全一样,并且 IDE 应该能够识别这些常量。如果需要,您甚至可以使用静态导入。
    【解决方案3】:

    我讨厌劫持话题,但依赖 i18n 的枚举最终会将你逼入绝境。 Java 有适当的 i18n 支持,甚至有一个 tutorial 支持它。

    【讨论】:

      【解决方案4】:

      虽然 java 使用 ResourceBundle 支持 i18n,但我认为使用枚举来实现此目的的想法并不是那么糟糕。我相信这两种方法可以合并。您可以创建包含所有文本标识符的enum Texts。您可以为每种支持的语言创建资源包,并在此包中使用相同的标识符。

      然后在枚举中实现getText()方法如下:

      return ResourceBundle.getBundle("texts").getString(name());
      

      因此,您不必关心每种语言的文本初始化。标准机制关心这一点。

      现在您可以在代码中使用枚举并享受捆绑包的所有功能。您还可以创建单元测试来验证所有枚举成员在捆绑包中是否有适当的行,反之亦然,以避免捆绑包中的垃圾。

      我可能会在我的下一个项目中使用这种方法。谢谢你的想法!

      【讨论】:

      • 不客气 :) 我会看看你的解决方案,它看起来非常完整和简单(同时保持我可以用符号引用字符串而不是字符串的事实,这是我的主要目的)
      【解决方案5】:

      感谢您向我展示了一个我以前从未见过的编译器错误。编译时生成的源文件:

      public static void main(String[] args) throws Exception {
          PrintWriter w = new PrintWriter("C:\\test.java");
          w.println("enum Test {");
          for (int i = 0; i < 3000; i++) {
              w.println("c" + i + ",");
          }
          w.println("}");
          w.close();
      }
      

      日食说

      静态初始化器的代码是 超过 65535 字节限制

      只有 2000 个常量的相同测试可以完美编译。

      当然,如果您有这么多常量,最好将它们组织到多个源文件中。

      是的,为每个枚举常量分配一个(并且只有一个)对象。使用 2000 个常量,内存高达 16KB :-)(在 Sun 的 32 位 VM 上,其他 VM 可能略有不同)

      每个枚举常量都是一个对象,每个枚举常量都有一个字段text。该字段不是最终的,因此不受内联的影响。是的,字段访问是恒定时间的。

      但是,一般来说,在枚举中具有可变状态是很奇怪的。不过有可能。

      好的方法包括:

      如 AlexR 所示,委托给 ResourceBundle。缺点:您必须手动管理资源文件。如果你这样做,我建议使用 UnitTest 来检测错误输入/丢失/多余的资源键,或者甚至使用命令行实用程序将丢失的键附加到资源文件中,这样你就不必(错误)输入它们。

      如果您只支持几种语言,您也可以将所有语言存储在枚举中:

      enum Message {
          Hello("Hello", "Hallo", "Salut");
      
          String en;
          String de;
          String fr;
      
          Message(String en, String de, String fr) {
              this.en = en;
              this.fr = fr;
              this.it = it;
          }
      

      缺点:没有外行编辑(需要编译器),源文件编码最好支持目标语言中的所有特殊字符(unicode转义很尴尬......)。此外,如果您的语言超过 3 或 4 种,源文件就会变得混乱。

      优点:添加/删除文本轻而易举,编译器会捕获文本名称中的所有拼写错误,并且“资源文件”始终保持一致。

      无论哪种方式,您都应该使用 MessageFormat 作为教程 R.Bemrose 在他的回答中链接到的链接。

      最后,在使用 Enums 时,您可能会发现 values() 方法很方便:

      for (Text t : Text.values()) {
      
      }
      

      【讨论】:

      • 您可以将text 字段设为final,但是,从由语言选择的资源文件初始化它包含一些黑魔法(我不建议这样做)。
      【解决方案6】:

      我同意枚举最适合 I18n 的键,而不是它们转换为的字符串。 但是对于您的具体问题,您应该使用构造函数而不是设置器。恕我直言,事实上,在 90% 以上的情况下,您应该使用构造函数,其中值是在构造时设置且未更改,而不是使用设置器。

      public enum Text {
        STRING1("String one"),
        STRING2("String two"),
        STRING3("String two");
      
        private final String text;
        private Text(String text) { this.text = text; }
      }
      

      在创建枚举的性能方面,你不应该为游戏担心,应该首先考虑清楚和灵活性。 1000 个枚举可能会使应用的启动时间增加 1 毫秒。参考文献从文件加载文本可能会增加 10 毫秒。

      【讨论】:

      • 是的,我正在考虑使用构造函数 + final 字段,但这不允许我在运行时更改语言。
      • constructor 解决方案有一个缺点:您必须为每种语言编译一个 Text.java 文件,如果您在单个文件中忘记了一个枚举,正在运行的 JVM 可能会向穷人抛出令人讨厌的错误播放器。每种语言一个通用枚举文件和一个设置类的风险较小。 (text 应初始化为空字符串或来自原始语言的消息)
      • 就像我说的,我会单独使用枚举作为密钥。所有语言的键都是相同的,只有翻译会改变。因此,您将拥有一个 Map 为属性文件加载。
      猜你喜欢
      • 2011-12-23
      • 1970-01-01
      • 2011-03-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多