可能是之前曾经问过的一个问题,但是像往常一样,你提到泛通这个词你会得到一千个答案来解释类型擦除。我很久以前就经历过那个阶段,现在知道很多关于泛型及其用途的知识,但这种情况稍微有些微妙。
我有一个表示电子表格中数据单元格的容器,它实际上以两种格式存储数据:作为显示的字符串,还有另一种格式,取决于数据(存储为对象)。单元格还包含一个转换器,它在类型之间进行转换,并且还对类型进行有效性检查(例如,IntegerTransformer检查字符串是否为有效整数,如果返回则存储整数,反之亦然)。
单元格本身没有输入,因为我希望能够更改格式(例如,将辅助格式更改为float而不是整数或原始字符串),而无需使用新类型重建单元格对象。之前的尝试确实使用了泛型类型,但是一旦定义了编码变得非常庞大并且有很多反射,就无法改变类型。
问题是:如何以键入的方式从我的Cell中获取数据?我进行了实验,发现使用泛型类型可以使用方法完成,即使没有定义约束
public class Cell {
private String stringVal;
private Object valVal;
private Transformer<?> trans;
private Class<?> valClass;
public String getStringVal(){
return stringVal;
}
public boolean setStringVal(){
//this not only set the value, but checks it with the transformer that it meets constraints and updates valVal too
}
public <T> T getValVal(){
return (T) valVal;
//This works, but I don't understand why
}
}
让我失望的是:那是?它不能投射任何东西,没有类型T的输入限制它匹配任何东西,实际上它并没有真正说任何地方。拥有一个返回类型的Object只会在任何地方产生并发症。
在我的测试中我设置了一个Double值,它存储了Double(作为一个对象),当我做了Double testdou = testCell.getValVal();它立即起作用,甚至没有未经检查的施法警告。但是,当我做String teststr = testCell.getValVal()时,我得到了一个ClassCastException。真是不足为奇。
我在此看到两种观点:
一个:使用未定义的Cast似乎只不过是一种将模型放在方法内而不是在返回之后的方法。从用户的角度来看它非常简洁,但是该方法的用户必须担心使用正确的调用:所有这一切都是隐藏复杂的警告并检查直到运行时,但似乎有效。
第二种观点是:我不喜欢这段代码:它不干净,它不是我在写作时自豪的代码质量。代码应该是正确的,而不仅仅是工作。错误应该被捕获和处理,并且预期的接口应该是万无一失的,即使唯一的预测用户是我自己,并且我总是喜欢灵活的通用和可重用技术而不是笨拙的。问题是:有没有正常的方法来做到这一点?这是一种偷偷摸摸的方式来实现无类型,所有接受的ArrayList,它返回你想要的任何东西,而不是铸造?或者有什么我在这里失踪的。有些东西告诉我,我不应该相信这段代码!
或许更多的是一个比我想要的哲学问题,但我猜这就是我的要求。编辑:进一步测试。
我尝试了以下两个有趣的片段:
public <T> T getTypedElem() {
T output = (T) this.typedElem;
System.out.println(output.getClass());
return output;
}
public <T> T getTypedElem() {
T output = null;
try {
output = (T) this.typedElem;
System.out.println(output.getClass());
} catch (ClassCastException e) {
System.out.println("class cast caught");
return null;
}
return output;
}
当为typedElem分配一个double并尝试将它放入一个String时,我得到一个异常不在转换为,但在返回时,第二个代码片段不保护。 getClass的输出是java.lang.Double,表明它是从typedElem动态推断的,但是编译器级别类型检查只是被强制排除。
作为辩论的注释:还有一个获取valClass的函数,这意味着可以在运行时进行可分配性检查。
Edit2:结果
在考虑了选项后,我选择了两个解决方案:一个是轻量级解决方案,但是将函数注释为@depreciated,第二个是将传递给它的类,您想要尝试将其转换为。这种方式取决于具体情况。
答案 0 :(得分:20)
您可以尝试输入代币:
public <T> T getValue(Class<T> cls) {
if (valVal == null) return null;
else {
if (cls.isInstance(valVal)) return cls.cast(valVal);
return null;
}
}
请注意,这不会进行任何转换(例如,如果Double
是valVal
或Float
的实例,则无法使用此方法提取Integer
)。
你应该得到一个关于你getValVal
定义的编译器警告。这是因为在运行时无法检查强制转换(Java泛型通过“擦除”工作,这实际上意味着在编译后忘记了泛型类型参数),因此生成的代码更像是:
public Object getValVal() {
return valVal;
}
答案 1 :(得分:3)
正如您所发现的那样,使用Java类型系统可以表达的内容是有限的,即使使用泛型也是如此。有时,您希望使用类型声明断言的某些值的类型之间存在关系,但您不能(或者您可能会以过多的复杂性和冗长的冗长代码为代价)。我认为这篇文章中的示例代码(问答)很好地说明了这一点。
在这种情况下,如果将对象/字符串表示存储在“transformer”中,Java编译器可以执行更多类型检查。 (也许你必须重新考虑它是什么:也许它不仅仅是一个“变换器”。)在你的基类Transformer
类上放置一个通用的边界,并使它与“对象”的类型相同。 / p>
至于获取单元格的值 out ,编译器类型检查无法帮助您,因为值可以是不同的类型(并且您在编译时不知道时间将在给定单元格中存储什么类型的对象。)
我相信你也可以做类似的事情:
public <T> void setObject(Transformer<T> transformer, T object) {}
如果设置变换器和对象的唯一方法是通过该方法,则对参数进行编译器类型检查将阻止不兼容的转换器/对象对进入单元格。
如果我理解你在做什么,你使用的Transformer
的类型完全由单元格所持有的对象类型决定,是吗?如果是这样,我不是将变换器/对象设置在一起,而是仅为对象提供一个setter,并进行哈希查找以找到适当的变换器(使用对象类型作为键)。每次设置值或将其转换为String时,都可以执行哈希查找。无论哪种方式都可行。
这自然会导致错误类型的Transformer
无法传入。
答案 2 :(得分:1)
我认为你是一个静态类型的人,但是试试看:你有没有考虑过使用像groovy这样的动态语言?
从你的描述来看,在我看来,类型更能阻碍而不是帮助任何事情。
在groovy中,您可以让Cell.valVal
动态输入并轻松转换:
class Cell {
String val
def valVal
}
def cell = new Cell(val:"10.0")
cell.valVal = cell.val as BigDecimal
BigDecimal valVal = cell.valVal
assert valVal.class == BigDecimal
assert valVal == 10.0
cell.val = "20"
cell.valVal = cell.val as Integer
Integer valVal2 = cell.valVal
assert valVal2.class == Integer
assert valVal2 == 20
as
这里是最常见转换所需的一切。你也可以加你的。
如果需要转换其他代码块,请注意java的语法是有效的groovy语法,但do { ... } while()
块除外