Java 将对象强制转换为泛型类型时未引发无效的类强制转换异常

Java 将对象强制转换为泛型类型时未引发无效的类强制转换异常,java,android,generics,casting,Java,Android,Generics,Casting,我有一个项目,我正在处理很多泛型,但也需要类型安全性。 由于stacktrace大约有200个调用,当当前错误出现时,我恐怕无法提供所涉及的完整源代码。我将试图把它归结到我迄今为止发现的问题,并给出一个简单的例子 首先,有一些类,包含泛型值。每个泛型实现都具有可比性。这可能是一个实现此接口的长类、布尔类甚至自定义类 每个这样的类都有一个DatabaseID,它是一个枚举条目,包含某个类型的默认值大多数时候此数据库ID的默认值与Holdingclass的泛型类的类型相同 大概是这样的: publi

我有一个项目,我正在处理很多泛型,但也需要类型安全性。 由于stacktrace大约有200个调用,当当前错误出现时,我恐怕无法提供所涉及的完整源代码。我将试图把它归结到我迄今为止发现的问题,并给出一个简单的例子

首先,有一些类,包含泛型值。每个泛型实现都具有可比性。这可能是一个实现此接口的长类、布尔类甚至自定义类

每个这样的类都有一个DatabaseID,它是一个枚举条目,包含某个类型的默认值大多数时候此数据库ID的默认值与Holdingclass的泛型类的类型相同

大概是这样的:

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()){
-我只是想知道,如果我不使用手动比较,为什么不会引发异常。但是我的答案是满意的