Reputation: 133587
I was thinking about using enum
type to manage i18n in a Java game I'm developing but I was curious about performance issues that can occur when working with enums that have lots of elements (thousands I think).
Actually I'm trying something like:
public enum Text {
STRING1,
STRING2,
STRING3;
public String text() {
return text;
}
public String setText() {
this.text = text;
}
}
Then to load them I can just fill the fields:
static
{
Text.STRING1.setText("My localized string1");
Text.STRING2.setText("My localized string2");
Text.STRING3.setText("My localized string3");
}
Of course when I'll have to manage many languages I'll load them from a file.
What I'm asking is
Text.STRING1.text()
). So it should be constant complexity or maybe they are just replaced during the compiling phase..Thanks
Upvotes: 5
Views: 8065
Reputation: 115338
although java has i18n support using ResourceBundle I do not think that idea to use enum for this purpose is so bad. I believe that these 2 approaches can be merged. You can create enum Texts
that contains all your text identifiers. You can create resource bundles for each supported language and use the same identifiers in this bundle.
Then implement getText()
method in the enum as following:
return ResourceBundle.getBundle("texts").getString(name());
So, you do not have to care about the initialization of texts for each language. The standard mechanism cares about this.
Now you use in code the enums and enjoy all features of bundles. You can also create unit test that verifies that all enum members have appropriate lines in bundle and vice versa to avoid garbage in your bundles.
I will probably use this approach in my next project. Thank you for the idea!
Upvotes: 2
Reputation: 70564
Kudos for showing me a compiler error I have never seen before. When compiling the source file generated by:
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();
}
eclipse says
The code for the static initializer is exceeding the 65535 bytes limit
Same test with a mere 2000 constants compiles flawlessly.
Of course, if you have that many constants, it would be a good idea to organize them into more than one source file.
Yes, one (and only one) object is allocated for every enum constant. With 2000 constants, that's a whopping 16KB memory :-) (on Sun's 32-bit VM, other VMs might differ a little)
Each enum constant is an object, and each of them has a field text
. The field is not final, and hence not subject to inlining. Yes, field access is constant-time.
However, in general it's wierd having mutable state in an enum. It's possible, though.
Good approaches include:
Delegate to a ResourceBundle as AlexR shows. Disadvantage: You have to manually manage the resource files. If you do that, I recommend a UnitTest to detect mistyped/missing/superfluous resource keys, or even a command line utility to append the missing keys to the resource file so you don't have to (mis-)type them.
If you only support a few languages, you can alternatively store all languages in the enum:
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;
}
Disadvantages: No editing by laymen (needs a compiler), and the source file encoding had better support all special characters in the target language (unicode escapes are awkward ...). Also, the source file gets cluttered if you have more than 3 or 4 languages.
Advantages: Adding/Deleting texts is a snap, and the compiler catches all typos in the name of the text, and the "resource file" is always consistent.
Either way, you should use MessageFormat
as the tutorial R.Bemrose links to in his answer explains.
And finally, when working with Enums you might find the values() method handy:
for (Text t : Text.values()) {
}
Upvotes: 1
Reputation: 114787
Found and adapted a nice mix of enums and 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!
Upvotes: 14
Reputation: 533530
I agree that an enum is best for the keys of I18n rather than the strings they translate to. However to your specific problem, you should a constructor rather than a setter. IMHO, In fact you should use a constructor in 90%+ of cases where a value is set on construction and not changed rather than using a setter.
public enum Text {
STRING1("String one"),
STRING2("String two"),
STRING3("String two");
private final String text;
private Text(String text) { this.text = text; }
}
In terms of performance of creating enums, you shouldn't worry about it for a game, clarify and flexibility should be considered first. A 1000 enums might add 1 ms to the startup time of your app. c.f. Loading the text from a file is likely to add 10 ms.
Upvotes: 0
Reputation: 88796
I hate to hijack to topic, but relying on enums for i18n is going to eventually paint you into a corner. Java has proper i18n support, even going so far as to have a tutorial for it.
Upvotes: 7
Reputation: 54306
You're probably better off using the java.util.ResourceBundle
class. It is designed to solve exactly this problem.
To answer your questions:
Upvotes: 11