【问题标题】:Java Generics to return Enum based on valueJava泛型根据值返回枚举
【发布时间】:2017-03-08 00:23:52
【问题描述】:

我有多个 Enum 共享它们之间的方法。如果可能的话,我想把它移到一个界面上,这样我就不必复制它了,代码看起来会更干净。但是经过很多努力,我仍然无法将方法移动到接口。

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

使用泛型和 Java 8 是否可以将 getTypefromValue 方法移动到接口,以便我可以在所有 Enums 之间共享?另请注意 Jackson 注释 JsonValueJsonCreator

【问题讨论】:

  • 您实际上需要状态,而状态需要类。枚举类型不能是您自己的类的子类型。
  • 感谢您的简单解释
  • 您可以创建一个辅助类来保存状态并在其他类之间共享一个实例。然而,枚举似乎不是你想要的。您可以滚动自己的类型安全枚举并为它们提供通用代码,或者更好的是,创建一个状态维护类,该类包含所需的状态并保存枚举值。目前还不清楚什么最适合您,因为您的示例没有暗示您为什么需要枚举,更不用说两种共享状态的类型了。从表面上看,您可能不会,而是应该重新考虑您的模型。
  • @Sotirios Delimanolis:这种状态是不变的,接口支持常量。因此,在这种特定情况下,这是可能的。但我会在enum 类型中保留getType 方法。

标签: java generics enums java-8


【解决方案1】:

您可以将 fromValue 实现移动到 interface,但是,我想,您必须在具体类型中保留存根以支持 JSON 工厂注释:

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

为了完整起见,由于类型属性是不变的,如果我们使用不同的实现,可以将getType 方法移动到interface

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

但这有几个缺点,例如基于反射的访问在编译时不会被检查,并且在运行时可能会有性能劣势。而且我不知道@JsonValue 在出现在从interface 继承的default 方法时是否会得到预期的尊重。

【讨论】:

  • 你为什么要使用TypeA.class而不是values()
  • @Tom Hawtin:TypeA.class 是一条廉价的字节码指令,values() 是一个方法调用,而被调用的方法创建并填充一个新数组。因此,让调用方成为一个廉价操作并让fromValue 封装它的工作方式应该是很自然的,即EnumSet.allOf(…) 创建一个数组。 fromValue 甚至可以实现缓存从 String 值到 enum 常量的映射,而无需在调用方进行任何更改。传入一个数组会适得其反。
  • 我认为与您为避免它所做的所有事情相比,方法调用的假定成本将无关紧要。还有代码……
  • @Tom Hawtin:我不是为了“避免它”而做任何事情。如果你想使用values()而不是EnumSet.allOf,你只需要用T[]替换EnumSet&lt;T&gt;,而不需要改变任何程序逻辑。仍然没有理由让每个调用者都执行该操作,而不是让单个方法来处理它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多