30

I have a Java generics question I was hoping someone could answer. Consider the following code:

public interface Event{}
public class AddressChanged implements Event{}
public class AddressDiscarded implements Event{}

public interface Handles<T extends Event>{
    public void handle(T event);
}

I want to implement this Handles interface like this:

public class AddressHandler implements Handles<AddressChanged>, Handles<AddressDiscarded>{
    public void handle(AddressChanged e){}
    public void handle(AddressDiscarded e){}
}

But java doesn't allow implementing Handles twice using the Generic. I was able to accomplish this with C#, but cannot figure a workaround in java without using Reflection or instanceof and casting.

Is there a way in java to implement the Handles interface using both generic interfaces? Or perhaps another way to write the Handles interface so the end result can be accomplished?

Marijn
  • 10,367
  • 5
  • 59
  • 80
whistlenuts
  • 517
  • 1
  • 5
  • 11

8 Answers8

20

Going after @Amir Raminfar, you can use visitor pattern

interface Event{
 void accept(Visitor v);
}
interface Visitor {
 void visitAddressChanged(AddressChanged a);
 void visitAddressDiscarded(AddressDiscarded a);
}

class AddressChanged implements Event{
 @Override
 public void accept(Visitor v) {
  v.visitAddressChanged(this);
 } 
}

class AddressDiscarded implements Event{
 @Override
 public void accept(Visitor v) {
  v.visitAddressDiscarded(this);
 } 
}

class AddressHandler implements Visitor {
    void handle(Event e){
       e.accept(this);
     }
    public void visitAddressChanged(AddressChanged e){}
    public void visitAddressDiscarded(AddressDiscarded e){}
}
Stan Kurilin
  • 15,614
  • 21
  • 81
  • 132
10

You can't do that in Java. You can only implement one concrete realization of the same generic interface. I would do this instead:

public class AddressHandler implements Handles<Event>{
    public void handle(Event e){
      if(e instanceof AddressDiscarded){
         handleDiscarded(e);
      } else if(e instanceof AddressChanged){
         handleChanged(e);
      }
    }
    public void handleDiscarded(AddressDiscarded e){}
    public void handleChanged(AddressChanged e){}
}
cdhowie
  • 158,093
  • 24
  • 286
  • 300
Amir Raminfar
  • 33,777
  • 7
  • 93
  • 123
  • 4
    This is arguable... This "instanceof cascade" is a very procedural paragidm. Of course it works, but IMHO it not very good style. But hence having different method names may not be perfect either, it is probably a matter of taste. – Daniel Schneller Dec 01 '10 at 15:43
  • 2
    This is what I would do. You don't have to :) – Amir Raminfar Dec 01 '10 at 15:44
  • 5
    @Don: Not multiple instances of the same generic interface though. That's what we're talking about. :P – cdhowie Dec 01 '10 at 15:51
2

No, because different "concrete" generic types in Java compile to the same type. The actual interface your object will implement is:

public interface Handles {
    public void handle(Event event);
}

And, obviously, you can't have two different methods with an identical signature...

cdhowie
  • 158,093
  • 24
  • 286
  • 300
1

AFAIK you cannot do that, because when compiling the source code in Java these will both boil down to handle(Event), making the method ambiguous.

The generic information is not available during runtime in Java, in contrast to C#. That is why there it works as you describe.

You will have to change the method names to make them unique, like handleAddressChanged and handleAddressDiscarded.

This is indeed one of the weak points of Java generics.

Daniel Schneller
  • 13,728
  • 5
  • 43
  • 72
  • It's really a compile-time issue. You don't need to know about runtime properties of the objects (the argument could even be `null`!) to distinguish the methods. – Tom Hawtin - tackline Dec 01 '10 at 16:40
0

Unfortunately not. The usual solution (fat, ugly, fast) is to create one Handles interface (i.e. HandlesAddressChange, HandlesAddressDiscarded) and give each of them a different method (handleAddressChange(...), handleAddressDiscarded()).

That way, the Java runtime can tell them apart.

Or you can use anonymous classes.

Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820
0

It isn't allowed because Java erases generic signatures during compilation. The interface method will actually have the signature

public void handle(Object event);

So you have two choices. Either implement separate Handlers for different events:

public class AddressChangedHandler implements Handles<AddressChanged>{ /* ... */ }
public class AddressDiscardedHandler implements Handles<AddressDiscarded>{ /* ... */ }

or implement one handler for all but check the type of the incoming event:

public void handle(Event e){
  if (e instanceof AddressChanged) {
     handleAdressChanged(e);
  }
  else if (e instanceof AddressDiscareded) {
     handleAdressDiscarded(e);
  }
}
Andreas Dolk
  • 113,398
  • 19
  • 180
  • 268
0

An implementation like this won't work due to the constraints of the java specification. But if you're not afraid to use AOP or some sort of an IOC-Container you could use annotations for that. Than your Aspects or the container could manage the messaging infrastructure and call the methods you annotate.

First you have to create the annotations.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EventConsumer {}

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Handles{}

The you may annotate your class like that:

@EventConsumer
public class AddressHandler{
    @Handles
    public void handle(AddressChanged e){}
    @Handles
    public void handle(AddressDiscarded e){}
}
Olivier Heidemann
  • 1,337
  • 10
  • 14
0

If you don't mind using a (small) library, here's one I wrote that solves your problem:

https://github.com/bertilmuth/requirementsascode

You'd build a model like this

Model.builder()
  .on(AddressChanged.class).system(this::handleAddressChanged)
  .on(AddressDiscarded.class).system(this::handleAddressDiscarded)
  .build()

and run it.

How to do that exactly is described on the website.

bertilmuth
  • 276
  • 1
  • 11