静态值的 Java 枚举或哈希映射

2022-09-04 03:20:28

我正在生成一个CSV文件以及CTL文件,以便与.CTL 文件需要知道我要加载的列的名称,而我的 CSV 文件需要知道这些字段的默认值。sqlldr

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

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

这2个类被初始化并在内部使用,我有2种方法:CSVExportFile

1. 枚举

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

2. 哈希地图

public class CSVExportFile {
    private HashMap<String, String> columns;

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

减小了列的范围,并且意味着它们只能在 内部使用。我不打算扩展此功能(所有类都将是),所以我不确定我是否为我带来了什么。HashMapCSVExportFilefinalenum

支持/反对每种方法的论据是什么,这是一种优越者的具体情况,还是一种方式总是优越的?


答案 1

我总是在这里使用一个,因为有一个与生俱来的顺序,而s则没有。enumenumMap

通过使用,您可以从枚举本身生成 CTL 文件,并将值用作工厂来填充 csv 文件。enumenum

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));
}

答案 2

我更喜欢Enum - 使用类型将使您灵活地扩展和更改实现,使用抽象并仍然将其封装在类中。举个例子,如果在以后的某个时候,你决定,你需要为CTL文件设置不同的日期格式,该怎么办?使用枚举,你可以用抽象来实现它,当你有十个日期和一百列时,什么是重要的:

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());
    }
}

您可能还出于某种原因希望存储一种类型的值,然后只需向枚举添加新属性即可。使用映射方法,您实际上需要第二个映射或定义要用作映射值的类型。

如果您发现 CSV 和 CTL 需要不同的顺序,该怎么办?好吧,使用Map(排序的副本)应该很容易创建一个不同排序的副本,但是您可以同样轻松地使用枚举:

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()));

    }
}

我唯一能想到的,Map会更好的地方,是当你实际上不想迭代,而是访问所选值时 - Map会更快地获得它。