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?
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> {
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 :-))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With