I used to define a set of related constants like Bundle keys together in an interface like below:
public interface From{
String LOGIN_SCREEN = "LoginSCreen";
String NOTIFICATION = "Notification";
String WIDGET = "widget";
}
This provides me a nicer way to group related constants together and used them by making a static import (not implements). I know Android framework also uses the constants in same way like Toast.LENTH_LONG, View.GONE.
However, I often feel that the Java Enums provide much better and powerful way to represent the constant.
But is there a performence issue in using enums on Android?
With a bit of research I ended up in confusion. From this question
"Avoid Enums Where You Only Need Ints” removed from Android's performance tips? it's clear that Google has removed "Avoid enums" from its performance tips, but from it's official training docs Be aware of memory overhead section it clearly says: "Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android." Is this still holds good? (say in Java versions after 1.6)
One more issue that I observed is to send enums across intents using Bundle I should send them by serializing (i.e putSerializable(), that I think an expensive operation compared to primitive putString() method, eventhough enums provides it for free).
Can someone please clarify which one is the best way to represent the same in Android? Should I strictly avoid using enums on Android?
Use enum when you need its features. Don't avoid it strictly.
Java enum is more powerful, but if you don't need its features, use constants, they occupy less space and they can be primitive itself.
When to use enum:
type checking - you can accept only listed values, and they are not continuous (see below what I call continuous here)
method overloading - every enum constant has its own implementation of a method
public enum UnitConverter{
METERS{
#Override
public double toMiles(final double meters){
return meters * 0.00062137D;
}
#Override
public double toMeters(final double meters){
return meters;
}
},
MILES{
#Override
public double toMiles(final double miles){
return miles;
}
#Override
public double toMeters(final double miles){
return miles / 0.00062137D;
}
};
public abstract double toMiles(double unit);
public abstract double toMeters(double unit);
}
more data - your one constant contains more than one information that cannot be put in one variable
complicated data - your constant need methods to operate on the data
When not to use enum:
you can accept all values of one type, and your constants contain only these most used
you can accept continuous data
public class Month{
public static final int JANUARY = 1;
public static final int FEBRUARY = 2;
public static final int MARCH = 3;
...
public static String getName(final int month){
if(month <= 0 || month > 12){
throw new IllegalArgumentException("Invalid month number: " + month);
}
...
}
}
for names (like in your example)
for everything else that really doesn't need an enum
Enums occupy more space
a single reference to an enum constant occupies 4 bytes
every enum constant occupies space that is a sum of its fields' sizes aligned to 8 bytes + overhead of the object
the enum class itself occupies some space
Constants occupy less space
a constant doesn't have a reference so it's a pure data (even if it's a reference, then enum instance would be a reference to another reference)
constants may be added to an existing class - it's not necessary to add another class
constants may be inlined; it brings extended compile-time features (such as null checking, finding dead code etc.)
If the enums simply have values, you should try to use IntDef/StringDef , as shown here:
https://developer.android.com/studio/write/annotations.html#enum-annotations
Example: instead of :
enum NavigationMode {NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS}
you use:
#IntDef({NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS})
#Retention(RetentionPolicy.SOURCE)
public #interface NavigationMode {}
public static final int NAVIGATION_MODE_STANDARD = 0;
public static final int NAVIGATION_MODE_LIST = 1;
public static final int NAVIGATION_MODE_TABS = 2;
and in the function that has it as a parameter/returned value , use:
#NavigationMode
public abstract int getNavigationMode();
public abstract void setNavigationMode(#NavigationMode int mode);
In case the enum is complex, use an enum. It's not that bad.
To compare enums vs constant values, you should read here:
http://hsc.com/Blog/Best-Practices-For-Memory-Optimization-on-Android-1
Their example is of an enum with 2 values. It takes 1112 bytes in dex file compared to 128 bytes when constant integers are used . Makes sense, as enums are real classes, as opposed to how it works on C/C++ .
With Android P, google has no restriction/objection in using enums
The documentation has changed where before it was recommended to be cautious but it doesn't mention it now.
https://developer.android.com/reference/java/lang/Enum
In addition to previous answers, I would add that if you are using Proguard (and you should definitely do it to reduce size and obfuscate your code), then your Enums will be automatically converted to #IntDef wherever it is possible:
https://www.guardsquare.com/en/proguard/manual/optimizations
class/unboxing/enum
Simplifies enum types to integer constants, whenever possible.
Therefore, if you have some discrete values and some method should allow to take only this values and not others of the same type, then I would use Enum, because Proguard will make this manual work of optimizing code for me.
And here is a good post about using enums from Jake Wharton, take a look at it.
As a library developer, I recognize these small optimizations that should be done as we want to have as little impact on the consuming app's size, memory, and performance as possible. But it's important to realize that [...] putting an enum in your public API vs. integer values where appropriate is perfectly fine. Knowing the difference to make informed decisions is what's important
Should I strictly avoid using enums on Android?
No. "Strictly" means they are so bad, they should not be used at all. Possibly a performance issues might arise in an extreme situation like many many many (thousands or millions of) operations with enums (consecutive on the ui thread). Far more common are the network I/O operations that should strictly happen in a background thread.
The most common usage of enums is probably some kind of type check - whether an object is this or that which is so fast you won't be able to notice a difference between a single comparison of enums and a comparison of integers.
Can someone please clarify which one is the best way to represent the same in Android?
There is no general rule of thumb for this. Use whatever works for you and helps you get your app ready. Optimize later - after you notice there's a bottleneck that slows some aspect of your app.
I like to add, that you can not use #Annotations when you declare a List<> or Map<> where either key or value is of one of your annotation interfaces.
You get the error "Annotations are not allowed here".
enum Values { One, Two, Three }
Map<String, Values> myMap; // This works
// ... but ...
public static final int ONE = 1;
public static final int TWO = 2;
public static final int THREE = 3;
#Retention(RetentionPolicy.SOURCE)
#IntDef({ONE, TWO, THREE})
public #interface Values {}
Map<String, #Values Integer> myMap; // *** ERROR ***
So when you need to pack it into a list/map, use enum, as they can be added, but #annotated int/string groups can not.
Two facts.
1, Enum is one of the most powerful feature in JAVA.
2, Android phone usually has a LOT of memory.
So my answer is NO. I will use Enum in Android.
Related
I'm just beginning to learn OOP programming in java. I have already programmed a little in C++, and one of the things I miss the most in Java is the possibility to return multiple values. It's true that C++ functions only strictly return one variable, but we can use the by-reference parameters to return many more. Conversely, in Java we can't do such a thing, at least we can't for primitive types.
The solution I thought off was to create a class grouping the variables I wanted to return and return an instance of that class. For example, I needed to look for an object in a an array and I wanted to return a boolean(found or not) and an index. I know I could make this just setting the index to -1 if nothing was found, but I think it's more clear the other way.
The thing is that I was told by someone who knows much more about Java than I know that I shouldn't create classes for the purpose of returning multiple values ( even if they are related). He told classes should never be used as C++ structs, just to group elements. He also said methods shouldn't return non-primitive objects , they should receive the object from the outside and only modify it. Which of these things are true?
I shouldn't create classes for the purpose of returning multiple values
classes should never be used as C++ structs, just to group elements.
methods shouldn't return non-primitive objects, they should receive the object from the outside and only modify it
For any of the above statements this is definitely not the case. Data objects are useful, and in fact, it is good practice to separate pure data from classes containing heavy logic.
In Java the closest thing we have to a struct is a POJO (plain old java object), commonly known as data classes in other languages. These classes are simply a grouping of data. A rule of thumb for a POJO is that it should only contain primitives, simple types (string, boxed primitives, etc) simple containers (map, array, list, etc), or other POJO classes. Basically classes which can easily be serialized.
Its common to want to pair two, three, or n objects together. Sometimes the data is significant enough to warrant an entirely new class, and in others not. In these cases programmers often use Pair or Tuple classes. Here is a quick example of a two element generic tuple.
public class Tuple2<T,U>{
private final T first;
private final U second;
public Tuple2(T first, U second) {
this.first = first;
this.second = second;
}
public T getFirst() { return first; }
public U getSecond() { return second; }
}
A class which uses a tuple as part of a method signature may look like:
public interface Container<T> {
...
public Tuple2<Boolean, Integer> search(T key);
}
A downside to creating data classes like this is that, for quality of life, we have to implement things like toString, hashCode, equals getters, setters, constructors, etc. For each different sized tuple you have to make a new class (Tuple2, Tuple3, Tuple4, etc). Creating all of these methods introduce subtle bugs into our applications. For these reasons developers will often avoid creating data classes.
Libraries like Lombok can be very helpful for overcoming these challenges. Our definition of Tuple2, with all of the methods listed above, can be written as:
#Data
public class Tuple2<T,U>{
private final T first;
private final U second;
}
This also makes it extremely easy to create custom response classes. Using the custom classes can avoid autoboxing with generics, and increase readability greatly. eg:
#Data
public class SearchResult {
private final boolean found;
private final int index;
}
...
public interface Container<T> {
...
public SearchResult search(T key);
}
methods should receive the object from the outside and only modify it
This is bad advice. It's much nicer to design data around immutability. From Effective Java 2nd Edition, p75
Immutable objects are simple. An immutable object can be in exactly one state, the state in which it was created. If you make sure that all constructors establish class invariants, then it is guaranteed that these invariants will remain true for all time, with no further effort on your part or on the part of the programmer who uses the class. Mutable objects, on the other hand, can have arbitrarily complex state spaces. If the documentation does not provide a precise description of the state transitions performed by mutator methods, it can be difficult or impossible to use a mutable class reliably.
Immutable objects are inherently thread-safe; they require no synchronization. They cannot be corrupted by multiple threads accessing them concurrently. This is far and away the easiest approach to achieving thread safety. In fact, no thread can ever observe any effect of another thread on an immutable object. Therefore, immutable objects can be shared freely.
As to your specific example ("how to return both error status and result?")
I needed to look for an object in a an array and I wanted to return a boolean(found or not) and an index. I know I could make this just setting the index to -1 if nothing was found, but I think it's more clear the other way.
Returning special invalid result values such as -1 for "not found" is indeed very common, and I agree with you that it is not too pretty.
However, returning a tuple of (statusCode, resultValue) is not the only alternative.
The most idiomatic way to report exceptions in Java is to, you guessed it, use exceptions. So return a result or if no result can be produced throw an exception (NoSuchElementException in this case). If this is appropriate depends on the application: You don't want to throw exceptions for "correct" input, it should be reserved for irregular cases.
In functional languages, they often have built-in data structures for this (such as Try, Option or Either) which essentially also do statusCode + resultValue internally, but make sure that you actually check that status code before trying to access the result value. Java now has Optional as well. If I want to go this route, I'd pull in these wrapper types from a library and not make up my own ad-hoc "structs" (because that would only confuse people).
"methods shouldn't return non-primitive objects , they should receive the object from the outside and only modify it"
That may be very traditional OOP thinking, but even within OOP the use of immutable data absolutely has its value (the only sane way to do thread-safe programming in my book), so the guideline to modify stuff in-place is pretty terrible. If something is considered a "data object" (as opposed to "an entity") you should prefer to return modified copies instead of mutating the input.
For some static Information you can use the static final options. Variables, declared as static final, can be accessed from everywhere.
Otherwise it is usual and good practise to use the getter/ setter concept to receive and set parameters in your classes.
Strictly speaking, it is a language limitation that Java does not natively support tuples as return values (see related discussion here). This was done to keep the language cleaner. However, the same decision was made in most other languages. Of course, this was done keeping in mind that, in case of necessity, such a behaviour can be implemented by available means. So here are the options (all of them except the second one allow to combine arbitrary types of return components, not necessarily primitive):
Use classes (usually static, self-made or predefined) specifically designed to contain a group of related values being returned. This option is well covered in other answers.
Combine, if possible, two or more primitive values into one return value. Two ints can be combined into a single long, four bytes can be combined into a single int, boolean and unsigned int less than Integer.MAX_VALUE can be combined into a signed int (look, for example, at how Arrays.binarySearch(...) methods return their results), positive double and boolean can be combined into a single signed double, etc. On return, extract the components via comparisons (if boolean is among them) and bit operations (for shifted integer components).
2a. One particular case worth noting separately. It is common (and widely used) convention to return null to indicate that, in fact, the returned value is invalid. Strictly speaking, this convention substitutes two-field result - one implicit boolean field that you're using when checking
if (returnValue != null)
and the other non-primitive field (which can be just a wrapper of a primitive field) containing the result itself. You use it after the above checking:
ResultClass result = returnValue;
If you don't want to mess with data classes, you can always return an array of Objects:
public Object[] returnTuple() {
return new Object[]{1234, "Text", true};
}
and then typecast its components to desired types:
public void useTuple() {
Object[] t = returnTuple();
int x = (int)t[0];
String s = (String)t[1];
boolean b = (boolean)t[2];
System.out.println(x + ", " + s + ", " + b);
}
You can introduce field(s) into your class to hold auxiliary return component(s) and return only the main component explicitly (you decide which one is the main component):
public class LastResultAware {
public static boolean found;
public static int errorCode;
public static int findLetter(String src, char letter) {
int i = src.toLowerCase().indexOf(Character.toLowerCase(letter));
found = i >= 0;
return i;
}
public static int findUniqueLetter(String src, char letter) {
src = src.toLowerCase();
letter = Character.toLowerCase(letter);
int i = src.indexOf(letter);
if (i < 0)
errorCode = -1; // not found
else {
int j = src.indexOf(letter, i + 1);
if (j >= 0)
errorCode = -2; // ambiguous result
else
errorCode = 0; // success
}
return i;
}
public static void main(String[] args) {
int charIndex = findLetter("ABC", 'b');
if (found)
System.out.println("Letter is at position " + charIndex);
charIndex = findUniqueLetter("aBCbD", 'b');
if (errorCode == 0)
System.out.println("Letter is only at position " + charIndex);
}
}
Note that in some cases it is better to throw an exception indicating an error than to return an error code which the caller may just forget to check.
Depending on usage, this return-extending fields may be either static or instance. When static, they can even be used by multiple classes to serve a common purpose and avoid unnecessary field creation. For example, one public static int errorCode may be enough. Be warned, however, that this approach is not thread-safe.
Consider the following (totally contrived) example:
public class Length {
private static final int MAX_LENGTH = 10;
private final int length;
public Length(int length) {
if (length > MAX_LENGTH)
throw new IllegalArgumentException("Length too long");
this.length = length;
}
}
I would like to test that this throws an exception when called with a length greater than MAX_LENGTH. There are a number of ways this can be tested, all with disadvantages:
#Test(expected = IllegalArgumentException.class)
public void testMaxLength() {
new Length(11);
}
This replicates the constant in the testing case. If MAX_LENGTH becomes smaller this will silently no longer be an edge case (though clearly it should be paired with a separate case to test the other side of the edge). If it becomes larger this will fail and need to be changed manually (which might not be a bad thing).
These disadvantages can be avoided by adding a getter for MAX_LENGTH and then changing the test to:
new Length(Length.getMaxLength());
This seems much better as the test does not need to be changed if the constant changes. On the other hand it is exposing a constant that would otherwise be private and it has the significant flaw of testing two methods at once - the test might give a false positive if both methods are broken.
An alternative approach is to not use a constant at all but, rather, inject the dependency:
interface MaxLength {
int getMaxLength();
}
public class Length {
public static void setMaxLength(MaxLength maxLength);
}
Then the 'constant' can be mocked as part of the test (example here using Mockito):
MaxLength mockedLength = mock(MaxLength.class);
when(mokedLength.getMaxLength()).thenReturn(17);
Length.setMaxLength(mockedLength);
new Length(18);
This seems to be adding a lot of complexity for not a lot of value (assuming there's no other reason to inject the dependency).
At this stage my preference is to use the second approach of exposing the constants rather than hardcoding the values in the test. But this does not seem ideal to me. Is there a better alternative? Or is the lack of testability of these cases demonstrating a design flaw?
As Tim alluded to in the comments, your goal is to make sure that your software behaves according to the specifications. One such specification might be that the maximum length is always 10, at which point it'd be unnecessary to test a world where length is 5 or 15.
Here's the question to ask yourself: How likely is it that you'll want to use your class with a different value of the "constant"? I've quoted "constant" here because if you vary the value programmatically, it's not really a constant at all, is it? :)
If your value will never ever change, you could not use a symbolic constant at all, just comparing to 10 directly and testing based on (say) 0, 3, 10, and 11. This might make your code and tests a little hard to understand ("Where did the 10 come from? Where did the 11 come from?"), and will certainly make it hard to change if you ever do have reason to vary the number. Not recommended.
If your value will probably never change, you could use a private named constant (i.e. a static final field), as you have. Then your code will be easy enough to change, though your tests won't be able to automatically adjust the way your code would.
You could also relax to package-private visibility, which would be available to tests in the same package. Javadoc (e.g. /** Package-private for testing. */) or documentation annotations (e.g. #VisibleForTesting) may help make your intentions clear. This is a nice option if your constant value is intended to be opaque and unavailable outside of your class, like an URL template or authentication token.
You could even make it a public constant, which would be available to consumers of your class as well. For your example of a constant Length, a public static final field is probably best, on the assumption that other pieces of your system may want to know about that (e.g. for UI validation hints or error messages).
If your value is likely to change you could accept it per-instance, as in new Length(10) or new Length().setMaxLength(10). (I consider the former to be a form of dependency injection, counting the constant integer as a dependency.) This is also a good idea if you wanted to use a different value in tests, such as using a maximum length of 2048 in production but testing against 10 for practicality's sake. To make a flexible length validator, this option is probably a good upgrade from a static final field.
Only if your value is likely to change during your instance's lifetime would I bother with a DI-style value provider. At that point, you can query the value interactively, so it doesn't behave at all like a constant. For "length", that'd be obvious overkill, but maybe not for "maximum allowed memory", "maximum simultaneous connections", or some other pseudo-constants like that.
In short, you'll have to decide how much control you need, and then you can pick the most straightforward choice from there; as a "default", you may want to make it a visible field or constructor parameter, as those tend to have good balance of simplicity and flexibility.
I am in a situation where I want to use mutable versions of things like Integer. Do I have to use these classes (below) or does Java have something built in?
http://www.java2s.com/Code/Java/Data-Type/Amutableintwrapper.htm
You could always wrap the value in an array like int[] mutable = {1}; if including the code for a mutable wrapper class is too cumbersome.
No, Java doesn't have these built in. And that is for a reason. Using mutable types is dangerous, as they can easily be misused. Additionally, it is really easy to implement it. For example, commons-lang has a MutableInt.
Since JDK 1.5 java now has java.util.concurrent.atomic.AtomicInteger
This is a thread safe mutable integer, example of use:
final AtomicInteger value = new AtomicInteger(0);
then later on:
value.incrementAndGet();
Here's a small class I made for a mutable integer:
public class MutableInteger {
private int value;
public MutableInteger(int value) {
this.value = value;
}
public void set(int value) {
this.value = value;
}
public int intValue() {
return value;
}
}
You could easily extend this to any other primitive. Of course, like everyone else is saying, you should use it carefully.
You can use an nnnn[] as a mutable object for any primitive type as #Alexandre suggests, java also has AtomicInteger and AtomicLong.
IMHO int is usually a better choice than Integer and that is mutable.
Can you more details of why you need a mutliple object, perhaps there is another way to achieve the same thing.
AtomicInteger has already been mentioned. Mutable Doubles can be emulated with AtomicReference<Double>. The already mentioned warnings apply and it is bad style, but sometimes you have code like this
double sum=0
for (Data data:someListGenerator())
sum+=data.getValue()
and want to refactor it in functional Java 8 style. If the code follows this pattern but adds considerable complexity to it, the most sensible conversion could be
AtomicReference<Double> sumref=new AtomicReference<>(0d);
someStreamGenerator().forEach(data->
sumref.set(sumref.get().doubleValue()+data.getValue()));
double sum=sumref.get().doubleValue();
Of course, this is at least questionable style. But I found myself more than once in a situation with a twisted loop over a ResultSet computing and partly cumulating three different information from it. This makes it really hard to convert the code into proper functional style. Converting the cumulating parts according to the above pattern seemed to me a reasonable tradeoff between clean code and oversimplified refactoring.
You can import the org.omg.CORBA package(or just the class you need) and in it you can use the Holder classes.
For example, it has the "IntHolder" where the field where it stores the integer is public, giving access to modify it.
public static void triple(IntHolder x){
x.value = 3 * x.value;
}
IntHolder mutableInt = new IntHolder(10);
triple(mutableInt);
System.out.println(mutableInt.value);
It also has "LongHolder" and "DoubleHolder" and tons of others that you can use. Use with caution.
Here is the api for it: https://docs.oracle.com/javase/7/docs/api/org/omg/CORBA/package-summary.html
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
is an obect allocated (in addition to the string) for every element? (I guess yes, since enums are implemented with objects)
how is the right element retrieved from the enum? is it static at compile time? (I mean when somewhere I use Text.STRING1.text()). So it should be constant complexity or maybe they are just replaced during the compiling phase..
in general, is it a good approach or should I look forward something else?
Thanks
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!
You're probably better off using the java.util.ResourceBundle class. It is designed to solve exactly this problem.
To answer your questions:
Yes, there is exactly one instance of each enum value.
Yes, constant complexity for looking up an Enum value.
Not really. Changing the content/behaviour of the enum kinda defeats the purpose of having enums in the first place. They're supposed to represent fixed-range constants with type safety. You can do this kind of thing but that's not what they were designed for.
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.
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!
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()) {
}
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.
Suppose you're maintaining an API that was originally released years ago (before java gained enum support) and it defines a class with enumeration values as ints:
public class VitaminType {
public static final int RETINOL = 0;
public static final int THIAMIN = 1;
public static final int RIBOFLAVIN = 2;
}
Over the years the API has evolved and gained Java 5-specific features (generified interfaces, etc). Now you're about to add a new enumeration:
public enum NutrientType {
AMINO_ACID, SATURATED_FAT, UNSATURATED_FAT, CARBOHYDRATE;
}
The 'old style' int-enum pattern has no type safety, no possibility of adding behaviour or data, etc, but it's published and in use. I'm concerned that mixing two styles of enumeration is inconsistent for users of the API.
I see three possible approaches:
Give up and define the new enum (NutrientType in my fictitious example) as a series of ints like the VitaminType class. You get consistency but you're not taking advantage of type safety and other modern features.
Decide to live with an inconsistency in a published API: keep VitaminType around as is, and add NutrientType as an enum. Methods that take a VitaminType are still declared as taking an int, methods that take a NutrientType are declared as taking such.
Deprecate the VitaminType class and introduce a new VitaminType2 enum. Define the new NutrientType as an enum. Congratulations, for the next 2-3 years until you can kill the deprecated type, you're going to deal with deprecated versions of every single method that took a VitaminType as an int and adding a new foo(VitaminType2 v) version of each. You also need to write tests for each deprecated foo(int v) method as well as its corresponding foo(VitaminType2 v) method, so you just multiplied your QA effort.
What is the best approach?
How likely is it that the API consumers are going to confuse VitaminType with NutrientType? If it is unlikely, then maybe it is better to maintain API design consistency, especially if the user base is established and you want to minimize the delta of work/learning required by customers. If confusion is likely, then NutrientType should probably become an enum.
This needn't be a wholesale overnight change; for example, you could expose the old int values via the enum:
public enum Vitamin {
RETINOL(0), THIAMIN(1), RIBOFLAVIN(2);
private final int intValue;
Vitamin(int n) {
intValue = n;
}
public int getVitaminType() {
return intValue;
}
public static Vitamin asVitamin(int intValue) {
for (Vitamin vitamin : Vitamin.values()) {
if (intValue == vitamin.getVitaminType()) {
return vitamin;
}
}
throw new IllegalArgumentException();
}
}
/** Use foo.Vitamin instead */
#Deprecated
public class VitaminType {
public static final int RETINOL = Vitamin.RETINOL.getVitaminType();
public static final int THIAMIN = Vitamin.THIAMIN.getVitaminType();
public static final int RIBOFLAVIN = Vitamin.RIBOFLAVIN.getVitaminType();
}
This allows you to update the API and gives you some control over when to deprecate the old type and scheduling the switch-over in any code that relies on the old type internally.
Some care is required to keep the literal values in sync with those that may have been in-lined with old consumer code.
Personal opinion is that it's probably not worth the effort of trying to convert. For one thing, the "public static final int" idiom isn't going away any time soon, given that it's sprinkled liberally all over the JDK. For another, tracking down usages of the original ints is likely to be really unpleasant, given that your classes will compile away the reference so you're likely not to know you've broken anything until it's too late
(by which I mean
class A
{
public static final int MY_CONSTANT=1
}
class B
{
....
i+=A.MY_CONSTANT;
}
gets compiled into
i+=1
So if you rewrite A you may not ever realize that B is broken until you recompile B later.
It's a pretty well known idiom, probably not so terrible to leave it in, certainly better than the alternative.
There is a rumor that the creator of "make" realized that the syntax of Makefiles was bad, but felt that he couldn't change it because he already had 10 users.
Backwards compatibility at all costs, even if it hurts your customers, is a bad thing. SO can't really give you a definitive answer on what to do in your case, but be sure and consider the cost to your users over the long term.
Also think about ways you can refactor the core of your code will keeping the old integer based enums only at the outer layer.
Wait for the next major revision, change everything to enum and provide a script (sed, perl, Java, Groovy, ...) to convert existing source code to use the new syntax.
Obviously this has two drawbacks:
No binary compatibility. How important this one is depends on the use cases, but can be acceptable in the case of a new major release
Users have to do some work. If the work is simple enough, then this too may be acceptable.
In the meantime, add new types as enums and keep old types as ints.
The best would be if you could just fix the published versions, if possible. In my opinion consistency would be the best solution, so you would need to do some refactoring. I personally don't like deprecated things, because they get into way. You might be able to wait until a bigger version release and use those ints until then, and refactor everything in a big project. If that is not possible, you might consider yourself stuck with the ints, unless you create some kinds of wrappers or something.
If nothing helps but you still evolve the code, you end up losing consistency or living with the deprecated versions. In any case, usually at least at some point of time people become fed up with old stuff if it has lost it's consistency and create new from scratch... So you would have the refactoring in the future no matter what.
The customer might scrap the project and buy an other product, if something goes wrong. Usually it is not the customer's problem can you afford refactoring or not, they just buy what is appropriate and usable to them. So in the end it is a tricky problem and care needs to be taken.