Java enum reverse lookup - java

So I have this enum that doesn't work as I expected and need some modifications:
public enum MyEnum {
CODE000("text description comes here"),
private final String value;
private static final Map<String, MyEnum> LOOKUP = Maps.uniqueIndex(
Arrays.asList(MyEnum.values()),
MyEnum::getValue
);
MyEnum(final String value) {
this.value = value;
}
public String getValue() {
return value;
}
public static MyEnum fromStatus(String status) {
return LOOKUP.get(status);
}
}
The way it works now is:
MyEnum.fromStatus("text description comes here") and of course I want the other way around:
MyEnum.fromStatus("CODE000") to return me "text description comes here"
Can someone suggest how I can change this ?

What you need is a literal lookup, which you get by calling valueOf:
MyEnum code000 = MyEnum.valueOf("CODE000");
And then:
String val = code000.getValue();
Please note that an exception will be raised if the string passed to valueOf doesn't resolve to an enum literal in MyEnum.

Your key function (MyEnum::getValue) is wrong as it returns the value.
It must be MyEnum::name
This will return the enum and not the text description as the value of the map is of type MyEnum. You can get the text value by calling getValue on the enum OR you can store the value in the map instead of the enum

If you want to get an enum value by enum name you can use this function :
public static String fromStatus(String status) {
MyEnum myEnum = valueOf(status);
return myEnum.getValue();
}

The answers so far are using the method valueOf. This method will return the enum constant as long as you provide a name of an enum constant. Otherwise an IllegalArgumentException will be thrown.
In your question you're using a lookup map. The Map (it looks like as it's created by Guava Maps) will return for non-enum-constant-names null. It will not throw a IllegalArgumentException in such cases. So it is a different behaviour.
In addition you say: "and of course I want the other way around"
This means you want to get the enum by status and the status by an enums name.
Therefore you would need to have two lookup methods:
status -> enum
name -> status
But you would get a compile time error if you define the two methods you mentioned:
public static MyEnum fromStatus(String status) { ... }
public static String fromStatus(String name) { ... }
The compiler could not distinguish the methods by name and parameter. But even though you wrote MyEnum.fromStatus("CODE000") actually it's the enum constant name you are using as parameter. So let's resolve the naming conflict by calling the second method fromName. The code for MyEnum could look like this:
public enum MyEnum {
CODE000("text description comes here");
private final String value;
private static final Map<String, MyEnum> LOOKUP_ENUM = Maps.uniqueIndex(Arrays.asList(MyEnum.values()), MyEnum::getValue);
private static final Map<String, String> LOOKUP_STATUS = Arrays.stream(MyEnum.values()).collect(Collectors.toMap(MyEnum::name, MyEnum::getValue));
MyEnum(final String value) {
this.value = value;
}
public String getValue() {
return value;
}
public static MyEnum fromStatus(String status) {
return LOOKUP_ENUM.get(status);
}
public static String fromName(String name) {
return LOOKUP_STATUS.get(name);
}
}
If you want to lookup the enum constants by it's names in the same manner (no exception on non-enum-constant-names), you need a third map and a third lookup method:
private static final Map<String, MyEnum> LOOKUP = Maps.uniqueIndex(Arrays.asList(MyEnum.values()), MyEnum::name);
public static MyEnum byName(String name) {
return LOOKUP.get(name);
}
This would work as follows:
System.out.println(MyEnum.fromStatus("text description comes here")); // CODE000
System.out.println(MyEnum.fromStatus("invalid")); // null - no exception
System.out.println(MyEnum.fromStatus(null)); // null - no exception
System.out.println(MyEnum.fromName("CODE000")); // "text description comes here"
System.out.println(MyEnum.fromName("invalid")); // null - no exception
System.out.println(MyEnum.fromName(null)); // null - no exception
System.out.println(MyEnum.byName("CODE000")); // CODE000
System.out.println(MyEnum.byName("invalid")); // null - no exception
System.out.println(MyEnum.byName(null)); // null - no exception
If you need the byName method I would suggest to rename the methodfromName to something like statusByName to keep them comprehensible apart.
Finally one more suggestion:
Since the lookup methods may return null we could return Optional<String> / Optional<MyEnum> as result. This would allow to immediately continue processing the result.
public static Optional<MyEnum> fromStatus(String status) { ... }
public static Optional<String> statusByName(String name) { ... }
public static Optional<MyEnum> byName(String name) { ... }

Related

Can Java enum class set default value

Mycode is
public enum PartsOfSpeech2 {
n("noun"),
wp("标点"),
a("adjective"),
d("conjunction"),
...;
which I want
public enum PartsOfSpeech2 {
n("noun"),
wp("标点"),
a("adjective"),
d("conjunction"),
%("noun");
can I hava a default value which is not in it, can it be set as a default value?
because I have a type is "%", but enum is not support %, so I want a default value to solve it
The default for one who holds a reference to an enum without setting a value would be null (either automatically in case of a class field, or set by the user explicitly).
Unfortunately you cannot override the method valueOf for your own enum, as it is static.
But you can still create your methods:
public enum PartsOfSpeech2 {
n("noun"),
wp("标点"),
a("adjective"),
d("conjunction");
private String value;
PartsOfSpeech2(String value) {
this.value = value;
}
// declare your defaults with constant values
private final static PartsOfSpeech2 defaultValue = n;
private final static String defaultString = "%";
// `of` as a substitute for `valueOf` handling the default value
public static PartsOfSpeech2 of(String value) {
if(value.equals(defaultString)) return defaultValue;
return PartsOfSpeech2.valueOf(value);
}
// `defaultOr` for handling default value for null
public static PartsOfSpeech2 defaultOr(PartsOfSpeech2 value) {
return value != null ? value : defaultValue;
}
#Override
public String toString() { return value; }
}
From JLS 8.9. Enums
An enum type has no instances other than those defined by its enum constants. It is a compile-time error to attempt to explicitly instantiate an enum type (§15.9.1).
So you can't have any instance which is take default value.
You can create default constant and use that using some condition.
public enum PartsOfSpeech2 {
....
DEFAULT("DEFAULT");
}
And use condition to check if your string have constant, Ex "%" have enum or not. if not use default value:
PartsOfSpeech2 result = PartsOfSpeech2.valueOf("%"); //Your String EX: %
PartsOfSpeech2 resultNew = result==null?PartsOfSpeech2.DEFAULT: result;
The way I solved it was the following
public enum YourEnum{
ENUM1("stringToMatchWith"),
ENUM2("stringToMatchWith2"),
DEFAULTENUM("Default");
public final String label;
YourEnum(String label) {
this.label = label;
}
public static YourEnum resolveYourEnum(String stringToMatch) {
return Arrays.stream(YourEnum.values()).filter(aEnum -> aEnum.label.equals(stringToMatch)).findFirst().orElse(YourEnum.DEFAULTENUM);
}
That way you can do YourEnum.resolveYourEnum("aString") and return the specified enum or the default we set

Pass enum type as parameter

I want to create a method, that:
Takes the type of an enum and a String as arguments
The String is the name of one specific enum instance
Returns the enum instance that fits that name.
What I have tried:
In class TestUtil.java:
public static <E extends Enum<E>> E mapToEnum(Enum<E> mappingEnum, String data) {
return mappingEnum.valueOf(E, data); // Not working, needs Class of Enum and String value
}
The enum:
public enum TestEnum {
TEST1("A"),
TEST2("B");
private String value;
private TestEnum(String value) {
this.value = value;
}
}
How it should work (For example in main method):
TestEnum x = TestUtil.mapToEnum(TestEnum.class, "TEST1"); // TEST1 is the name of the first enum instance
The problem is, that I can't figure out what I need to pass into the mapToEnum method, so that I can get the valueOf from that Enum.
If the code you provided is acceptable:
public static <E extends Enum<E>> E mapToEnum(Enum<E> mappingEnum, String data) {
return mappingEnum.valueOf(E, data); // Not working, needs Class of Enum and String value
}
Then all you have to do is fix it.
Here's the code I tested:
static <T extends Enum<T>> T mapToEnum(Class<T> mappingEnum, String data) {
return Enum.valueOf(mappingEnum, data);
}
Usage:
#Test
public void test() {
TestEnum myEnum = mapToEnum(TestEnum.class, "TEST1");
System.out.println(myEnum.value); //prints "A"
}
Strongly suggest using Apache commons-lang library for boiler plate function like this ...
TestEnum x = EnumUtils.getEnum(TestEnum.class, "TEST1");
... which is exactly the code #Fenio demonstrates but handles null or wrong input with a null instead of throwing an Exception.
If you didn't know about this then check out what the rest of the lang3 library holds. I view it as a de-facto standard, saving millions of devs from re-writing minor plumbing utilities.
This is how you can iterate the enum class value and match with the parameter you have passed in the method, please check the below-mentioned code.
enum TestEnum {
TEST1("test1"),
TEST2("test2");
private String value;
private TestEnum(String value) {
this.value = value;
}
public String getName() {
return value;
}
public static TestEnum mapToEnum(String data) {
for (TestEnum userType : TestEnum.values()) {
if (userType.getName().equals(data)) {
return userType;
}
}
return null;
}
}

Can I map between values inside enums?

I create enum which has two values: brand name and brand code.
I want to know the brand code by inputting the brand name.
And I also want to know the brand name by inputting the brand code.
Can this problem solved using Enum? or other code is more effective? I want to make the code as shorter as possible
I have created following code to search the code of a brand. If I want to do vice versa, I can create another Hashmap and method to convert the code into a brand. But is that the effective way to solve it?
public enum Brand{
COLA("cola", "CL8935"),
BREAD("bread", "BR2810"),
SNICKERS("snickers", "SN4423");
private static final Map<String, String> BY_BRAND = new HashMap<>();
static {
for (Brand brand : values()){
BY_BRAND.put(brand.code, brand.brand);
}
}
private final String brand;
private final String code;
public static String convertToCode(String brand){
return BY_BRAND.get(brand.toLowerCase()).toString();
}
}
Update - Adding the full enum (with imports)
import java.util.Arrays;
import java.util.function.Function;
enum Brand {
COLA("cola", "CL8935"),
BREAD("bread", "BR2810"),
SNICKERS("snickers", "SN4423");
private final String brand;
private final String code;
Brand(String brand, String code) {
this.brand = brand;
this.code = code;
}
public static Brand findBy(String value, Function<Brand, String> extractor) {
return Arrays.stream(Brand.values())
.filter(brand -> extractor.apply(brand).equalsIgnoreCase(value))
.findFirst()
.orElse("Either a default or throw exception here");
}
public String getBrand() {
return brand;
}
public String getCode() {
return code;
}
}
Original
You could use a static findBy method as an alternative to the map. This would allow you to pass in the value and method reference for the getter which will be used to compare the values stored within the enum.
The difference here would be performance (as maps would be faster), the fact that you would be returning the enum and that you most likely would want either a default enum value or to throw an exception on no matched being found. Below is an example
public static Brand findBy(String value, Function<Brand, String> extractor) {
return Arrays.stream(Brand.values())
.filter(brand -> extractor.apply(brand).equalsIgnoreCase(value))
.findFirst()
.orElse("Either a default or throw exception here");
}
And this can be called like this
public static void main(String[] args) {
Brand brand1 = Brand.findBy("cola", Brand::getBrand);
Brand brand2 = Brand.findBy("BR2810", Brand::getCode);
}
Simple static method in Brand should do:
public static String getBrand(String code) {
for(Brand b : Brand.values()){
if(b.getCode().equals(code)) return b.getBrand();
}
return null;
}
Similarly you can write a getCode(String brand)
Edit: assuming the two attributes do not have the same value, you can check do the bi-di mapping in the same method:
public static String getOtherAttribute(String value) {
for(Brand b : Brand.values()){
if(b.getCode().equals(value)) return b.getBrand();
if(b.getBrand().equals(value)) return b.getCode();
}
return null;
}
If the two attributes may have the same value you can add an argument (flag) to the method's signature to tell which attribute you want to retrieve.

Design Issue | Enum to represent combo box options

I need to use an Enum with a combobox (values shown below).
YES (shown as YES on UI, stored in DB as Y)
NO (shown as NO on UI, stored in DB as N)
DEFAULT (shown as "" on UI, stored in DB as null)
The Enum has methods to perform the following -
toString() - to provide the custom String for UI. (showing the combo options)
OptionToDB (static) - Convert a selected option to db value (on save / update)
DBToOption (static)- Convert a DB value to selcted option (while loading the screen)
static enum EnumOption{
YES,NO,DEFAULT;
....
public static EnumOption DBToOption(String val){
if("Y".equals(val)){
return YES;
} else if("N".equals(val)){
return NO;
}else {
return DEFAULT;
}
}
....
}
It works pretty well, but the issue with above methods is that it uses if/else comparison to deduce which option / db value to be returned.
I thought of storing the dbValue as a field in enum but I was not able to reduce the if/else from DBToOption.
Can this if/else be avoided in any way using a better design??
If you store the dbValue as a field in the enum, you can remove the if/else and replace it with a for-loop, although I don't see anything wrong with those if/elses for this particular case:
static enum EnumOption {
YES("Y"),
NO("N"),
DEFAULT("");
private final String value;
private EnumOption(String value) {
this.value = value;
}
public static EnumOption DBToOption(String val) {
for (EnumOption opt : EnumOption.values()) {
if (opt.value.equals(val)) {
return opt;
}
}
return DEFAULT;
}
}
public enum EnumOption {
YES("Y"), NO("N"), DEFAULT("");
private final String value;
private final static Map<String, EnumOption> options;
static {
options = new HashMap<String, EnumOption>();
for (EnumOption opt : EnumOption.values()) {
options.put(opt.value, opt);
}
}
private EnumOption(String value) {
this.value = value;
}
public static EnumOption DBToOption(String val) {
return options.get(val) != null ? options.get(val) : DEFAULT;
}
}
And here is the test that proves it works.
public void testDBToOption() {
assertEquals(EnumOption.NO, EnumOption.DBToOption("N"));
assertEquals(EnumOption.YES, EnumOption.DBToOption("Y"));
assertEquals(EnumOption.DEFAULT, EnumOption.DBToOption(""));
assertEquals(EnumOption.DEFAULT, EnumOption.DBToOption(null));
assertEquals(EnumOption.DEFAULT, EnumOption.DBToOption("R"));
}
So you want to get rid of the remaining if/else ...Are you doing Object Calisthenics?
You could do the following, if you do not have compatibility issues:
public enum EnumOption {
Y("Y", "YES"),
N("N", "NO"),
D("D", "");
private final String dbValue;
private final String uiValue;
private EnumOption(String dbValue, String uiValue) {
this.dbValue = dbValue;
this.uiValue = uiValue;
}
public String getDbValue() {
return this.dbValue;
}
public String uiValue() {
return this.uiValue;
}
public static EnumOption getFromDb(String dbValue) {
return EnumOption.valueOf(dbValue);
}
}
Since each enum value can only occur once, this has at least the same performance as all the other implementations.
For details about the automatically generated valueOf(String) method in enum types, and James DW's solution, you can read up in Josh Bloch's Effective Java Item 30 (Use enums instead of int constants), page 154.

Get enum by its inner field

Have enum with inner fields, kind of map.
Now I need to get enum by its inner field.
Wrote this:
package test;
/**
* Test enum to test enum =)
*/
public enum TestEnum {
ONE(1), TWO(2), THREE(3);
private int number;
TestEnum(int number) {
this.number = number;
}
public TestEnum findByKey(int i) {
TestEnum[] testEnums = TestEnum.values();
for (TestEnum testEnum : testEnums) {
if (testEnum.number == i) {
return testEnum;
}
}
return null;
}
}
But it's not very efficient to look up through all enums each time I need to find appropriate instance.
Is there any other way to do the same?
You can use a static Map<Integer,TestEnum> with a static initializer that populates it with the TestEnum values keyed by their number fields.
Note that findByKey has been made static, and number has also been made final.
import java.util.*;
public enum TestEnum {
ONE(1), TWO(2), SIXTY_NINE(69);
private final int number;
TestEnum(int number) {
this.number = number;
}
private static final Map<Integer,TestEnum> map;
static {
map = new HashMap<Integer,TestEnum>();
for (TestEnum v : TestEnum.values()) {
map.put(v.number, v);
}
}
public static TestEnum findByKey(int i) {
return map.get(i);
}
public static void main(String[] args) {
System.out.println(TestEnum.findByKey(69)); // prints "SIXTY_NINE"
System.out.println(
TestEnum.values() == TestEnum.values()
); // prints "false"
}
}
You can now expect findByKey to be a O(1) operation.
References
JLS 8.7 Static initializers
JLS 8.9 Enums
Related questions
Static initalizer in Java
How to Initialise a static Map in Java
Note on values()
The second println statement in the main method is revealing: values() returns a newly allocated array with every invokation! The original O(N) solution could do a little better by only calling values() once and caching the array, but that solution would still be O(N) on average.
Although someone has suggested using Map<Integer, TestEnum> think twice about it.
Your original solution, especially for small enums, may be magnitudes faster than using HashMap.
HashMap will probably be not faster until your enum contains at least 30 to 40 elements.
This is one case of "If it ain't broken, don't fix it".
Here is the most convenient way to find enum value by its field:
public enum TestEnum {
A("EXAMPLE_1", "Qwerty", 1),
B("EXAMPLE_2", "Asdfgh", 2),
C("EXAMPLE_3", "Zxcvbn", 3);
private final String code;
private final String name;
private final Integer typeID;
TestEnum(String code, String name, Integer typeID) {
this.code = code;
this.name = name;
this.key = typeID;
}
public String getCode() {
return code;
}
public String getName() {
return name;
}
public Integer getKey() {
return key;
}
public static TestEnum findValueByTypeId(Integer key) {
return Arrays.stream(TestEnum.values()).filter(v ->
v.getKey().equals(key)).findFirst().orElseThrow(() ->
new Exception(String.format("Unknown TestEnum.key: '%s'", key)));
}
}
You should have a HashMap with the numbers as keys and the enum values as values.
This map can typically be in your repository. Then you can easily replace an int variable from the database with your preferred enum value.
If your keys (int values) are stored in a database, then I will say its bad design to carry those keys around in an enum on your business layer. If that's the case, I will recommend not to store the int value in the enum.
One solution is to add
public final Test[] TESTS = { null, ONE, TWO, THREE };
public static Test getByNumber(int i) {
return TESTS[i];
}
To the enum.
If the internal data is not an integer, you could have a Map which you populate in a static { ... } initializer. This map could later be used in the getByNumber method above.

Categories

Resources