如何处理Java中“未经检查的转换”可能引发的运行时异常?

4
所以我有一个类似于这样的函数:
@SuppressWarnings("unchecked")
public static <E> Set<E> getSetOfClass(Query q,Class<E> clazz) {
    return new LinkedHashSet<E>( q.getResultList() );
}

我认为这段代码的作用是将javax.persistence.Query中的结果集以泛型Set<Class>的形式返回。

这似乎是一个不错的解决方案,但首先,它确实是我想象的那样吗?这是实现该目标的最佳方式吗?我觉得奇怪的是我从未引用我的clazz参数,但它似乎确实做到了我想要的。

其次,如果这一切都是正确合理的,那么这个函数可能会出现什么错误?我想,如果我提供了一个错误的类,这个函数就无法运行,尽管我并不确定。

如果我执行以下操作:
Query q = em.createQuery("FROM Element");
Set<Fish> s = MyUtil.getSetOfClass( q, Fish.class);

如果Fish不是Element的超类,那会发生什么?我应该假设这个函数总是被正确使用,还是应该进行错误处理?人们推荐哪种最佳实践方法?

问候,

Glen x


1
关于未使用的 clazz,实际上,您不使用类参数是因为您不需要传递它。您可以省略该参数并调用方法: MyUtil.<Fish>getSetOfClass(q),但如果您不习惯这种语法,则会更奇怪。我建议按照 @Andreas_D 的建议使用 clazz 参数。 - Blaisorblade
3个回答

3

getSetOfClass不能保证Set中的所有元素都是类型为E的对象。如果您调用不正确(您总是可以这样做),例如:

 Set<Cat> cats = getSetOfClass(findAllFish(), Cat.class);

你以后会在各个地方收到类转换异常...

我建议在公共的getSetOfClass方法中添加一些检查,以保证集合的内容与类型参数相匹配:

@SuppressWarnings("unchecked")
public static <E> Set<E> getSetOfClass(Query q,Class<E> clazz) {
   return getSetOfClass(Query q,Class<E> clazz, true);
}

@SuppressWarnings("unchecked")
public static <E> Set<E> getSetOfClass(Query q,Class<E> clazz, boolean validate) {
    List result = q.getResultList();
    if (validate) {
       for (Object o:result) {
          if(!o.getClass().equals(clazz)) {
              // handle error
          }
       }
    }
    return new LinkedHashSet<E>( result );
}

1
也许 Class.isAssignableFrom(Class)Object.equals(Object) 更合适。 - nicholas.hauschild
是的,它会。这取决于需求 - “模式”适用于两个测试。 (通常情况下,如果我没有IDE进行测试,我会避免推荐isAssignable - 有50%的机会在错误的方向上使用它;)) - Andreas Dolk
我遇到了完全相同的问题 :) - nicholas.hauschild
嗯,试图理解isAssignableFrom函数的语义让我头疼。那么,如何处理这个错误呢?抛出我们自己的异常类?返回一个空集并输出日志记录器错误?有什么建议吗? - Link19

2

补充@Andreas_D的回答,要记住所有Java泛型信息仅用于在编译时检查代码的类型正确性,并在运行时擦除。因此,实际上您得到的是类似于以下内容:

public static Set<Object> getSetOfClass(Query q,Class<Object> clazz) {
  return new LinkedHashSet<Object>( q.getResultList() );
}

这意味着在运行时,就以上述方法而言,一切都能正常工作。
更新:如@Blaisorblade所指出的那样,getSetOfClass方法可以使用clazz来检查类型正确性,并在类型错误时立即失败。虽然这不能在编译时完成,但在运行时失败时,它将更容易地定位问题。
现在假设你稍后有:
Query q = em.createQuery("FROM Element");
Set<Fish> s = MyUtil.getSetOfClass( q, Fish.class);
for(Fish fish : s){
  fish.swim();
}

然后在运行时它将会看起来像:

Query q = em.createQuery("FROM Element");
Set<Object> s = MyUtil.getSetOfClass( q, Fish.class);
for(Object fish : s){
  ((Fish)fish).swim();
}

现在,如果元素是Cat类型,你可以看到会发生什么。如果运行到(Fish)fish部分,将会抛出一个ClassCastException异常。

因此,在编译器可以跟踪类型信息而不需要任何unchecked警告的情况下,泛型非常有用。对于其他情况(如您的情况),其中泛型被“篡改”在中间,它无法保证程序的正确性。这是不可避免的,特别是在数据持久化到磁盘或数据库的情况下,因为无法确定持久化的数据是否是正确的类型。程序员必须小心谨慎。


你的帖子很有趣,我想点赞,但在这种情况下,由于传递了一个 Class 实例(奇怪的是),你可以在运行时检查一切是否正常。当然,正如你所说,编译器无法检查:你需要正确地编写未经检查的代码。如果你在结论中加入这个观点,我会点赞的。 - Blaisorblade

1

我从来不确定自己是否喜欢泛型。在这种情况下,它们似乎是个好主意,而且可能会为您节省很多麻烦。由于持久化(尚)不支持它们,那么我会戴上反泛型帽子,并解释一下如何进行“真正的”Java编程。

因此,我的建议是放弃泛型和类,只返回一个简单的Set:

public static Set getSetOfClass( Query q )  {
    return new LinkedHashSet( q.getResultList() );
}

(如有必要,可以使用 @SuppressWarnings("unchecked"),如果您无法获得 1.4 编译器。)

如果查询中只包含 E,则您永远不会遇到问题。或者至少,问题很少见,在运行时很明显,并且最好由误用您的方法的程序员处理。没有什么比在运行时出现意外的 ClassCastException 更能说明“更改基本方法”的必要性。

如果其中偶尔有合法的非 E Fish 对象,则使用该方法的程序员比您更适合处理它们。他们可以在运行时检查并选择性地丢弃单个 fish 或将整个 Set 丢弃,以适应他们知道而您不知道的目的。

如果您确实知道他们的目的,则可以通过添加 Clazz 参数来为他们节省一些麻烦,以便您知道他们想要什么。然后,您可以进行过滤或返回 null 或抛出自己的已检查异常或返回一个特殊的类对象,该对象详细说明了 Set 内容的性质。但是,请确保您不要做比节省该方法用户更多的工作。


我提供一项服务,这些查询在DAO中,并且该服务提供了各种Set<E> getSet()方法,因此在某个时刻我必须进行这些转换。我选择将其隔离到单个Utilities类中,在那里可以在一个地方禁止任何警告,并使其他人保持他们的泛型。我认为这是一种相当干净的方式。 - Link19

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接