Replace String Literals If/elseIf block with Enum - java

I'm new to using Java Enums and I've read that replace IF logic that compares String literals should be replaced with an Enum. I don't quite understand how to replace my below code with an Enum, any ideas? Based on the col value being passed into applyEQ, I need to do a base the next method call on it's value. I do know the possible values of col ahead of time and I'm using a constants file for now. Should I create an Enum and place it in my Interface of Constants file?
public class FilterHelper implements IFilterHelper {
private final EQuery eQuery;
public FilterHelper(EQuery query) {
eQuery = query;
}
#Override
public void applyEQ(String col, String val) throws Exception {
int return = 0;
if (col.equalsIgnoreCase(EConstants.NAME)) {
ret = Sample.addName(eQuery, val);
} else if (col.equalsIgnoreCase(EConstants.KEYWORDS)) {
ret = Sample.addKey(eQuery, val);
} else if (col.equalsIgnoreCase(EConstants.ROLE)) {
ret = Sample.addRole(eQuery, val);
}
if (return != 0) {
throw new Exception("failed");
}
}
}
EConstants.java
public final class EConstants {
public static final String NAME = "cewName";
public static final String KEYWORDS = "cewKeywords";
public static final String ROLE = "cewRole";
}

First create an enum:
public enum EConstants {
CEWNAME,
CEWROLE,
CEWKEYWORDS;
}
Then convert col String to this enum and use switch:
public void applyEQ(String col, String val) throws Exception {
int ret = 0;
final EConstants constant = EConstants.valueOf(col.toUpperCase());
switch(constant) {
case CEWNAME:
ret = Sample.addName(eQuery, val);
break;
case CEWROLE:
ret = Sample.addRole(eQuery, val);
break;
case CEWKEYWORDS:
ret = Sample.addKey(eQuery, val);
break;
default:
throw new Exception("Unhandled enum constant: " + constant);
}
}
Note that EConstants.valueOf() can throw IllegalArgumentException if col.toUpperCase() does not match any of constant values.
BTW I hate local variables initialized in multiple places (and break keyword), try extracting method:
final EConstants constant = EConstants.valueOf(col.toUpperCase());
final int ret = processSample(val, constant);
And the method itself:
private int processSample(String val, EConstants constant) throws Exception {
switch(constant) {
case CEWNAME:
return Sample.addName(eQuery, val);
case CEWROLE:
return Sample.addRole(eQuery, val);
case CEWKEYWORDS:
return Sample.addKey(eQuery, val);
default:
throw new Exception("Unhandled enum constant: " + constant);
}
}

You can rewrite your EConstants as enum:
public enum EConstants {
NAME, KEYWORDS, ROLE
}
And evaluate condition using switch statement:
// col has type of EConstants
switch (col) {
case NAME:
// do something
break;
case KEYWORDS:
// do something
break;
case ROLE:
// do something
break;
default:
// what to do otherwise
break;
}

The great thing about Java Enums is that they provide language level support for the type safe enum pattern, because among other things it allows you to define methods and even override them. So you could do this:
public enum CewColumn {
NAME("cewName") {
#Override
public int add(EQuery eQuery, String val) {
return Sample.addName(eQuery, val);
}
},
KEYWORDS("cewKeywords") {
#Override
public int add(EQuery eQuery, String val) {
return Sample.addKey(eQuery, val);
}
},
ROLE("cewRole") {
#Override
public int add(EQuery eQuery, String val) {
return Sample.addRole(eQuery, val);
}
};
private final String colName;
private MyColumn(String colName) {
this.colName = colName;
}
private static final Map<String, CewColumn> COLUMNS = new HashMap<>(values().length);
static{
for (CewColumn cewColumn : values()){
COLUMNS.put(cewColumn.colName, cewColumn);
}
}
public abstract int add(EQuery eQuery, String val);
public static CewColumn getCewColumn(String colName){
return COLUMNS.get(colName);
}
}
Then you can use it like this:
CewColumn cewColumn = CewColumn.getCewColumn(colName);
if (cewColumn != null){
int ret = cewColumn.add(eQuery, val);
}
-> You replaced the switch statement with polymorphism!

it is best to create a Enum.
public Enum AvailableCols{
COL_1,
COL_2;
}
and convert the procedure as
public void applyEQ(AvailableCols col, String val) throws Exception {
switch(col){
case COL1:
...
If you still want the string to be preserved you can see the following post

Basically create an enum and change the type of col and use equals() or == to compare the value of col against the enum values. Alternatively you could use a switch statement but I doubt that would make your code more readable for only 3 constants.
Example:
enum EConstants {
NAME,
KEYWORDS,
ROLE;
}
public void applyEQ(EConstants col, String val) throws Exception {
if( col == EConstants.NAME ) {
...
}
....
}
//or
public void applyEQ(EConstants col, String val) throws Exception {
if( EConstants.NAME.equals(col) ) { //col might be null
...
}
....
}
//or
public void applyEQ(EConstants col, String val) throws Exception {
switch( col ) {
case NAME:
...
break;
case ROLE:
...
}
}

http://docs.oracle.com/javase/tutorial/java/javaOO/enum.html
If your raw data is a string, you will still need to do a string comparison to assign the enum. This might be faster if you do a lot of comparisons on the result data, but if not, it simply adds complication to your code.
You can iterate over the values of the enum like a collection, which gives you an advantage when you need to add constants. That's not bad.
Here is how to do it:
public enum EConstants {
NAME, KEYWORDS, ROLE
}
...
public EConstants setConstant(String from) {
if (from.equalsIgnoreCase("cewName")) {
return NAME;
} else if (col.equalsIgnoreCase("cewKeywords")) {
return KEYWORDS;
} else if (col.equalsIgnoreCase("cewRole")) {
return ROLE;
}
}
You preprocess your data that way and now when you are trying to figure out logic you can use a switch on the enum type value.

Here is a trick for you. No switch/case (just come up with a better name for EConstants).
public enum EConstants {
NAME,
KEYWORDS,
ROLE;
private interface Applier {
void apply(EQuery query, String val);
}
public void apply(EQuery query, String val) {
map.get(this).apply(query, val);
}
private static Map<EConstants, Applier> map = new HashMap<EConstants, EConstants.Applier>();
static {
map.put(NAME, new Applier() {
#Override
public void apply(EQuery query, String val) {
Sample.addName(query, val);
}
});
map.put(KEYWORDS, new Applier() {
#Override
public void apply(EQuery query, String val) {
Sample.addKey(query, val);
}
});
map.put(ROLE, new Applier() {
#Override
public void apply(EQuery query, String val) {
Sample.addRole(query, val);
}
});
}
}
Now you just write:
#Override
public void applyEQ(EConstants econs, String val) {
econs.apply(equery, val);
}

Related

Setting an enum by int value [duplicate]

This question already has answers here:
Getting enum associated with int value
(8 answers)
Closed 5 years ago.
I have an enum with an int value for storage in a DB:
public enum DayType {
REGULAR(1), VACATION(2), SICK(3);
private final int value;
private DayType(int value)
{
this.value = value;
}
public int getValue() {
return value;
}
}
And here is a setter for this enum:
private DayType dayType
public void setDayType(int dayType) {
switch(dayType) {
case 1:
this.dayType = DayType.REGULAR;
break;
case 2:
this.dayType = DayType.VACATION;
break;
case 3:
this.dayType = DayType.SICK;
break;
default :
this.dayType = DayType.REGULAR;
break;
}
Everything works just fine. But there has to be a more "pretty" way to write the setter if there is a int value for each enum value. Without using switch case...
In this particular case you don't need to explicitly specify int value for each element, but just treat its constants as array:
private DayType dayType = DayType.REGULAR;
public void setDayType(int dayType) {
if (dayType <= DayType.values().length) this.dayType = DayType.values()[dayType - 1];
}
The usual way is to add a static method in the enum
public enum DayType {
REGULAR(1), VACATION(2), SICK(3);
private final int value;
private DayType(int value)
{
this.value = value;
}
public int getValue() {
return value;
}
public static DayType byValue(int value) {
Arrays.stream(DayType.values())
.filter(dt -> dt.value == value)
.findFirst()
.orElse(DayType.REGULAR);
}
}
Usage
private DayType dayType
public void setDayType(int dayType) {
this.dayType = DayType.byValue(dayType);
}
You can use the values of .ordinal() and values[index] to acess the values.
Instead of having the value field in the enum, store its DayType.REGULAR.ordinal() value.
So to load:
this.dayTipe = DayType.values[dayType];
The only downside here is to update the Enum, you need to add all new ones after the last one, so its indexes never conflicts.
You can avoid use of switch statement by using
public void setDayType(int type) {
for(DayType dayType : DayType.values()) {
if(dayType.getValue() == type) {
this.dayType = dayType;
}
}
}
If you needed performance rather than memory you can use HashMap:
private HashMap<Integer, DayType> dayTypes;
private void initDayTypes() {
for(DayType type : DayType.values()) {
dayTypes.put(type.value, type);
}
}
Usage will be the next:
public void setDayType(int type) {
this.dayType = dayTypes.get(type);
}

JAVA: Parsing String to static final int value

I have simple problem, but I'm not able to fix it. I have this interface...
public interface KeyInput extends Input {
public static final int TEST1 = 0x01;
public static final int TEST2 = 0x02;
}
...this string variable...
String inputString = "TEST1";
...and this method.
public void doSomething(int _input) {
}
I want to parse inputString variable to KeyInput static final int value. So that I could call....
doSomething(KeyInput.parse(inputString));
I know the enum valueOf, but this doesn't work here...
If you have only these two (or any other fixed number of) values, you might just enumerate them in switch:
public static int parse(String input) {
int res = -1;
switch (input) {
"TEST1":
res = TEST1;
break;
"TEST2":
res = TEST2;
break;
// ... other options
default: throw new IllegalArgumentException("unknown string");
}
}
The other option is to keep this values inside some map, so you can do this:
private static final Map <String, Integer> TESTS = new HashMap<>();
static {
TESTS.put("TEST1", 0x01);
TESTS.put("TEST2", 0x02);
// ...
}
public static int parse(String input) {
if (TESTS.containsKey(input))
return TESTS.get(input);
else
throw new IllegalArgumentException("unknown string");
}
Still, if you see the enums as an option in your case, I can consider this solution:
public enum Keys {
TEST1(0x01), TEST2(0x02);
int value;
private Keys(int value) {
this.value = value;
}
public getValue() {
return value;
}
}
Here you'll just do valueOf as you suggesed:
public static int parse(String input) {
return Keys.valueOf(input).getValue();
}
If all these options is now for your case, you should use reflection (though, I'm quite sure, it's not the case):
public static int parse(String input) {
Field[] fields = KeyInput.class.getDeclaredFields();
for (Field field : fields) {
if (Modifier.isStatic(fields.getModifiers()) && field.getDeclaringClass().equals(int.class) && field.getName().equals(input)) {
return field.getInt(null);
}
}
throw new IllegalArgumentException("unknown string");
}

How to compare an input string with an enum?

I have my below enum -
public enum TestEnum {
h1, h2, h3, h4;
public static String forCode(int code) {
return (code >= 0 && code < values().length) ? values()[code].name() : null;
}
public static void main(String[] args) {
System.out.println(TestEnum.h1.name());
String ss = "h1";
// check here whether ss is in my enum or not
}
}
Now what I want to check is given a String h1, I need to see whether this String h1 is in my enum or not? How would I do this using the enum?
You should avoid using ordinals for your enum. Rather give a value to each enum constant, and have a field.
So, your enum should look like:
public enum TestEnum {
h1("h1"), h2("h2"), h3("h3"), h4("h4");
private final String value;
TestEnum(String value) { this.value = value; }
public static TestEnum forValue(String value) {
// You can cache the array returned by `values()` in the enum itself
// Or build a map from `String` to `TestEnum` and use that here
for (TestEnum val: values()) {
if (val.value.equals(value)) {
return val;
}
}
}
}
And then for a given String, you can check if it's valid value or not like this:
String value = "h1";
TestEnum enumValue = TestEnum.forValue(value);
if (enumValue == null) {
System.out.println("Invalid value");
}
Easiest approach:
try {
TestEnum.valueOf(ss);
System.out.println("valid");
} catch (IllegalArgumentException e) {
System.out.println("invalid");
}
Setting values for each enum seemed unnecessary to me. Here's a solution that uses toString() instead of .value, which I feel is a bit simpler:
public class tester {
public static void main(String[] args) {
TestEnum enumValue = TestEnum.forValue("X1");
if (enumValue == null) {
System.out.println("Invalid value");
} else {
System.out.println("Good value");
}
}
public enum TestEnum {
X1, X2, X#;
public static TestEnum forValue(String value) {
for (TestEnum val : values())
if (val.toString().equals(value))
return val;
return null;
}
}

java generics T extends Simpletype?

I'd like to write a method, that does return something of a PrimitiveType like float, integer, boolean and also String if possible. I'd like to use generics for it but i stuck and dont find a solution for it. I do need it for a Configparser. Ill use it to get different values from the Config.
Current it des look like this and i know that the switch does not work like this but you get an idea of what id like to do:
public class ConfigurationManager extends XmlReader {
private final static String FILE_PATH = "config/config.cfg";
private static Element xml;
public ConfigurationManager() throws IOException {
FileHandle handle = Gdx.files.internal(FILE_PATH);
this.xml = this.parse(handle);
}
public Resolution getResolution() {
Resolution r = new Resolution();
r.height = xml.getFloat("height");
r.width = xml.getFloat("width");
return r;
}
public static <T> T getConfig(Class<T> type, String name) {
if (type.equals(Integer.class)) {
return type.cast(xml.getInt(name));
} else if (type.equals(Float.class)) {
return type.cast(xml.getFloat(name));
} else if (type.equals(Boolean.class)) {
return type.cast(xml.getBoolean(name));
} else if (type.equals(String.class)) {
return type.cast(xml.get(name));
}
throw new AssertionError("Invalid type");
}
}
Thanks alot
Well, I don't think you can do it with primitive types directly, but how about something like this:
public static <T> T getConfig(Class<T> type, String name) {
if(type.equals(Integer.class)){
return type.cast(xml.getInteger(name));
} else if(type.equals(Float.class)){
return type.cast(xml.getFloat(name));
} else if(type.equals(Double.class)) {
return type.cast(xml.getDouble(name));
} else if(type.equals(String.class)) {
return type.cast(xml.getString(name));
}
throw new AssertionError("Invalid type");
}
You could use an Enum to avoid the branching logic and the explicit casting.
public enum TypeSelector {
INTEGER() {
#Override
public Integer getValue(Elements xml, String name) {
return xml.getInteger(name);
}
},
DOUBLE() {
#Override
public Double getValue(Elements xml, String name) {
return xml.getDouble(name);
}
};
private static final Map<Class<?>, TypeSelector> SELECTORS = new HashMap<Class<?>, TypeSelector>() {
{
put(Integer.class, INTEGER);
put(Double.class, DOUBLE);
}
};
public static <T> TypeSelector getSelectorForType(Class<T> c) {
TypeSelector selector = SELECTORS.get(c);
if (selector == null) {
throw new AssertionError("Invalid type");
}
return selector;
}
public abstract <T> T getValue(Elements xml, String name);
}

How to genericize a Java enum with static members?

I am refactoring a part of our legacy app which handles exporting and importing of DB tables from/to Excel sheets. We have a Formatter subclass for each table, to provide the definition of that table: how many columns it has, and what is the name, format and validator of each column. The getters which supply this data are then called by a Template Method which exports/imports the table. I have extracted the column data into an enum, which greatly simplified the code. A formatter now looks like this (some details omitted for brevity):
public class DamageChargeFormatter extends BaseFormatter {
public static final int NUM_COLUMNS = 7;
public enum Column {
VEHICLE_GROUP(0, "Vehicle Group", /* more params */),
NAME_OF_PART(1, "Name of Part", /* more params */),
//...
LOSS_OF_USE(6, "Loss of Use", /* more params */);
private static final Map<Integer, Column> intToColumn = new HashMap<Integer, Column>();
static {
for (Column type : values()) {
intToColumn.put(type.getIndex(), type);
}
}
public static TableColumn valueOf(int index) {
return intToColumn.get(index);
}
private int index;
private String name;
Column(int index, String name, /* more params */) {
this.index = index;
this.name = name;
//...
}
public int getIndex() { return index; }
public String getName() { return name; }
// more members and getters...
}
protected String getSheetName() {
return "Damage Charges";
}
public String getColumnName(int columnNumber) {
TableColumn column = Column.valueOf(columnNumber);
if (column != null) {
return column.getName();
}
return null;
}
// more getters...
protected int getNumColumns() {
return NUM_COLUMNS;
}
protected boolean isVariableColumnCount() {
return false;
}
}
Now, I have about a dozen such classes, each of which containing exactly the same code except that NUM_COLUMNS and the enum values of Column are different. Is there any way to genericize this somehow? The main obstacle to this is the static Column.valueOf() method and the static constant NUM_COLUMNS. Another concern with latter is that it really belongs to an abstraction one level higher, i.e. to the table, not to an individual column - it would be nice to somehow incorporate this into the generic solution.
Technically I could solve this with a base interface (TableColumn below) and reflection, but I don't really like that, as apart from trading compile time errors to runtime errors, it makes the code ugly (to me):
public class GenericFormatter<E extends TableColumn> extends BaseFormatter {
private Method valueOfMethod;
public GenericFormatter(Class<E> columnClass) {
try {
valueOfMethod = columnClass.getDeclaredMethod("valueOf", Integer.class);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
public String getColumnName(int columnNumber) {
try {
#SuppressWarnings("unchecked")
E elem = (E) valueOfMethod.invoke(columnNumber);
if (elem != null) {
return elem.getName();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return null;
}
//...
}
Note that this code is purely experimental, as yet untested...
Is there a nicer, cleaner, safer way?
May be, something like this:
public class TableMetadata<E extends Enum & TableColumn> {
private Map<Integer, TableColumn> columns = new HashMap<Integer, TableColumn>();
public TableMetadata(Class<E> c) {
for (E e: c.getEnumConstants()) {
columns.put(e.getIndex(), e);
}
}
public String getColumnName(int index) {
return columns.get(index).getName();
}
}
public class GenericFormatter<E extends TableColumn> extends BaseFormatter {
private TableMetadata<E> m;
public GenericFormatter(TableMetadata<E> m) {
this.m = m;
}
public String getColumnName(int columnNumber) {
return m.getColumnName(index);
}
//...
}
EDIT: Enum added to the type parameter for more compile-time safety

Categories

Resources