【问题标题】:Java: substituting a subclass/subtype of a parameter when overiding a method?Java:重写方法时替换参数的子类/子类型?
【发布时间】:2010-12-11 17:23:48
【问题描述】:

所以我之前问过这个问题,但我在代码中犯了一个大多数人都知道的错误,而不是问题本身。

无论如何,我正在尝试覆盖类中的接口方法。但是,我希望覆盖方法中的参数类型是覆盖方法中定义的参数类型的子类。

界面是:

public interface Observer {
 public void update(ComponentUpdateEvent updateEvent) throws Exception;
}

而重写此方法的类是:

public class ConsoleDrawer extends Drawer {

//...

 @Override
 public void update(ConsoleUpdateEvent updateEvent) throws Exception {
  if (this.componentType != updateEvent.getComponentType()) {
   throw new Exception("ComponentType Mismatch.");
  }
  else {
   messages = updateEvent.getComponentState(); 
  }
 }

//...

}

ConsoleUpdateEvent 是 ComponentUpdateEvent 的子类。

现在,我可以让 ConsoleDrawer 中的 update() 方法将 ComponentUpdateEvent 作为参数,然后将其转换为 ConsoleUpdateEvent,但如果可能的话,我正在寻找更优雅的解决方案。任何帮助,将不胜感激。谢谢。

【问题讨论】:

  • 我不相信这是可能的,因为它违背了接口的原则(即实现接口的类将完全匹配接口指定的所有方法签名)。但是,我会看这个问题,因为我很想看看其他人是否会证明我错了。

标签: java generics parameters overriding subclass


【解决方案1】:

按照 OOP 原则,子类的使用方式应该与父类完全相同。 例如

Observer ob = new ConsoleDrawer();
ob.update(new ComponentUpdateEvent()); // This needs to work always.

但是,如果 Java 允许您在覆盖方法时使用参数的子类型,那么它会将您的代码暴露给覆盖方法(在子类中)会拒绝输入参数的情况(上面的 ComponentUpdateEvent案子)。因此,您永远无法确定在 Observer 引用上调用 update() 是否安全。

因此,唯一合乎逻辑的解决方案是接受父类参数,对其进行类型检查,然后将其转换为所需的子类型。

【讨论】:

    【解决方案2】:

    你不能。这不是埃菲尔。问题是您可以使用接口调用具有不兼容类型的实现方法。所以协变参数是不允许的。也不允许逆变参数,但提供重载更容易。允许协变返回类型(从 1.5 开始)。

    你可以参数化接口:

    public interface Observer<T extends ComponentEvent> {
        void update(T event) throws Exception;
    }
    

    或者,使用更有意义的界面:

    public interface ConsoleObserver {
        void update(ConsoleEvent event) throws Exception;
    }
    

    【讨论】:

      【解决方案3】:

      您可以尝试以下方法。如果编译器知道您将调用第一个方法而不是第二个方法,@Deprecated 会产生警告。

      @Override @Deprecated
      public void update(ComponentUpdateEvent updateEvent) {
          // throws a ClassCastException if its not the right type.
          update((ConsoleUpdateEvent) updateEvent); 
      }
      
      public void update(ConsoleUpdateEvent updateEvent) {
          messages = updateEvent.getComponentState(); 
      }
      

      顺便说一句:你不应该只是将 throws Exception 放在所有东西上。这当然不是最佳实践。

      编辑:我为这个问题实现了一个不同的解决方案,它适用于 OSGi,但可以在任何地方工作。

      Observer 将自己注册到 Broker 并期望找到带有注释的方法,例如 ObserverCallback。

      例如

      public class ConsoleDrawer extends Drawer {
       @ObserverCallback
       public void onConsoleUpdateEvent(ConsoleUpdateEvent updateEvent) {
         messages = updateEvent.getComponentState(); 
       }
      }
      
      public class DeviceDrawer extends Drawer {
       @ObserverCallback
       public void onDeviceUpdateEvent(DeviceUpdateEvent updateEvent) {
         // do something.
       }
      }
      

      在第一种情况下,代理找到一个带有 @ObserverCallback 的方法,该方法接受一个参数。这是 Broker 将传递的唯一类型。第二类需要不同的类型。观察者可以有多种方法/类型,允许他们以适合该类型的不同方法处理不同的消息。你也知道你永远不会收到你不期望的数据类型。

      【讨论】:

      • 我个人(以及与我交谈过的许多其他人)不喜欢将 Deprecated 标签用于除原始意图之外的任何内容:标记曾经有效但现在已弃用的方法。用它来做类似这样的事情感觉就像黑客一样。
      • @DGH,该方法曾经有效(对于父级),但对于此类不再有效。 ;) 我同意你的观点。理想情况下,会有一个产生编译器错误的标签。更好的是设计 update() 方法,以便所有实现都遵守合同。
      • 感谢您的建议。使用第一种解决方案;我将第二个更新方法重命名为 private void addEventMessages(ConsoleUpdateEvent) {//...},这似乎工作得很好。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-06-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多