1

So I have these classes:

public class DeviceInState implements MyInterface<Device> {

    private List<DeviceState> toStates(String statesString) {
        List<String> states = Lists.newArrayList(statesString.split(","));

        return states.stream().map(DeviceState::valueOf).collect(Collectors.toList());
    }
}

  
public class DeviceHistoryInState implements MyInterface<DeviceHistory> {
    private List<EventType> toStates(String statesString) {
        List<String> states = Lists.newArrayList(statesString.split(","));

        return states.stream().map(EventType::valueOf).collect(Collectors.toList());
    }
}

And these enums:

public enum EventType{
    NEW("N"), ACTIVE("A"), INACTIVE("I");
}
    
public enum DeviceState{
    REGISTRATED("R"), SUSPENDED("S"), DELETED("D");
}

The differences are:

  • DeviceInState implements MyInterface<Device>; but DeviceHistoryInState implements MyInterface<DeviceHistory>

  • DeviceState::valueOf is called in DeviceInState; but EventTypes::valueOf is called in DeviceHistoryInState

I have a couple of other classes like these so I would like to make a generic one. But I have no idea whether or not it is possible. How might I parameterize my classes or methods in a way that I can call the ::valueOf method?

Thanks in advance.

deduper
  • 1,944
  • 9
  • 22
stackstack293
  • 341
  • 3
  • 12
  • `DeviceState` and `EventType` are enums that are the element types of the list returned by the `toStates` method, which is overriden. Both classes implement the `MyInterface` interface. Could you show that interface? – fps Oct 01 '20 at 15:28
  • Actually not these methods are overridden. These methods are just called from the overridden method. Well, if it's important for you... These codes are just part of the class. The interface I use is actually the Specification from Spring. I changed it because I don't think it's important. https://docs.spring.io/spring-data/data-jpa/docs/current/api/org/springframework/data/jpa/domain/Specification.html – stackstack293 Oct 01 '20 at 15:44
  • 1
    Pass the class as parameter https://stackoverflow.com/questions/4837190/java-generics-get-class and get enum using class https://stackoverflow.com/questions/26357132/generic-enum-valueof-method-enum-class-in-parameter-and-return-enum-item – Eklavya Oct 01 '20 at 15:48
  • Declare the enum type as the 2nd generic type parameter and grab it in the constructor as shown in the q&a linekd in the previous comment. You might need an abstract class where to inherit from – fps Oct 01 '20 at 15:51
  • Thing is I can pass my enum to the class like this. But I can't call the valueOf() because it's not an enum (for the code) – stackstack293 Oct 01 '20 at 15:56
  • If you have enum class you can do `Enum.valueOf(YourEnum.class, value)` – Eklavya Oct 01 '20 at 19:09
  • „*…But I can't call the valueOf() because it's not an enum (for the code)…*“ – @stackstack293 — This comment is confusing to me. Why can you not call, say *`EventType.valueOf(String)`* in the *`DeviceHistoryInState.toStates(String)`* method? Can you edit your question with the error message that you get when such a call fails? — „*…Actually not these methods are overridden. These methods are just called from the overridden method…*“ — Don't you think the *`@Overrides`* annotation should be removed then? It's confusing. And, therefore, makes it harder to know exactly what solution to propose. – deduper Oct 01 '20 at 19:42

2 Answers2

0

This is how I would do it

public interface State< E extends Enum< E > > { … }

Then my enums would implement that…

…
public enum DeviceState implements State< DeviceState > { … }
…
public enum EventType implements State< EventType > { … }
…

…Actually not these methods are overridden. These methods are just called from the overridden method…

Considering that you annotated the methods in your snippet with @Overrides, I found that to be a pretty confusing comment. To make things a little less confusing for myself, my experimental implementation does actually override something…

public class DeviceInState extends ToStateable< DeviceState > implements Specification< Device > {

    @Override
    public List< DeviceState > toStates( String statesString ) {
        
        List< String > states = asList( statesString.split( "," ) );

        return states.stream( ).map( String::trim ).map( DeviceState::valueOf ).collect(Collectors.toList());
    }
}

I was able to call that successfully like…

…
ToStateable< DeviceState > deviceToStateable = new DeviceInState( );
      
List< DeviceState > deviceStates = deviceToStateable.toStates( "SUSPENDED,REGISTERED,DELETED" );
      
ToStateable< EventType > eventToStateable = new DeviceHistoryInState( );
      
List< EventType > eventTypes = eventToStateable.toStates( "INACTIVE, NEW, ACTIVE"  );
…

Printing those objects out I could see…

                                  [SUSPENDED, REGISTERED, DELETED]
                                           [INACTIVE, NEW, ACTIVE]
                                                         SUSPENDED
                                             EXPERIMENT SUCCESSFUL
deduper
  • 1,944
  • 9
  • 22
0

You can remove your two similar implementations of toState() inside DeviceHistoryInState and DeviceInState and define a single generic method that works with any enumeration type:

static <E extends Enum<E>> List<E> toStates(Class<E> cls, String statesString) {
    List<String> states = Arrays.asList(statesString.split(","));
    return states.stream().map(String::trim).map(s -> Enum.valueOf(cls, s))
                          .collect(Collectors.toList());
}

There is however a small cost: you have to pass in the EnumType.class as a parameter, but this is fairly straightforward:

List< DeviceState > devStates = toStates(DeviceState.class, "SUSPENDED,REGISTRATED,DELETED");
=> [SUSPENDED, REGISTRATED, DELETED]
List< EventType > evTypes = toStates(EventType.class, "INACTIVE,NEW,ACTIVE");
=>  [INACTIVE, NEW, ACTIVE]

Alternatively you can make simple definition of EventType.toState(String) which calls the above.

DuncG
  • 12,137
  • 2
  • 21
  • 33