javagenericsenumsvavr

Why this converter needs casting?


I need to implement an enum to enum converter in java: Enum_2 > Enum_1 and I'd like to do it in generic way.

So I defined an interface:

interface LabelAware<T extends Enum> {
    String getLabel();

    T getObject();
}

and Enum_1:

enum Enum_1 {
    A, B;

    String getValue() {
        return "whatever";
    }
}

and Enum_2 which implements LabelAware and needs to be converted to Enum_1:

enum Enum_2 implements LabelAware<Enum_1> {
    C("c", Enum_1.A), D("d", Enum_1.B);

    private final String label;
    private final Enum_1 object;

    Enum_2(String label, Enum_1 object) {
        this.label = label;
        this.object = object;
    }

    public String getLabel() {
        return label;
    }

    public Enum_1 getObject() {
        return object;
    }
}

Finally, here's a generic converter (List.ofAll() comes from javaslang):

class Converter<S extends LabelAware, D extends Enum> {

    private S[] values;

    Converter(S[] values) {
        this.values = values;
    }

    D map(String label) {
        return (D) List.of(values)
                .find(v -> v.getLabel().equals(label))
                .map(LabelAware::getObject)
                .getOrElseThrow(() -> new RuntimeException(""));
    }
}

And a main method:

public class Main {        
   public static void main(String[] args) {
      System.out.println(new Converter<Enum_2, Enum_1>(Enum_2.values()).map("c").getValue());
   }
}

It all compiles and runs well, however I've no idea why I need to cast the result of Converter.map method to D, since I've declared D to extend Enum. Can it be done in a generic way without any warnings?


Solution

  • As a general rule, all warnings related to generics should be handled to have a safer code and avoid a warning chain (the visible warning is caused by a very far warning of the dependency chain).

    But in your case, you have not a warning chain problem since externally, LabelAware is safe.
    LabelAware has only a internal warning (in its implementation) as Enum in extends Enum is raw-declared.

    Here, a single missing generic declaration explains why the cast in Converter.map() method is not safe : Converter class declaration doesn't specify the generic for LabelAware.

    You declare Converter class as :

    class Converter<S extends LabelAware, D extends Enum> {
    

    with its value field of type S:

     private S[] values;
    

    and its map() method as :

     D map(String label) {
            return (D) List.of(values)
                    .find(v -> v.getLabel().equals(label))
                    .map(LabelAware::getObject)
                    .getOrElseThrow(() -> new RuntimeException(""));
        }
    

    In map(), here .find(v -> v.getLabel().equals(label)), your retrieve so a S instance and you declared that S extends LabelAware. Therefore finally, your retrieve an instance of LabelAware or extending it.

    And LabelAware is typed with Enum generic :

    interface LabelAware<T extends Enum> {
        String getLabel();    
        T getObject();
    }
    


    So, in map() method when .map(LabelAware::getObject) is called, you retrieve a Enum type .

    And an Enum type is not necessarily a D type, while the reverse is true.

    Therefore, if you want to avoid the cast (and the related warning) in map(), you should specify that the generic type returned by getObject() is an instance of D by typing LabelAware with D generic :

    class Converter<S extends LabelAware<D>, D extends Enum> {