问题描述
我正在生成一个CSV文件和CTL文件,以便与sqlldr
一起使用。
CTL文件需要知道我要加载的列的名称,我的CSV文件需要知道这些字段的默认值。
/*
* Models a line in the CSV file
*/
public class CSVRecord {
...
}
/*
* Models the CTL file
*/
public class ControlFile {
...
}
这两个类初始化并在CSVExportFile
,我有两种方法:
1.枚举
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
减少了列的范围,并且意味着它们只能在CSVExportFile
。
我不打算扩展这个功能(所有类都将是final
),所以我不确定我的enum
我带来什么。
对于每种方法有什么争论,这是一个特殊情况,其中一个是优越的,还是一种优越的方式?
1楼
我总是会在这里使用enum
,因为enum
有一个与生俱来的排序,而Map
则没有。
通过使用enum
您可以从枚举本身生成CTL文件,并使用enum
值作为工厂来填充csv文件。
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文件格式化日期,该怎么办? 使用enum,您可以使用抽象实现,重要的是,当您有十个日期和一百列时:
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(Sorted one),创建一个不同排序的副本应该很容易,但你可以轻松地使用enum:
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会更快。
3楼
在设计应用程序时,您始终会考虑将来可能发生的变化。
您的列的顺序可能会更改,或者您将获得更多列。
使用Map
时获得的主要内容(您还应该编程到Map
接口而不是HashMap
)是在应用程序之外轻松存储此配置的功能。
将配置与应用程序逻辑分离可能非常有用,即使您此时没有计划,也可能希望在将来的某个时候执行此操作。
使用枚举可以让您对代码进行更多的静态控制,这样可以更容易避免错误(但无论如何都应该编写测试)。
综上所述:
地图的积极因素:
- 易于更改配置
- 配置可以存储在系统外部
- 更容易扩展
- 可以为此任务编写通用代码/框架
地图的负面影响:
- 稍微容易犯错误
Enum的积极因素:
- 更容易不做msitakes,静态检查
Enum的否定:
- 您在complie时修复配置
- 更难延伸
对于Map解决方案,我实际上推荐使用接口而不是HashMap,而是实际的实现。 如果订单很重要。
4楼
我将创建一个具有列名属性的类,如:
public class CSVRecord {
private int id;
private String name;
// getters and setters here.
}
其中id和name是csv文件中的实际列。
然后创建记录List<CSVRecord> csvRecordList
。