【问题标题】:Is there a way to avoid @SuppressWarnings in this code?有没有办法在这段代码中避免@SuppressWarnings?
【发布时间】:2010-09-16 19:25:46
【问题描述】:

有没有办法避免在下面使用@SuppressWarnings 并在没有警告的情况下保持相同的功能'Type safety: Unchecked cast from AbstractDO[] to E[]'

public MyClass {
  ...
  private Map<Class<? extends AbstractDO>, AbstractDO[]> map;
  ...
  private void saveConcreteDOs(AbstractDO[] theEntities) {        
    entityMap.put(theEntities[0].getClass(), theEntities);
  }

  @SuppressWarnings("unchecked")
  protected <E extends AbstractDO> E[] getConcreteDOs(Class<E> theType) {
    return (E[]) map.get(theType);
  }
  ...
}

也许增强地图声明?

【问题讨论】:

  • 以防万一,请忽略 NPE 和类似的错误 - 代码当然是缩短版
  • 数组和泛型不能很好地混合,最好用 List 代替。
  • 你能把MyClass设为通用吗?
  • @starblue。更改为列表不会删除警告,因为 OP 需要强制转换为 List&lt;E&gt;。但是列表更可取,因为您可以返回unmodifiableList
  • 是的,这只是一般性评论。我认为 grigory 正在做的是最佳实践,以单独的方法隔离演员表并将@SuppressWarnings("unchecked") 应用于该方法。

标签: java arrays generics collections casting


【解决方案1】:

您有一个选择:要么抑制您知道将始终成功的强制转换的警告,要么避免警告并使用 try/catch 块验证强制转换是否成功。

只有这两种选择。

也许有办法增强地图声明?

在您的情况下,我会说您有几个选择。

我认为您最好的选择是在您的 getConcreteDOs 方法中添加一个 throws ClassCastException 子句,并让调用者处理由于该方法的无效使用而导致的无效转换——假设他们可以编译它你的extends AbstractDO 子句。这有一个不幸的副作用,即强制消费者将调用包装在 try/catch 块中或声明他们自己的 throws 子句以强制 try/catch 块在堆栈中更高。

你可以用一个空的 catch 块吞下异常;坦率地说,我更喜欢@SuppressWarning。

或者您可以完全放弃该方法,只处理抽象实体,从而有效地让您的存储库的使用者处理演员表。

底线:每当您尝试构建通用存储库时,都会遇到这些问题。使用每实体类型的具体存储库模式可能会更好。

【讨论】:

  • 也许有办法增强地图声明?
  • 打败我;我要说的是,无论何时你从父级转换为子级,总会收到此警告,因为编译器无法保证类型兼容。
  • 您的意思是“@SuppressWarnings”注释会影响运行时行为? (无论是否“施放成功”)我理解正确吗?现在,这是一些新闻。
  • @Nikita Rybak:不,@SuppressWarnings 不会影响运行时行为。它用于那些通常担心编译器警告但理解在这种情况下,由于编译器无法理解的东西,警告是没有根据的。只有当您知道警告无效时,您才应该禁止此类警告。如果转换失败的可能性很小,您应该使用 try/catch 块验证转换是否成功,或者声明无效的转换异常可能会冒泡(在 Java 中使用 throws 子句,或在 C# 中使用文档,例如示例)。
  • 你错了。例如,&lt;T&gt; T cast(Object o){return (T)o;} 在运行时实际上并没有做任何事情。它不会在那个地方抛出 ClassCastException 。当调用者尝试将返回的对象用作T 时,就会发生异常。如果调用者忽略返回值,也不例外。
【解决方案2】:

您可以通过使您的类成为通用类来避免未经检查的强制转换,例如

public class MyClass<E extends AbstractDO> {

    private Map<Class<? extends AbstractDO>, E[]> map;

    public void saveConcreteDOs(E[] theEntities) {        
      map.put(theEntities[0].getClass(), theEntities);
    }

    public E[] getConcreteDOs(Class<E> theType) {
      return map.get(theType);
    }
}

【讨论】:

  • 我认为当我使用 X.class 作为参数调用它时,其目的是返回一个 X[],因此映射将包含每个类和相应数组的一个条目。您的提议意味着强制所有数组子类 E...
  • @grigory,那么如果没有未经检查的演员表,你就无法逃脱。
【解决方案3】:

首先,您的代码不是类型安全的。它可以在运行时抛出类转换异常。你应该有

私人无效 saveConcreteDOs(AbstractDO[] theEntities) { entityMap.put(theEntities.getClass().getComponentType(), theEntities); }

在运行时你可能只有同构数组,并且 element[0] 的类型与数组组件类型相同。但是,仅通过检查这个类是无法知道这一点的。

有了这个更正的代码,超级智能的编译器可以证明getConcreteDOs() 是类型安全的。然而 javac 并不那么聪明。语言规范要求它发出警告。

一般来说,Java 中无法表达更复杂的键和值之间的关系。值是一个以组件类型为键的数组的不变量,只保留在您的脑海中。

现在,看看这个非数组版本:

private Map<Class<? extends AbstractDO>, AbstractDO> map;

protected <E extends AbstractDO> E getConcreteDOs(Class<E> theType) 
{
  AbstractDO obj = map.get(theType);
  return theType.cast(obj);
}

这没有任何警告,但它有点作弊。 Class.cast() 为我们隐藏警告,仅此而已。

对数组版本没有帮助,Class&lt;T&gt;中没有T[] castArray(Object[])。您可以自己制作一种方法,有效地将警告隐藏在其中。

或者你可以这样做,但它真的是不必要的俗气。如果您知道自己在做什么并且仔细检查了程序以确保类型安全,请不要害怕未经检查的强制转换警告。

protected <E extends AbstractDO> E[] getConcreteDOs(Class<E[]> arrayType) 
{
  AbstractDO[] array = map.get(arrayType.getComponentType());
  return arrayType.cast(array);
}
...
X[] array = getConcreteDOs(X[].class);

【讨论】:

  • 我同意避免警告和@SupressWarnings 本身并不是一个目标。目标是拥有清晰、充分和不言自明的代码。这就是为什么我喜欢你的“不要害怕未经检查的强制转换警告,如果你知道你在做什么并且你仔细检查了你的程序以确保类型安全。”
  • 关于类型安全的第一个注释我不清楚。 theEntities[0].getClass() 怎么可能不会转换为 Class&lt;? extends AbstractDO&gt;
  • @grigory 说 B 是 A 的子类,如果 array = new A[]{ new B(), new A() },你的代码会认为数组是 B[]
  • 我明白你的意思。但是我的代码没有对 array 的类型做出任何假设 - 只是对它的 第一个元素 做出假设。感谢您的回答,因为到目前为止它最有帮助。
  • 当我尝试这个时:map.put(theEntities.getClass().getComponentType(), theEntities); 我得到编译错误:方法 put(Class extends AbstractDO>, AbstractDO[]) 在类型 Map,AbstractDO[]> 不适用于参数 (Class, E[])
猜你喜欢
  • 1970-01-01
  • 2013-04-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多