Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java Enum or HashMap for static values

Tags:

java

enums

I am generating a CSV file along with CTL file for use with sqlldr. The CTL file needs to know the names of the columns that I want to load, and my CSV file needs to know the default values of these fields.

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

/*
 * Models the CTL file
 */
public class ControlFile {
    ...
}

These 2 classes are initialised and used inside 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");
        ...
    }
}

The HashMap reduces the scope of the columns and will mean that they can only be used inside CSVExportFile. I do not plan to extend this functionality (all classes will be final), so I'm not sure that my enum gains me anything.

What are the arguments for/against each approach, is this a specific case where one is superior, or is one way always superior?

like image 834
Rossiar Avatar asked Jul 17 '15 10:07

Rossiar


3 Answers

I would always use an enum here as enum have an innate ordering while Maps do not.

By using an enum you can generate the CTL file from the enum itself and use the 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));
}
like image 80
OldCurmudgeon Avatar answered Nov 01 '22 09:11

OldCurmudgeon


I would prefer Enum - using a type will give you flexibility to extend and change your implementation, using abstraction and still keeping it encapsulated in your class. To use an example, what if at some later point you decide, that you need to format your dates differently for CTL file? With enum you can implement that with abstraction, what matters, 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());
    }
}

You may also wish to store a type of value for some reason, then you need only to add new property to your enum. With map approach, you would need actually a second map or to define a type to be used as map value.

And what if you discover, that you need different order for CSV and CTL? Well, with Map (Sorted one) it should be easy to create a differently sorted copy, but you can go with enum 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 thought of, where Map will be better, is when you actually do not want to iterate, but access chosen value - Map will get it faster.

like image 24
Marcin Kosinski Avatar answered Nov 01 '22 08:11

Marcin Kosinski


I would create a class with properties for column names like:

public class CSVRecord {
  private int id;
  private String name;

  // getters and setters here.
}

Where id and name are actual columns in csv file.

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

like image 1
Gondy Avatar answered Nov 01 '22 09:11

Gondy