Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 212
Opal Avatar asked Nov 05 '16 15:11

Opal


2 Answers

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> {
like image 54
davidxxx Avatar answered Nov 09 '22 09:11

davidxxx


You have been using raw types at several places (not only the one that yshavit pointed out in the comment). Particularly, the

class Converter<S extends LabelAware, D extends Enum> 

has to be

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

The following should compile without warnings:

import javaslang.collection.List;

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

    T getObject();
}

enum Enum_1
{
    A, B;

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

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

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

    private S[] values;

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

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

(EDIT: This only tells you how to fix the problem, pragmatically. See the answer by davidxxx for details about what went wrong there, and don't forget to leave a +1 there :-))

like image 26
Marco13 Avatar answered Nov 09 '22 07:11

Marco13