Best Pranctice, in Java to keep some hardcoded info - java

If I have some many constants like shortName="rule1"; descrition="description1"; rule2/description2; rule3 / description3? and I need to find out the description for a certain shortName, given by a parameter, what would be the best way to do this?
Would it be a enum like this?
public enum Description {
RULE1 ("rule1", "description1"),
RULE2 ("rule2", "description2"),
RULE3 ("rule3", "description3");
private final String shortName;
private final String description;
Description(String shortName, String description) {
this.shortName= shortName;
this.description = description;
}
}
But if I have a method like private String getDescription(String shortName) how can I use the enum to get the description of a shortName declared in enum?
I can't use constants because I have ~200 definitions like this.

An enum defines a type
You asked:
and I need to find out the description for a certain shortName, given by a parameter, what would be the best way to do this?
Would it be a enum like this?
Yes, if those rule1, rule2, and such are all conceptually members of the same type.
For example:
public enum Pet{ DOG , CAT , BIRD , HAMSTER }
public enum Flavor{ VANILLA , CHOCOLATE, STRAWBERRY }
public enum Color{ BURLYWOOD , CORNFLOWER_BLUE, DARK_SLATE_GREY }
Using an enum such as those means you can write other code that is type-safe, ensures valid values, and is more self-documenting.
pictureMaker.displayIceCreamCone( Flavor.CHOCOLATE )
On the other hand, if your values are unrelated, just a hodgepodge of various strings for various purposes, I would use string constants. And if they are resources for localization, use specific localization tools.
You asked:
But if I have a method like private String getDescription(String shortName) how can I use the enum to get the description of a shortName declared in enum?
That question suggests you are passing around the text of the short name as a key to finding the description. But you should not be passing around some string, you should be passing around the enum object. Take, for example, java.time.DayOfWeek enum. You should be passing around DayOfWeek.SATURDAY rather than "SATURDAY".
But if you must, you could implement a static method on your enum to loop through all the enum objects to find one that matches.
// Utility method to loop all the enum objects until finding a match.
public static String getLongStringForShortName ( String shorty )
{
String result = null;
if ( RULE1.shortName.equals( shorty ) ) { result = RULE1.description; }
if ( RULE2.shortName.equals( shorty ) ) { result = RULE2.description; }
if ( RULE3.shortName.equals( shorty ) ) { result = RULE3.description; }
return result;
}
Or, in alternative syntax, use streams to softcode references to each and every enum object.
// Utility method to loop all the enum objects until finding a match.
public static String getLongStringForShortName ( String shorty )
{
String result = "";
Optional < Description > optionalDesc = Arrays.stream( Description.values() ).filter( ( Description d ) -> d.description.equals( shorty ) ).findFirst();
if ( optionalDesc.isPresent() ) { result = optionalDesc.get().description; }
return result;
}
Map
But that code has a smell about it. You likely have the wrong data structure if you do this often, or this is your main purpose. This looks like we are abusing the enum where instead should be using a Map.
Map < String, String > descriptions =
Map.of(
"rule1" , "description1" ,
"rule2" , "description2" ,
"rule3" , "description3"
)
;
String longDesc = descriptions.get( "rule1" );
EnumMap
You could mix the concepts of enum and map. Your Question lacks the context to know if this is right for you or not. But FYI…
Change your enum class to just this:
package work.basil.example;
public enum Description
{
RULE1, RULE2, RULE3;
}
Use an EnumMap to map each of these enum objects to some other object such as a string.
Map < Description, String > descriptionToLongForm = new EnumMap <>( Description.class );
descriptionToLongForm.put( Description.RULE1 , "description1" );
descriptionToLongForm.put( Description.RULE2 , "description2" );
descriptionToLongForm.put( Description.RULE3 , "description3" );
String longDesc = descriptionToLongForm.get( Description.RULE2 );
Or, in alternative syntax, using Map.of. This produces a non-modifiable map.
Map < Description, String > descriptionToLongForm =
Map.of(
Description.RULE1 , "description1" ,
Description.RULE2 , "description2" ,
Description.RULE3 , "description3"
);
String longDesc = descriptionToLongForm.get( Description.RULE2 );

Related

Best way to find if String is not in the list of ENUMs [duplicate]

This question already has answers here:
Java: Check if enum contains a given string?
(32 answers)
Closed 3 years ago.
I need to find if given String is not in the list of ENUMs.
These Strings come back with spaces, i.e.: "CHILD CARE", "CREDIT CARDS", etc...
Any other ExpenseType should be mapped to OTHER, except HOA. HOA should be completely ignored.
My ENUMs are as follows:
public enum ExpenseType {
AUTOLOAN("AUTO LOAN"),
ALIMONY("ALIMONY"),
CHILDCARE("CHILD CARE"),
CREDITCARDS("CREDIT CARDS"),
INSTALLMENTLOANS("INSTALLMENT LOANS"),
FOOD("FOOD"),
UTILITIES("UTILITIES"),
TRANSPORTATION("TRANSPORTATION"),
OTHER("OTHER");
private String expenseType;
ExpenseType(String expenseType) {
this.expenseType = expenseType;
}
#Override public String toString() {
return this.expenseType;
}
}
The way I am doing this now is as follows:
String expenseDescription = expense.getExpenseDesc().replaceAll(" ", "");
if(EnumUtils.isValidEnum(ExpenseType.class, expenseDescription)) {
monthlyExpenses.setType(ExpenseType.valueOf(expenseDescription).toString());
}
else if(!expenseDescription.equals("HOA")) {
monthlyExpenses.setType(ExpenseType.OTHER.toString());
}
Does anyone know a better way to do this?
Why not use getEnum to get Enum if applicable (check for null or use Optional
if needed)
ExpenseType monthlyExpenses = EnumUtils.getEnum(ExpenseType.class, expenseDescription);
Gets the enum for the class, returning null if not found.
This method differs from Enum.valueOf(java.lang.Class, java.lang.String) in that it does not throw an exception for an invalid enum name.
Also prefer adding to enum a code (String) as a reference, which won't contains spaces and special characters, e.g.
//...
CHILDCARE("CHILD_CARE","CHILD CARE"),
//...
private String expenseType;
private String expenseTypeCode;
ExpenseType(String expenseType, String expenseTypeCode) {
this.expenseType = expenseType;
this.expenseTypeCode = expenseTypeCode;
}
Here is another alternative.
Map<String, String> codes = Arrays.stream(ExpenseType.values()).collect(
Collectors.toMap(ExpenseType::toString, ExpenseType::name));
codes.put("HOA","TBD");
String[] submittedCodes = { "CREDIT CARDS", "FOOD", "UTILITIES", "UNKNOWN"
};
for (String c : submittedCodes) {
String expenseType = codes.getOrDefault(c,"OTHER");
System.out.println(expenseType);
}
First, I didn't see a reason to remove the spaces from the submitted code unless you are concerned about addition of extra spaces creeping in. In that case you should probably also remove tabs.
Since you are already using the enum value to compare to the name, I figured I would just ignore the name as they are effectively the same.
The map is used only to allow a null to be returned in case of a missing key.
The enum was only used to conveniently populate the map.
I did not know exactly how to handle HOA. But all of this is just another alternative for you to possibly investigate as you can rearrange the keys and values, etc to suit your requirements.

Sorting by translated enum

I'm having a following problem: I have to sort entities by enum parameter. The thing is, that enum name is not equivalent to its translated name, for example, the enum values can be:
enum Sample {
Bus, Car, Train
}
However, let's say in my language, Bus corresponds to pks, Car to auto, and Train to ciuchcia, co their order should be:
Car, Train, Bus and not Bus, Car, Train. It's just an example, my problem involves something like 10 different values.
The problem is, I can't get all the data, then perform a sort in Java, because the data is paginated. I tried to solve this problem by doing this in SQL (the data is from database view):
(CASE sample WHEN 'Car' THEN 1 WHEN 'Train' THEN 2 WHEN 'Bus' THEN 3 ELSE 0 END)
I'm sorting by number, and this solution works. However, I feel like this can be done better, and doesn't need to be modified each time I want to add something. Any help would be very appreciated.
If you can hard-code or populate the translation in the enum you can make the enum generate the query.
enum Sample {
Bus("pks"), Car("auto"), Train("ciuchcia");
private final String localName;
Sample(String localName) {
this.localName = localName;
}
private static final List<Sample> inLocalOrder = Arrays.stream(values())
.sorted((a,b) -> a.localName.compareTo(b.localName))
.collect(Collectors.toList());
public static CharSequence inLocalOrder() {
StringBuilder sb = new StringBuilder("(CASE");
int i = 1;
inLocalOrder.stream().forEach(a -> sb.append(" WHEN '"+a.name()+"' THEN "+i));
sb.append(" ELSE 0 END)");
return sb;
}
}
public void test(String[] args) {
System.out.println(Sample.inLocalOrder());
}
prints:
(CASE WHEN 'Car' THEN 1 WHEN 'Train' THEN 2 WHEN 'Bus' THEN 3 ELSE 0 END)
If the translations happen later then a minor adjustment should suffice.
First things first. Why you're using an Enum to sort stuff? I can't agree with this feature, but it's ok if you really need it.
I really appreciate #OldCurmudgeon's answer, but I would have done something different.
Instead of using the property value as the column name, I would use a method to request the correct column name (obviously I'm supposing that you don't have the entities mapped inside your code, so you're using an Enum to sort them). This way:
enum Sample {
Bus, Car, Train;
public String getColumnName() {
// GET IT FROM SOME RESOURCE OR REQUEST IT FROM ANOTHER CONTEXT OR JUST RETURN IT THE WAY IT IS
// YOU CAN USE A RESOURCE FILE TO MAP YOUR COLUMN NAME, SO IF THE ENTITY CHANGES, YOU DON'T NEED
// TO UPDATE YOUR CODE...
return "";
}
public static String getSortSQL(Map<Sample, Integer> samples) {
StringBuilder sb = new StringBuilder("(CASE");
samples.forEach((sample, number) -> addSampleNumberSQL(sb, sample, number));
return sb.append(" ELSE 0 END)").toString();
}
private static StringBuilder addSampleNumberSQL(StringBuilder sb, Sample sample, Integer number) {
return sb.append(" WHEN '").append(sample.getColumnName()).append("' THEN ").append(number);
}
}
As you can see, you can request your column name from any resource you want. But this implementation still weak because if you need to sort by any new column, you will need to add another Enum value. So I would implement something more powerfull, like a properties reader that read each property from the resource and a method that receive an String (mean the property itself) and a number (to sort by the number) so I would do something like this:
public static String getSortSQL(Map<String, Integer> properties) {
StringBuilder sb = new StringBuilder("(CASE");
properties.forEach((prop, number) -> addSampleNumberSQL(sb, prop, number));
return sb.append(" ELSE 0 END)").toString();
}
private static StringBuilder addSampleNumberSQL(StringBuilder sb, String property, Integer number) {
return sb.append(" WHEN '").append(property).append("' THEN ").append(number);
}
Hope it helps you...

How to get an String value from another String

This could sound strange but actually is quite simple.
Short description: I have a class variable called
public static final String ACCELEROMETER_X = "AccX";
In one function I do this, which get me "ACCELEROMETER_X" from a enum (sensors is an arrayList of my enum).
for i...
columns = columns + sensors.get(i).name()
The point is I want to introduce in columns not "ACCELEROMETER_X", but "AccX". Any idea? I know I could do it using switch and cases but my enum has more than 30 values so Id rather prefer other "cleaner" way to do it.
If you want your enum constant to be replaced with that string value, a better way would be keep that string as a field in the enum itself:
enum Sensor {
ACCELEROMETER_X("AccX"),
// Other constants
;
private final String abbreviation;
private Sensor(final String abbreviation) {
this.abbreviation = abbreviation;
}
#Override
public String toString() {
return abbreviation;
}
}
And instead of:
sensors.get(i).name()
use this:
sensors.get(i).toString()
Solution 1 : If you keep the value in class
Create a Map with key as the id (ACCELEROMETER_X) and value as "AccX" and when you get the value from the Enum use that to find the value from the map using name() as the key.
Map<String, String> map = new HashMap<String,String>();
map.put("ACCELEROMETER_X","AccX");
//some place where you want value from name
map.get(enumInstance.name());
Solution 2: Change the enum (more preferable)
enum Some{
ACCELEROMETER_X("AccX");
}
In your enum add this
public String getAccelerometerX (){
return ACCELEROMETER_X ;
}
and then:
for i... columns = columns + sensors.get(i).getAccelerometerX ()
I think what you're trying to do is to get the value of a field from its name which you have in a String, if that's the case, you could do something like this:
public static final String ACCELEROMETER_X = "AccX";
// ...
Field field = MyClassName.class.getField("ACCELEROMETER_X");
String value = (String)field.get(new MyClassName());
System.out.println(value); // prints "AccX"
Of course you'll have to catch some Exceptions.

Using switch in Enum

What if I'll use switch in getByIntValue()? Is it really neccessary to use a SparseArray?
public enum Gender {
Unknown(0),
Male(1),
Female(2);
private static final SparseArray<Gender> lookupTable = new SparseArray<Gender>();
static {
for (final Gender gender : EnumSet.allOf(Gender.class)) {
lookupTable.put(gender.intValue, gender);
}
}
private final int intValue;
public static Gender getByIntValue(int val) {
return lookupTable.get(val);
}
private Gender(int intValue) {
this.intValue = intValue;
}
public int getIntValue() {
return intValue;
}
}
Since your int values go from 0 to 2, without hole, you could indeed simply use an array. A switch would also be fine, although it would probably be slightly slower than an array lookup. But unless you call the method billions of times, it won't make any noticeable difference. Use what you find the clearest and easiest to understand and maintain.
If you have posted realistic int values, then you don't need to set them explicitly on each enum member, and don't need switch. Just use
Gender.values()[intValue]
List.copyOf( EnumSet.allOf( Gender.class ) )
Caveat: This exercise in optimization seems silly for all but the most extreme scenario, as mentioned by JB Nizet. For real work, I would probably recommend the solution seen in the Answer by Marko Topolnik. But, for fun, I swung a bat at this ball.
Seems the goal is to render a static unmodifiable collection with very fast access by the given numbers 0, 1, 2.
As of Java 10, we have these new implemented (“default”) methods on the List interface: List.of & List.copyOf. These produce an unmodifiable collection. Though the backing implementation is undocumented and subject to change, I will assume it is something akin to an array with similar performance. Performance might even be faster than a conventional array, if the backing implementation detected the presence of an EnumSet and used some kind of bit vector.
I populate the List by passing an EnumSet to List.copyOf( Collection ).
So, this:
private static final SparseArray<Gender> lookupTable = new SparseArray<Gender>();
static {
for (final Gender gender : EnumSet.allOf(Gender.class)) {
lookupTable.put(gender.intValue, gender);
}
}
…becomes this:
private static final List < Gender > lookupTable = List.copyOf( EnumSet.allOf( Gender.class ) );
Entire class, with main for demo.
package com.basilbourque.example;
import java.util.EnumSet;
import java.util.List;
public enum Gender {
UNKNOWN( 0 ),
MALE( 1 ),
FEMALE( 2 );
private static final List < Gender > lookupTable = List.copyOf( EnumSet.allOf( Gender.class ) );
private final int intValue;
public static Gender getByIntValue ( int val ) {
return lookupTable.get( val );
}
public int getIntValue () {
return intValue;
}
// Constructor
private Gender ( int intValue ) {
this.intValue = intValue;
}
public static void main ( String[] args ) {
// Testing.
System.out.println( Gender.UNKNOWN.intValue );
System.out.println( Gender.getByIntValue( 0 ) );
System.out.println( "----" );
System.out.println( Gender.MALE.intValue );
System.out.println( Gender.getByIntValue( 1 ) );
System.out.println( "----" );
System.out.println( Gender.FEMALE.intValue );
System.out.println( Gender.getByIntValue( 2 ) );
}
}
When run.
0
UNKNOWN
1
MALE
2
FEMALE
By the way, as the biological default, FEMALE should come before MALE.

Java days of week calculation

I have an Enum for Days of week (with Everyday, weekend and weekdays) as follows where each entry has an int value.
public enum DaysOfWeek {
Everyday(127),
Weekend(65),
Weekdays(62),
Monday(2),
Tuesday(4),
Wednesday(8),
Thursday(16),
Friday(32),
Saturday(64),
Sunday(1);
private int bitValue;
private DaysOfWeek(int n){
this.bitValue = n;
}
public int getBitValue(){
return this.bitValue;
}
}
Given a TOTAL of any combination of the values, what would be the simplest way to calculate all individual values and make an arraylist from it. For example given the number 56 (i.e. Wed+Thur+Fri), how to calculate the days.
The correct way to represent a collection of enum values is to use an EnumSet. This uses a bit vector internally. But exposing such an implementation detail as in your code is not a good idea. We're doing OO here, not bit-twiddling.
Additionally, you are mixing the concepts of a single value and a collection of values, which will likely lead to headaches down the road.
Example using the DayOfWeek enum built into Java 8 and later.
EnumSet<DayOfWeek> weekend = EnumSet.of( DayOfWeek.SATURDAY , DayOfWeek.SUNDAY );
Boolean isTodayWeekend = weekend.contains( LocalDate.now().getDayOfWeek() );
As Michael suggested do not expose this implementation detail to the outside world.
Create a static method that converts int bitmask to EnumSet:
public static EnumSet< DaysOfWeek > fromBitValues (
final int origBitMask
)
{
final EnumSet< DaysOfWeek > ret_val =
EnumSet.noneOf( DaysOfWeek.class );
int bitMask = origBitMask;
for ( final DaysOfWeek val : DaysOfWeek.values( ) )
{
if ( ( val.bitValue & bitMask ) == val.bitValue )
{
bitMask &= ~val.bitValue;
ret_val.add( val );
}
}
if ( bitMask != 0 )
{
throw
new IllegalArgumentException(
String.format(
"Bit mask value 0x%X(%d) has unsupported bits " +
"0x%X. Extracted values: %s",
origBitMask,
origBitMask,
bitMask,
ret_val
)
);
}
return ret_val;
}
You may also need a static method that converts an EnumSet to a bit mask, I leave this exercise to the reader.
Also, looking at your enum, Everyday, Weekends and Weekdays do not belong there. They are aggregates of you other DaysOfWeek values and as such should be defined as EnumSets.

Categories