Java Enum or HashMap for Static Values

I am creating a CSV file along with a CTL file for use with sqlldr . The CTL file must know the column names that I want to load, and my CSV file must know the default values ​​for these fields.

 /* * Models a line in the CSV file */ public class CSVRecord { ... } /* * Models the CTL file */ public class ControlFile { ... } 

These 2 classes are initialized and used inside the CSVExportFile , and I have 2 approaches:

1. Enum

 public enum Columns { ID("1"), NAME("Bob"), ... } 

2. HashMap

 public class CSVExportFile { private HashMap<String, String> columns; public CSVExportFile() { columns = new HashMap<String, String>(); columns.put("ID", "1"); columns.put("Name", "Bob"); ... } } 

HashMap reduces the size of the columns and will mean that they can only be used inside the CSVExportFile . I do not plan to distribute this functionality (all classes will be final ), so I'm not sure if my enum gets me something.

What are the arguments / against each approach, is this a concrete case when one is superior or one of the methods is always superior?

+6
source share
5 answers

I would always use enum here, since enum is inherently ordered, but Map is not.

Using enum , you can generate a CTL file from the enumeration itself and use enum values ​​as factories to populate your csv file.

 class MyObj { final String foreName; final String surname; public MyObj(String foreName, String surname) { this.foreName = foreName; this.surname = surname; } public String getForeName() { return foreName; } public String getSurname() { return surname; } } enum Column { Forename { @Override String fromMyObj(MyObj it) { return it.getForeName(); } }, Surname { @Override String fromMyObj(MyObj it) { return it.getSurname(); } },; abstract String fromMyObj(MyObj it); static String asSelectStatement(Set<Column> columns, String tableName) { return join(columns, ",", "SELECT ", " FROM " + tableName); } static String asCSVHeader(Set<Column> columns) { return join(columns, ","); } static String asCSV(Set<Column> columns, MyObj it) { return join(columns, (Column a) -> a.fromMyObj(it), ","); } private static String join(Set<Column> columns, String between) { return join(columns, new StringJoiner(between)); } private static String join(Set<Column> columns, String between, String prefix, String suffix) { return join(columns, new StringJoiner(between, prefix, suffix)); } private static String join(Set<Column> columns, StringJoiner joined) { return join(columns, (Column a) -> a.name(), joined); } private static String join(Set<Column> columns, Function<Column, String> as, String between) { return join(columns, as, new StringJoiner(between)); } private static String join(Set<Column> columns, Function<Column, String> as, String between, String prefix, String suffix) { return join(columns, as, new StringJoiner(between, prefix, suffix)); } private static String join(Set<Column> columns, Function<Column, String> as, StringJoiner joined) { for (Column c : columns) { joined.add(as.apply(c)); } return joined.toString(); } // Also simple to auto-populate prepared statements, build INSERT statements etc. } public void test() { Set<Column> columns = EnumSet.of(Column.Forename, Column.Surname); System.out.println("As Select: " + Column.asSelectStatement(columns, "MyTable")); System.out.println("As CSV Header: " + Column.asCSVHeader(columns)); MyObj it = new MyObj("My Forename", "My Surname"); System.out.println("As CSV: " + Column.asCSV(columns, it)); } 
+4
source

When developing an application, you always think about what may change in the future. It is possible that the order of your columns will change, or you will get more columns. The main thing that you get when using Map (you must also program the Map interface, not the HashMap ) is the ability to easily store this configuration outside of your application.

Separating the configuration from the application logic can be very useful, and even if you are not planning it at the moment, you can do it at some point in the future.

Using enumerations gives you more static control over your code, so it is a little easier to avoid errors (but you should write tests independently).

In short:

Positive cards:

  • Easy configuration
  • Configuration can be stored outside the system
  • Easier to extend
  • You can write a common code / framework for this task.

Negative map properties:

  • It's a little easier to make a mistake

Positive Enum Values:

  • Easier not to do msitakes static checking

Negative Enum Values:

  • You set your configuration at runtime
  • Difficult to expand

To solve the map, I really recommend the SortedMap interface instead of a HashMap with TreeMap being the actual implementation. If order matters.

+1
source

I would create a class with properties for column names, for example:

 public class CSVRecord { private int id; private String name; // getters and setters here. } 

If id and name are actual columns in csv file.

Then create a list of your List<CSVRecord> csvRecordList .

+1
source

I would prefer Enum - using a type will give you the flexibility to extend and modify your implementation, use abstraction and store it in your class. To use the example, what if at some point you decide that you need to format the dates differently for the CTL file? With an enumeration, you can implement this with an abstraction, which is important when you have ten dates and one hundred columns:

 public enum Column { ID("1"), NAME("Bob"), DATE_OF_BIRTH("1980-01-01", "yyyy-MM-dd", "yyyyMMdd"); private String defaultValue; private String ctlDefaultValue; Column(String defaultValue) { this.defaultValue = defaultValue; } Column(String defaultValue, String csvFormat, String ctlFormat) { this(defaultValue); try { this.ctlDefaultValue = new SimpleDateFormat(ctlFormat) .format(new SimpleDateFormat(csvFormat) .parseObject(defaultValue)); } catch (ParseException e) { this.ctlDefaultValue = ""; } } public String valueForCTL() { return ctlDefaultValue == null ? defaultValue : ctlDefaultValue; } public String valueForCsv() { return defaultValue; } public static void main(String[] args) { System.out.println(DATE_OF_BIRTH.valueForCTL()); System.out.println(DATE_OF_BIRTH.valueForCsv()); } } 

For some reason, you can also save the value type, then you only need to add a new property to your list. When using the map approach, you will actually need a second map or determine the type that will be used as the map value.

So what if you find that you need a different order for CSV and CTL? Well, using Map (Sorted one) should be easy to create a differently sorted copy, but you can go with an enumeration with the same ease:

 public enum ColumnDifferentOrder{ ID("1", 3), NAME("Bob", 2), DATE_OF_BIRTH("1980-01-01", 1); private String defaultValue; private int csvOrder; ColumnDifferentOrder(String defaultValue, int csvOrder) { this.defaultValue = defaultValue; this.csvOrder = csvOrder; } public static ColumnDifferentOrder[] orderForCsv() { ColumnDifferentOrder[] columns = ColumnDifferentOrder.values(); Arrays.sort(columns, new Comparator<ColumnDifferentOrder>() { @Override public int compare(ColumnDifferentOrder o1, ColumnDifferentOrder o2) { return o1.csvOrder - o2.csvOrder; } }); return columns; } public static ColumnDifferentOrder[] orderForCtl() { return ColumnDifferentOrder.values(); } public static void main(String[] args) { System.out.println(Arrays.toString(ColumnDifferentOrder.orderForCsv())); System.out.println(Arrays.toString(ColumnDifferentOrder.orderForCtl())); } } 

The only thing I can think about where Map will be better is when you really do not want iteration, but access to the selected value - Map will be faster.

0
source

I would never use an enumeration in this way, since enumeration types are designed to store various possible values ​​that a field can handle, and not to name them.

I hope I have correctly explained.

-1
source

All Articles