Reputation: 7482
I have number of Enums that share the method across them. I would like to move it to an interface if possible so I don't have to duplicate it and the code would look a lot cleaner. But after much effort I am still unable to move the methods to an interface.
public enum TypeA {
ValueAA ("Value AA"),
ValueAB ("Value AB");
private final String type;
TypeA (final String type) {
this.type = type;
}
@JsonValue
public String getType() {
return this.type;
}
@JsonCreator
public static TypeA fromValue(final String value) {
for (TypeA t : TypeA.values()) {
if (t.getType().equalsIgnoreCase(value)) {
return t;
}
}
StringBuilder allTypes = new StringBuilder();
boolean bFirstTime = true;
for (TypeA val : TypeA.values()) {
allTypes.append(bFirstTime ? "" : ", ").append(val);
bFirstTime = false;
}
throw new IllegalArgumentException(value + " is an invalid value. Supported values are " + allTypes);
}
}
public enum TypeB {
ValueBA ("Value BA"),
ValueBB ("Value BB");
private final String type;
TypeB (final String type) {
this.type = type;
}
@JsonValue
public String getType() {
return this.type;
}
@JsonCreator
public static TypeB fromValue(final String value) {
for (TypeB t : TypeB.values()) {
if (t.getType().equalsIgnoreCase(value)) {
return t;
}
}
StringBuilder allTypes = new StringBuilder();
boolean bFirstTime = true;
for (TypeB val : TypeB.values()) {
allTypes.append(bFirstTime ? "" : ", ").append(val);
bFirstTime = false;
}
throw new IllegalArgumentException(value + " is an invalid value. Supported values are " + allTypes);
}
}
Using Generics and Java 8 is there anyway to move getType
and fromValue
methods to an interface so that I can share across all the Enums ? Also note the Jackson annotations JsonValue
& JsonCreator
.
Upvotes: 1
Views: 1159
Reputation: 298203
You can move the fromValue
implementation to an interface
, however, I suppose, you have to keep stubs in the concrete types for supporting the JSON factory annotation:
interface TypeX {
String getType();
static <T extends Enum<T>&TypeX> T fromValue(String value, Class<T> type) {
EnumSet<T> all=EnumSet.allOf(type);
for (T t: all) {
if (t.getType().equalsIgnoreCase(value)) {
return t;
}
}
throw new IllegalArgumentException(all.stream().map(t -> t.getType())
.collect(Collectors.joining(", ",
value+" is an invalid value. Supported values are ", "")));
}
}
public enum TypeA implements TypeX {
ValueAA ("Value AA"),
ValueAB ("Value AB");
private final String type;
TypeA (final String type) {
this.type = type;
}
@JsonValue
public String getType() {
return this.type;
}
@JsonCreator
public static TypeA fromValue(final String value) {
return TypeX.fromValue(value, TypeA.class);
}
}
enum TypeB implements TypeX {
ValueBA ("Value BA"),
ValueBB ("Value BB");
private final String type;
TypeB (final String type) {
this.type = type;
}
@JsonValue
public String getType() {
return this.type;
}
@JsonCreator
public static TypeB fromValue(final String value) {
return TypeX.fromValue(value, TypeB.class);
}
}
For completeness, since the type property is invariant, it is possible to move the getType
method to the interface
, if we use a different implementation:
interface TypeX {
@Retention(RetentionPolicy.RUNTIME) @interface Type { String value(); }
@JsonValue default String getType() {
for(Field f: getDeclaringClass().getDeclaredFields()) try {
if(f.isEnumConstant() && f.get(null)==this) {
return f.getAnnotation(Type.class).value();
}
} catch(IllegalAccessException ex) {
throw new AssertionError(ex);
}
throw new IllegalStateException();
}
Class<? extends TypeX> getDeclaringClass();
static <T extends Enum<T>&TypeX> T fromValue(String value, Class<T> type) {
EnumSet<T> all=EnumSet.allOf(type);
for (T t: all) {
if (t.getType().equalsIgnoreCase(value)) {
return t;
}
}
throw new IllegalArgumentException(all.stream().map(t -> t.getType())
.collect(Collectors.joining(", ",
value+" is an invalid value. Supported values are ", "")));
}
}
public enum TypeA implements TypeX {
@Type("Value AA") ValueAA,
@Type("Value AB") ValueAB;
@JsonCreator
public static TypeA fromValue(final String value) {
return TypeX.fromValue(value, TypeA.class);
}
}
enum TypeB implements TypeX {
@Type("Value BA") ValueBA,
@Type("Value BB") ValueBB;
@JsonCreator
public static TypeB fromValue(final String value) {
return TypeX.fromValue(value, TypeB.class);
}
}
But this has several drawbacks, e.g. the Reflection based access is not checked at compile-time and may have performance disadvantages at runtime. And I don’t know whether the @JsonValue
will be respected the intended way when appearing at a default
method inherited from an interface
.
Upvotes: 3