干净的代码 - 避免对基于通用数据类型构建的集合进行显式类型转换
Clean code - Avoiding explicit type casts with collections built on generic data types
我正在使用地图从电子表格中读取行并按以下方式存储其内容:
public class DocumentRow {
private Map<Column, DocumentCell<?>> rowContents = new HashMap<>();
private int rowNum;
public DocumentCell<?> getValue(Column column) {
return rowContents.get(column);
}
public void setValue(Column column, DocumentCell<?> value) {
rowContents.put(column, value);
}
public DigitalDocument toDomainObject() {
DomainObject domainObject = new DomainObject();
domainObject.setTextValue((String) rowContents.get(TEXT_VALUE).getValue());
domainObject.setNumericValue((int) rowContents.get(NUMERIC_VALUE).getValue());
domainObject.setDateValue((LocalDate) rowContents.get(DATE_VALUE).getValue());
return domainObject;
}
}
public class DocumentCell<T> {
private T value;
}
public enum Column {
TEXT_VALUE("Text_Column_Name", DataType.STRING),
NUMERIC_VALUE("Numeric_Column_Name", DataType.NUMBER),
DATE_VALUE("Date_Column_Name", DataType.DATE);
}
(为简洁起见,我省略了一些明显的 类)
行值提供为:
row.setValue(column, new DocumentCell<>(getDateCellValue(spreadSheetCell)));
有什么方法可以使它更简洁,这样我在构建域对象时就不需要这些未经检查的强制转换了吗?或者更好的设计方法?
enum 的问题是你不能使用通用类型。您可以在 How to implement enum with generics?.
中看到更多相关信息
首先,我们需要创建一个 "enum-like" class,其中包含使用泛型类型的常量。
class Column<T> {
public static final Column<String> TEXT_VALUE = new Column<>("Text_Column_Name", String.class);
public static final Column<Number> NUMERIC_VALUE = new Column<>("Numeric_Column_Name", Number.class);
public static final Column<Date> DATE_VALUE = new Column<>("Date_Column_Name", Date.class);
String name;
Class<T> clazz;
private Column(String name, Class<T> clazz){
this.name = name;
this.clazz = clazz;
}
}
这样,我们可以确保在映射中插入值以匹配 Column
的类型,方法如下:
public <U> void setValue(Column<U> column, DocumentCell<U> value) {
rowContents.put(column, value);
}
示例:
DocumentRow row = new DocumentRow();
row.setValue(Column.TEXT_VALUE, new DocumentCell<String>("asdf"));
row.setValue(Column.TEXT_VALUE, new DocumentCell<Integer>(4)); //Don't compile, can't set an `Integer` document cell into a `Column.TEXT_VALUE`
现在,我们确定为 Column.TEXT_VALUE
插入的值将包含 String
,并且每个 Column
常量都相同。
由于我们在插入的时候已经保证了类型,所以我们可以dirty,将map中的DocumentCell<?>
强制转换为Column
的相同类型:
public <U> U getValue(Column<U> column) {
@SuppressWarnings("unchecked")
DocumentCell<U> doc = (DocumentCell<U>) rowContents.get(column);
return doc.getValue(column);
}
结果的一个小例子:
String s = row.getValue(Column.TEXT_VALUE);
Integer i = row.getValue(Column.TEXT_VALUE); //DON'T COMPILE : `row.getValue` will return a value of the type define by `Column`, here a `String`
完整的用法示例:
DocumentRow row = new DocumentRow();
row.setValue(Column.TEXT_VALUE, new DocumentCell<>("asdf"));
row.setValue(Column.NUMERIC_VALUE, new DocumentCell<>(4));
row.setValue(Column.DATE_VALUE, new DocumentCell<>(new Date()));
String s = row.getValue(Column.TEXT_VALUE);
Number i = row.getValue(Column.NUMERIC_VALUE);
Date d = row.getValue(Column.DATE_VALUE);
请注意,在上一个示例中,我没有为 DocumentCell
提供类型,编译器知道它将使用与之前的 Column
参数相同的类型。
您可以在 this ideone 项目中找到完整的代码。
当然,我们可以去掉"enum-like"部分,根据需要初始化Column
实例。我们需要做的就是将构造函数设置为可见(至少不是私有的)并且我们可以创建新的 "column type mappings"
Column<LocalDateTime> colDate = new Column<>("A new Date", LocalDateTime.class);
row.setValue(colDate , new DocumentCell<>(LocalDateTime.now()));
我正在使用地图从电子表格中读取行并按以下方式存储其内容:
public class DocumentRow {
private Map<Column, DocumentCell<?>> rowContents = new HashMap<>();
private int rowNum;
public DocumentCell<?> getValue(Column column) {
return rowContents.get(column);
}
public void setValue(Column column, DocumentCell<?> value) {
rowContents.put(column, value);
}
public DigitalDocument toDomainObject() {
DomainObject domainObject = new DomainObject();
domainObject.setTextValue((String) rowContents.get(TEXT_VALUE).getValue());
domainObject.setNumericValue((int) rowContents.get(NUMERIC_VALUE).getValue());
domainObject.setDateValue((LocalDate) rowContents.get(DATE_VALUE).getValue());
return domainObject;
}
}
public class DocumentCell<T> {
private T value;
}
public enum Column {
TEXT_VALUE("Text_Column_Name", DataType.STRING),
NUMERIC_VALUE("Numeric_Column_Name", DataType.NUMBER),
DATE_VALUE("Date_Column_Name", DataType.DATE);
}
(为简洁起见,我省略了一些明显的 类)
行值提供为:
row.setValue(column, new DocumentCell<>(getDateCellValue(spreadSheetCell)));
有什么方法可以使它更简洁,这样我在构建域对象时就不需要这些未经检查的强制转换了吗?或者更好的设计方法?
enum 的问题是你不能使用通用类型。您可以在 How to implement enum with generics?.
中看到更多相关信息首先,我们需要创建一个 "enum-like" class,其中包含使用泛型类型的常量。
class Column<T> {
public static final Column<String> TEXT_VALUE = new Column<>("Text_Column_Name", String.class);
public static final Column<Number> NUMERIC_VALUE = new Column<>("Numeric_Column_Name", Number.class);
public static final Column<Date> DATE_VALUE = new Column<>("Date_Column_Name", Date.class);
String name;
Class<T> clazz;
private Column(String name, Class<T> clazz){
this.name = name;
this.clazz = clazz;
}
}
这样,我们可以确保在映射中插入值以匹配 Column
的类型,方法如下:
public <U> void setValue(Column<U> column, DocumentCell<U> value) {
rowContents.put(column, value);
}
示例:
DocumentRow row = new DocumentRow();
row.setValue(Column.TEXT_VALUE, new DocumentCell<String>("asdf"));
row.setValue(Column.TEXT_VALUE, new DocumentCell<Integer>(4)); //Don't compile, can't set an `Integer` document cell into a `Column.TEXT_VALUE`
现在,我们确定为 Column.TEXT_VALUE
插入的值将包含 String
,并且每个 Column
常量都相同。
由于我们在插入的时候已经保证了类型,所以我们可以dirty,将map中的DocumentCell<?>
强制转换为Column
的相同类型:
public <U> U getValue(Column<U> column) {
@SuppressWarnings("unchecked")
DocumentCell<U> doc = (DocumentCell<U>) rowContents.get(column);
return doc.getValue(column);
}
结果的一个小例子:
String s = row.getValue(Column.TEXT_VALUE);
Integer i = row.getValue(Column.TEXT_VALUE); //DON'T COMPILE : `row.getValue` will return a value of the type define by `Column`, here a `String`
完整的用法示例:
DocumentRow row = new DocumentRow();
row.setValue(Column.TEXT_VALUE, new DocumentCell<>("asdf"));
row.setValue(Column.NUMERIC_VALUE, new DocumentCell<>(4));
row.setValue(Column.DATE_VALUE, new DocumentCell<>(new Date()));
String s = row.getValue(Column.TEXT_VALUE);
Number i = row.getValue(Column.NUMERIC_VALUE);
Date d = row.getValue(Column.DATE_VALUE);
请注意,在上一个示例中,我没有为 DocumentCell
提供类型,编译器知道它将使用与之前的 Column
参数相同的类型。
您可以在 this ideone 项目中找到完整的代码。
当然,我们可以去掉"enum-like"部分,根据需要初始化Column
实例。我们需要做的就是将构造函数设置为可见(至少不是私有的)并且我们可以创建新的 "column type mappings"
Column<LocalDateTime> colDate = new Column<>("A new Date", LocalDateTime.class);
row.setValue(colDate , new DocumentCell<>(LocalDateTime.now()));