使用@SuppressWarnings("未经检查")演示Java Generics会导致运行时异常

时间:2015-05-18 06:25:05

标签: java generics suppress-warnings

我有2个接口SubObs,如下所示:

public interface Sub<O extends Obs<? extends Sub>>{
   public void addObs(O o);

   public void removeObs(O o);

   public void notifyObs();
}

public interface Obs<S entends Sub<?>>{
   public void update(S s);
}

现在上面有2个具体的实现作为类Vie实现了Obs和类Mod,它实现了Sub,如下所示:

public class Mod implements Sub<Vie<Mod>>{

   private Vie<Mod>[] vies = new Vie<Mod>[0];//Here is the error.

   public void addObs(Vie<Mod> vie){
     vies = addToArray(vies, vie);
     //Some other code;
   }

   public void removeObs(Vie<Mod> vie){
     vies = removeFromArray(vies, vie);
     //Some other code;
   }

   public void notifyObs(){
      for(Vie<Mod> v : this.vies){
         v.update(this);
      } 
   }
}

public class Vie<M extends Mod> implements Obs<M>{

   private M mod;

   public void update(M){
      //some code;
   }

   public void setMod(M mod){
      this.mod.removeObs(this); //Here is the error.
      mod.addObs(this); //Here is the error.
      this.mod = mod;
   }
} 

在上面的Mod代码中,数组vies的初始化错误。适用的更正是:

@SuppressWarnings("unchecked")
private Vie<Mod>[] vies = (Vie<Mod>[])new Vie<Mod>[0];

对于Vie类的setMod方法,可以应用的更正是:

@SuppressWarnings("unchecked")
public void setMod(M mod){
   this.mod.removeObs((Vie<Mod>)this); 
   mod.addObs((Vie<Mod>)this); 
   this.mod = mod;
}

正如我们所看到的,上述两种情况都必须在程序使用之前显式地键入实例,我们还必须添加@SuppressWarnings("unchecked"),以便编译器不会抛出任何编译错误。

现在我对@SuppressWarnings("unchecked")的理解是我明确要求编译器在编译时不检查变量实例的类型。如果这是正确的,那么我可以遇到任何运行时ClassCastException

此上面的代码也可以修改,以便我不需要任何@SuppressWarnings("unchecked")

其他信息

我已更新代码以显示变量vies的使用情况。以上是观察者模式的基本实现。请注意,就Observer模式而言,这是完整的实现。我的意思是说在实际实现中,真正的类继承了其他类和接口,其方法在这里没有提到。但就viesmod变量而言,这是完整的。

2 个答案:

答案 0 :(得分:1)

创建参数化类型数组的问题与数组无法检查添加的元素是否为数组通常所做的正确类型。由于你只在课堂内使用阵列并且不将它暴露在外面,所以很好,你可以抑制警告。您使用的类型和警告的抑制是类的内部实现细节,外部代码并不关心。

this传递给removes()addObs的类型不匹配是一个更大的问题。 Vie<M>Vie<Mod>是不兼容的类型。不清楚为什么Vie是通用的。如果你没有使它成为通用的,那就可以了:

public class Mod implements Sub<Vie> {

   private Vie[] vies = new Vie[0];

   public void addObs(Vie vie) {
     //vies = addToArray(vies, vie);
     //Some other code;
   }

   public void removeObs(Vie vie) {
     //vies = removeFromArray(vies, vie);
     //Some other code;
   }

   public void notifyObs() {
      for (Vie v : this.vies) {
         v.update(this);
      } 
   }
}

public class Vie implements Obs<Mod> {

   private Mod mod;

   public void update(Mod mod) {
      //some code;
   }

   public void setMod(Mod mod) {
      this.mod.removeObs(this);
      mod.addObs(this);
      this.mod = mod;
   }
}

如果您希望此代码能够用于ModVie子类,那么它会更复杂。

答案 1 :(得分:0)

您正在抑制的编译器警告旨在警告由于类型擦除而可能出现的错误。类型擦除意味着在运行时,参数化类不是参数化的,而是普通类型。这意味着运行时知道VieVie,但它无法检查它在mod中保存的对象类型(直到您获取它为止)。

这里解释了一般问题类别:Java GenericsFAQ。您的示例类似于给定的Wrapper示例。

以下是使用传统类忽略未检查警告的危险示例:

    ArrayList<String> as = new ArrayList<String>();
    ArrayList<Integer> ai = new ArrayList<Integer>();

    ArrayList ao1;
    ArrayList ao2;

    as.add("Hello");

    ao1 = as;
    ao2 = ai;

    ao2.add(ao1.get(0));

    Integer i = ai.get(0); // Class cast exception, even though no casting done

您的代码模糊地相似,我认为理解上面的示例有助于了解您正在做什么。当您将Vie<M extends Mod>个对象放到Vie<Mod>持有者时,您不会在提供的代码中看到任何错误。问题在于,当你从Vie<Mod>持有者那里得到东西时,你认为它们可能是Vie<ParticularM>个对象,这不一定是真的。

您目前没有任何方法获取Vie对象,但如果您这样做,则在运行时编译器将无法告知{{1}对象的确切类型包装,所以你对Vie所做的任何强制转换都是对编译器的建议,实际上不能强制执行。

在你的代码中,它由Vie类本身保存在一起,它可以调节对Mod类的访问,并确保(我认为)你总是在数组中放置一致的对象。

但是你的代码并不完整,你只需要一步就可以添加一些允许将Vie<M>对象放在你认为是Vie<M2 extends Mod>个对象的数组中的代码。

因此,总而言之,我认为当前代码是安全的,因为它是孤立的,但任何更改都可能引入编译器无法检测到的错误。