Java switch on enum that implements same interface

2020-06-18 10:30发布

问题:

I have a group project where we are forced to use interfaces and enumerations provided.

Imagine a situation like below :

// marker interface
interface Request<T extends Response>{}

// marker interface
interface Response{}

enum TypeAction implements Request<SomeEnumClassThatImplementsResponse>{
      TYPE1, TYPE2, TYPE3
}

enum OtherTypeAction implements Request<SomeOtherEnumClassThatImplementsResponse>{
      OTHERTYPE1, OTHERTYPE2    
}

In an other class, I have a List of Request like this : List<Request> req = new LinkedList<Request>() What I want to do is : make a switch like below :

switch(a_request){
   CASE TYPE1: ....
   CASE TYPE2: ....
   CASE TYPE3: ....
   CASE TYPE2: ....
   CASE OTHERTYPE1: ....
   CASE OTHERTYPE2: ....
}

How can I manage to do this please ?

IMPORTANT NOTE : I can't add methods into the interfaces and into the enums but I can create new enums that implements the interfaces you can see above. I'd rather not have two enums that do the same thing if possible.

EDIT : It's different to the possible duplicate answer in that I can't add any method in the Request interface and so I can't implement a method in the enum classes.

Thanks in advance

回答1:

The switch you mention does obviously not work.

I have a (quite weird) substitution: create a "helper enum" which contains all values listed, and have a Map<Request<Request<T extends Response>>, HelperEnum>, like this:

private enum HelperEnum {
       TYPE1(TypeAction.TYPE1),
       TYPE2(TypeAction.TYPE2),
       TYPE3(TypeAction.TYPE3),
       OTHERTYPE1(OtherTypeAction.OTHERTYPE1),
       OTHERTYPE2(OtherTypeAction.OTHERTYPE2),

       private Request<T extends Response> source;

       private HelperEnum(Request<T extends Response> source) {
           this.source = source;
       }

       private static Map<Request<T extends Response>, HelperEnum> map;

       public static HelperEnum lookUp(Request<SomeEnumClassThatImplementsResponse> source) {
           if (map == null) {
               map = Arrays.stream(HelperEnum.values())
                   .collect(Collectors.toMap(x -> x.source, x -> x));
           }
           return map.get(source);
       }

(untested! Especially the places where I use Request<T extends Response> might be wrong; I'd have to test them first. But you should get the idea.)

This way you can then do

switch(HelperEnum.lookUp(a_request)){
    case TYPE1: ....
    case TYPE2: ....
    case TYPE3: ....
    case OTHERTYPE1: ....
    case OTHERTYPE2: ....
}


回答2:

You could use a map instead of a switch:

interface MyRequestTypeAction{
  void doSomething();
}

Map<Request, MyRequestTypeAction> requestActions = new HashMap<>(){{
   put(TypeAction.TYPE1,()->System.out.printLine("TypeAction.TYPE1"));
   put(TypeAction.TYPE2,()->System.out.printLine("TypeAction.TYPE2"));
   put(TypeAction.TYPE3,()->System.out.printLine("TypeAction.TYPE3"));
   put(OtherTypeAction.OTHERTYPE1,()->System.out.printLine("OtherTypeAction.OTHERTYPE1"));
   put(OtherTypeAction.OTHERTYPE2,()->System.out.printLine("OtherTypeAction.OTHERTYPE2"));
}}

requestActions.get(a_request).doSomething();


回答3:

In case you have a behaviour, where e.g. a Request could trigger several enumerated actions, or individual actions' implementations differ greatly from other actions, so you need a more flexible solution, you could go that way:

public enum RequestHandlers {
    TYPE1_HANDLER(TypeAction.TYPE1::equals, Request::methodA),
    TYPE2_HANDLER(r -> r == TypeAction.TYPE2),
    OTHERTYPE1_HANDLER(OtherTypeAction.OTHERTYPE1::equals),
    COMMON_HANDLER(x -> true, MyLogger::trace);

    private final Predicate<Request<?>> requestFilter; // selects supported requests for this handler

    private final Consumer<Request<?>> consumer; // implements this handler's behaviour

    private RequestHandlers(Predicate<Request<?>> p, Consumer<Request<?>> consumer) {
        this.requestFilter = p;
        this.consumer = consumer;
    }

    private RequestHandlers(Predicate<Request<?>> p) {
        this.requestFilter = p;
        this.consumer = Request::methodB; // default behaviour for all handlers
    }

    public static void handle(Request<?>... requests) {
        Arrays.stream(RequestHandlers.values())
              .forEach(handler -> Arrays.stream(requests)
                                        .filter(handler.requestFilter)
                                        .forEach(request -> handler.consumer.accept(request)));
    }
}

Note that the criteria, when the action fits a given request, is configurable by a filter Predicate and the action by a Consumer.



回答4:

Unfortunately, you can't use switch for your actual code, but you can do this using if statement:

final List<Request> req = new LinkedList<>();

for (Request request : req) {
    if (TypeAction.TYPE1.equals(request)) {
        //code
    }
    else if (OtherTypeAction.OTHERTYPE1.equals(request)) {
        //code
    }
}

Maybe you can add more field to request and enums, and then you can equals this filed.