9

So I asked this question before but I had a mistake in the code which most people picked up on, rather than the problem itself.

Anyway, I'm trying to override an interface method in a class. However, I want the type of the parameter in the overriding method to be a subclass of the type of the parameter as defined in the overriden method.

The interface is:

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

While the class that overrides this method is:

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 is a subclass of ComponentUpdateEvent.

Now, I could just have the update() method in ConsoleDrawer take a ComponentUpdateEvent as a parameter and then cast it to a ConsoleUpdateEvent but I'm looking for a slightly more elegant solution if possible. Anyhelp would be appreciated. Thank you.

Bert F
  • 85,407
  • 12
  • 106
  • 123
carnun
  • 131
  • 1
  • 1
  • 4
  • 5
    I do not believe this is possible as it goes against the principle of an interface (i.e. Classes which implement an interface will exactly match all method signatures specified by the interface). However, I will watch this question as I am very interested to see if someone else will prove me wrong. – DGH Dec 11 '10 at 17:31

3 Answers3

9

You can't. This is not Eiffel. The problem being that you could use the interface to call the implementation method with an incompatible type. So covariant parameters are not allowed. Contravariant parameters aren't allowed either, but it is easier to provide an overload. Covariant return type is allowed (since 1.5).

You could parameterise the interface:

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

Alternatively, use a more meaningful interface:

public interface ConsoleObserver {
    void update(ConsoleEvent event) throws Exception;
}
Tom Hawtin - tackline
  • 145,806
  • 30
  • 211
  • 305
4

Going by OOP principles, a sub-class should be usable in exactly the same way as the parent class. e.g.

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

However, if Java were to allow you to use a subtype of the parameter when overriding a method, then it would expose your code to cases where the overriding method (in the subclass) would reject the input parameter (ComponentUpdateEvent in the above case). Thus, you would never be sure whether its safe to call update() or not on an Observer reference.

Hence, the only logical solution is to accept the parent class parameter, type-check it and then cast it to the required subtype.

user90766
  • 329
  • 1
  • 4
  • 17
2

You could try the following. The @Deprecated produces a warning if the compiler knows you will be calling the first method rather than the second.

@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(); 
}

BTW: You shouldn't just place throws Exception on everything. Its certainly not best practice.

EDIT: I have implemented a different solution to this problem which works well with OSGi but can work anywhere.

An Observer registers itself with a Broker and expects to find methods with an annotation such as ObserverCallback.

e.g.

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.
 }
}

In the first case, the broker finds a method with the @ObserverCallback which takes one argument. This is the only type the Broker will pass it. The second class expects a different type. Observers can have multiple method/types allowing them to handle different messages in different methods appropriate to that type. You also know you will never receive a data type you don't expect.

Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • I personally (and many others I've spoken to) dislike using the Deprecated tag for anything other than the original intent: marking methods which used to be valid but are now deprecated. Using it for something else like this feels hacker-ish. – DGH Dec 11 '10 at 17:34
  • @DGH, The method used to be valid (for the parent) but is no longer valid for this class. ;) I take your point though. Ideally there would be a tag which produced a compiler error. Even better would be to design the update() method so that all implementation honoured the contract. – Peter Lawrey Dec 11 '10 at 17:40
  • Thanks for the suggestion. Using the first solution; I renamed the second update method to private void addEventMessages(ConsoleUpdateEvent) {//...} and that seems to work reasonably well. – carnun Dec 11 '10 at 17:52