Java 将对象强制转换为泛型类型时未引发无效的类强制转换异常
我有一个项目,我正在处理很多泛型,但也需要类型安全性。 由于stacktrace大约有200个调用,当当前错误出现时,我恐怕无法提供所涉及的完整源代码。我将试图把它归结到我迄今为止发现的问题,并给出一个简单的例子 首先,有一些类,包含泛型值。每个泛型实现都具有可比性。这可能是一个实现此接口的长类、布尔类甚至自定义类 每个这样的类都有一个DatabaseID,它是一个枚举条目,包含某个类型的默认值大多数时候此数据库ID的默认值与Holdingclass的泛型类的类型相同 大概是这样的:Java 将对象强制转换为泛型类型时未引发无效的类强制转换异常,java,android,generics,casting,Java,Android,Generics,Casting,我有一个项目,我正在处理很多泛型,但也需要类型安全性。 由于stacktrace大约有200个调用,当当前错误出现时,我恐怕无法提供所涉及的完整源代码。我将试图把它归结到我迄今为止发现的问题,并给出一个简单的例子 首先,有一些类,包含泛型值。每个泛型实现都具有可比性。这可能是一个实现此接口的长类、布尔类甚至自定义类 每个这样的类都有一个DatabaseID,它是一个枚举条目,包含某个类型的默认值大多数时候此数据库ID的默认值与Holdingclass的泛型类的类型相同 大概是这样的: publi
public enum DatabaseId{
A_LONG_VALUE(0L),
A_BOOLEAN_VALUE(false),
A_INTERVAL("");
}
public void initialize() {
Database db = Database.getInstance();
try {
if (this.getValueHolderValueType() == Interval.class){
//Interval has an constructor for the string represenatation of it
this.setValue((T) new Interval((String)db.get(getDatabaseId()));
}else{
this.setValue((T) db.get(this.getDatabaseId()));
}
} catch (Exception ex) {
Log.e(LOG_TAG, "Invalid cast while initializing valueHolder",ex);
this.setValue(null);
}
}
Interval是实现可比较接口的自定义类。但间隔也是上述规则的一个例外。“Interval”的泛型保持类的泛型类型T为“Interval.Class”,而持久化该间隔的DatabaseValue将其存储为字符串。类似于离散值的[5250]
或[5]
现在,在某个时刻,我需要“加载”存储在数据库id后面的值,并将其注入holding的class CurrentValue属性中
由于这是在整个层次结构的“非常”父类上完成的,因此这也是以通用方式完成的:
public void initialize() {
Database db = Database.getInstance();
try {
this.setValue((T) db.get(this.getDatabaseId()));
} catch (Exception ex) {
Log.e(LOG_TAG, "Invalid cast while initializing Value Holder.", ex);
this.setValue(null);
}
}
对于上述示例,如果ValueHoler持有Interval.class
,而DatabaseId id提供了字符串
,那么这将明显失败。此时,我期望出现异常,因为我正在尝试将对象
(db.get交付对象
)强制转换为泛型类型T
(在本例中为Interval
)。然而,数据库条目的真正类型是String
但是:不会引发异常,因此永远不会执行setValue(null)
。应用程序将继续运行,直到调用ValueHolder.getCurrentvalue()
并将其与我期望的currentValue
类型相同的另一个值进行比较。(Interval
在这种情况下)
此时,JAVA注意到“CurrentValue”是一个字符串,并抛出一个异常:
java.lang.ClassCastException:无法将java.lang.String转换为
my.namespace.Interval
实际问题
我可以很容易地抓住这个szenario,就像这样:
public enum DatabaseId{
A_LONG_VALUE(0L),
A_BOOLEAN_VALUE(false),
A_INTERVAL("");
}
public void initialize() {
Database db = Database.getInstance();
try {
if (this.getValueHolderValueType() == Interval.class){
//Interval has an constructor for the string represenatation of it
this.setValue((T) new Interval((String)db.get(getDatabaseId()));
}else{
this.setValue((T) db.get(this.getDatabaseId()));
}
} catch (Exception ex) {
Log.e(LOG_TAG, "Invalid cast while initializing valueHolder",ex);
this.setValue(null);
}
}
但是,如果另一个开发人员出现并实现了一个新的类,他可能不会意识到“手动”转换,并遇到同样的问题
在撰写本文时,我找到了一个通知开发人员的解决方案,如下所示:
public enum DatabaseId{
A_LONG_VALUE(0L),
A_BOOLEAN_VALUE(false),
A_INTERVAL("");
}
public void initialize() {
Database db = Database.getInstance();
try {
if (this.getValueHolderValueType() == Interval.class){
//Interval has an constructor for the string represenatation of it
this.setValue((T) new Interval((String)db.get(getDatabaseId()));
}else{
this.setValue((T) db.get(this.getDatabaseId()));
}
} catch (Exception ex) {
Log.e(LOG_TAG, "Invalid cast while initializing valueHolder",ex);
this.setValue(null);
}
}
-首先进行非泛型转换
-比较两种类型
-如果无法进行常规强制转换,则引发通知
if (this.getValueHolderValueType() == Interval.class){
this.setValue((T) new Interval((String)db.get(getDatabaseId())));
}else if (this.getValueHolderValueType() != this.getDatabaseId().getType()){
Log.e(LOG_TAG, "Invalid cast while initializing ValueHolder. DatabaseId delivers '"+this.getDatabaseId().getType()+"', but valueHolder expects '"+this.getValueHolderValueType()+"'. Maybe you are missing a non-generic-cast right here?");
}else{
this.setValue((T) db.get(this.getDatabaseId()));
}
因此,唯一一个悬而未决的问题是:
为什么是this.setValue((T)db.get(this.getDatabaseId())
NOT引发异常,当T的类型为Interval
时,DatabaseID的值的类型为String
,db.get返回一个对象
为什么在我尝试访问“错误类”而不是执行强制转换时抛出异常?因为Java泛型是在编译器级别实现的,它将所有内容转换为
对象(或其他绑定),并在使用该对象的代码中插入适当的强制转换,而不是简单地传递它的代码
编辑:为了解决您的问题,您可能希望在持有者中有一个抽象方法,该方法返回它可以存储的正确类型,并将其与从数据库返回的值的类型进行比较(一个=
比较或其他类型),然后抛出异常(或执行其他操作)如果比较失败
编辑:例如:
Object candidate = db.get(this.getDatabaseId());
boolean canBeStored = this.getStorageType().isInstance(candidate);
...
编辑:我看到您已经有了类似的逻辑。为了让它更有效,我建议:
Object original = db.get(this.getDatabaseId());
T modified = this.adaptObjectToContainer(original);
...
protected T adaptObjectToContainer(Object original) {
if(this.getValueHolderValueType().isInstance(original)) {
return (T)original;
}
throw new ClassCastException(...);
}
// subclasses may do relevant conversions, throw an exception, etc
protected Interval adaptObjectToContainer(Object original) {
...
if(original instanceof String) {
return new Interval(original);
}
...
}
理想情况下,为了最大限度地减少代码,每个存储类型都可能有一个实用程序类,负责转换。正如前面的答案所指出的,setValue(Object)成功是因为
如果对您的应用程序有意义,您可以尝试以下方法,其中包含对预期类的引用:
class Foo<T> {
private final Class<T> klass;
private T value;
Foo(Class<T> klass) {
this.klass=klass;
}
void setValue(Object obj) {
value=klass.cast(obj);
}
}
class-Foo{
私人期末班;
私人T值;
傅(klass级){
这个.klass=klass;
}
无效设置值(对象对象对象){
值=klass.cast(obj);
}
}
然后
Foo-Foo=新的Foo(Integer.class);
foo.设定值(“bar”)//抛出异常
请参见您可以调试并查看db.get(this.getDatabaseId())
实际返回的内容。因为T
在运行时被Object
替换,基本上就像所有泛型一样。@LouisWasserman听起来有些逻辑。因此,这似乎是Java对泛型的类型擦除问题?@LouisWasserman,T
实际上被替换为T
的顶部,如果“每个泛型实现都是可比的”意味着我认为它的意思,那么它可能是可比的(必要的)“checked”--它们只是用来通知编译器类型。唯一保证会发生的检查(静态的,如果可能的话,或者动态的)是检查dbGet的结果是否可分配给setValue的参数。这就是我目前正在做的(并且它在c中工作。)if(this.getValueHolderValueType()!=this.getDatabaseId().getType()){
-我只是想知道,如果我不使用手动比较,为什么不会引发异常。但是我的答案是满意的