【问题标题】:using enum constants obtained through reflection使用通过反射获得的枚举常量
【发布时间】:2011-02-08 20:15:17
【问题描述】:

我有一个程序通过反射获取 Enum 值(允许任何 Enum),我想通过将它包装在一个以 Enum 作为其类型参数的泛型类中来做一些事情。但是,我不确定如何正确调用构造函数。我可以让它工作的唯一方法是使用原始类型。

(澄清:我的真实程序很复杂,并且在运行时从用户提供的文件中查找枚举类名。我的真实程序的包装类具有其他状态和方法,这些状态和方法无法通过枚举,所以我这样做不仅仅是为了学术。我编写了下面的示例程序来说明这个问题。它可能看起来很做作,但它应该是为了说明目的。)

谁能帮我修一下线

EnumWrapper<?> ewrapped = new EnumWrapper(e);

所以它有一个不那么邪恶的警告?

程序按预期工作(打印出未找到枚举常量的 3 个捕获的异常的堆栈跟踪,否则打印包装枚举的列表),但我养成了从不使用原始类型的习惯,并且不知道如何解决这个问题。

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class GenericEnum2 {

    enum Bird { OWL, EAGLE, HAWK };
    enum Mammal { LION, TIGER, BEAR };

    static class EnumWrapper<E extends Enum<E>>
    {
        final private E value;

        public EnumWrapper(E value) { this.value = value; }
        public E getEnum() { return this.value; }
        @Override public String toString() { return "wrapped "+value.toString(); }

        static public <E extends Enum<E>> EnumWrapper<E> wrap(E e) {
            return new EnumWrapper<E>(e);
        }
    }

    public static void main(String[] args) {
        List<EnumWrapper<?>> list = new ArrayList<EnumWrapper<?>>();
        list.add(EnumWrapper.wrap(Bird.OWL));
        list.add(EnumWrapper.wrap(Bird.EAGLE));
        list.add(EnumWrapper.wrap(Bird.HAWK));
        list.add(EnumWrapper.wrap(Mammal.LION));
        list.add(EnumWrapper.wrap(Mammal.TIGER));
        list.add(EnumWrapper.wrap(Mammal.BEAR));        
        System.out.println(list);

        list.clear();
        for (String s : Arrays.asList(
                "Bird.OWL", 
                "Bird.HAWK",
                "Bird.FULVOUS_WHISTLING_DUCK",
                "Mammal.LION", 
                "Mammal.BEAR",
                "Mammal.WARTHOG",
                "Computer.COMMODORE_64"
                ))
        {
            String className = GenericEnum2.class.getCanonicalName()+"$"+s;
            try
            {
                Enum<?> e = getEnum(className);

                // EnumWrapper<?> ewrapped0 = EnumWrapper.wrap(e);
                /*
                 * Bound mismatch: The generic method wrap(E) of type 
                 * GenericEnum2.EnumWrapper<E> is not applicable for 
                 * the arguments (Enum<capture#2-of ?>). The inferred 
                 * type Enum<capture#2-of ?> is not a valid substitute for 
                 * the bounded parameter <E extends Enum<E>>
                 */

                // EnumWrapper<?> ewrapped0 = new EnumWrapper<?>(e);
                // Cannot instantiate the type GenericEnum2.EnumWrapper<?>

                EnumWrapper<?> ewrapped = new EnumWrapper(e);
                // this works but gives me the warning of "EnumWrapper" being a raw type

                list.add(ewrapped);
            }
            catch (IllegalArgumentException e)
            {
                e.printStackTrace();
            }
            catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
        System.out.println(list);
    }

    static public Enum<?> getEnum(String enumFullName) throws IllegalArgumentException, ClassNotFoundException
    {
        String[] x = enumFullName.split("\\.(?=[^\\.]+$)");
        if (x.length == 2)
        {
            String enumClassName = x[0];
            String enumName = x[1];
            @SuppressWarnings("unchecked")
            final Class<Enum> cl = (Class<Enum>)Class.forName(enumClassName);
            if (cl.isEnum())
            {
                @SuppressWarnings("unchecked") 
                final Enum result = Enum.valueOf(cl, enumName);
                return result; 
            }
            else
                throw new IllegalArgumentException("Class is not an enum: "+enumClassName);
        }
        return null;
    }
}

编辑:根据 OrangeDog 的建议更新 getEnum():

static public Enum<?> getEnum(String enumFullName) throws IllegalArgumentException, ClassNotFoundException
    {
        String[] x = enumFullName.split("\\.(?=[^\\.]+$)");
        if (x.length == 2)
        {
            String enumClassName = x[0];
            String enumName = x[1];
            final Class<?> cl = Class.forName(enumClassName);
            if (cl.isEnum())
            {
                for (Object o : cl.getEnumConstants())
                {
                    Enum<?> e = (Enum<?>)o;
                    if (enumName.equals(e.name()))
                        return e;
                }
            }
            else
                throw new IllegalArgumentException("Class is not an enum: "+enumClassName);
        }
        return null;
    }
}

【问题讨论】:

  • 这个例子比较复杂。我建议使代码更简单并删除包装器。
  • 同意,除了静态工厂方法,我没有看到任何有用的方法。
  • 这似乎是一种复杂的写法Arrays.asList(Bird.OWL, Bird.EAGLE, Bird.HAWK, Mammal.LION, Mammal.TIGER, Mammal.BEAR)
  • 叹息。这是一个人为的例子,旨在说明我遇到的问题。我有一个枚举名称,需要在运行时通过反射查找,并包装在另一个对象中。我拥有的“真实”代码要复杂得多。这个例子几乎尽可能简单。

标签: java enums generics


【解决方案1】:

当混合泛型和动态时,通常没有办法让所有内容在语法上都是类型安全的。

您通常可以将其简化为 @SuppressWarnings("unchecked")@SuppressWarnings("rawtypes") 的一次使用,然后您可以使用良好的老式逻辑推理证明它们是正确的。您必须做出的决定是放置“不安全”操作的位置。

但在细节上,Class&lt;Enum&gt; 是错误的。你想使用Class&lt;? extends Enum&gt;(或者只是Class&lt;?&gt;,因为你正在做isEnum()检查)。

此外,您可以使用Class&lt;?&gt;.getEnumConstants() 更安全地重构实例。要么遍历查找正确的名称,要么直接使用序号进行索引。

【讨论】:

  • “在细节上,Class 是错误的”...啊,我刚刚在这里问了这个问题:stackoverflow.com/questions/4750965/…,这似乎是最好的选择。
  • suppresswarnings 评论 +1...所以“rawtypes”抑制并不比“unchecked”更好或更差?
  • re: getEnumConstants(): Enum.valueOf() 实际上是如何工作的?它是遍历列表,还是具有 HashMap 类型的查找?
  • @Jason S - 我会说得更好。 “rawtypes”表示以后可能需要进行未经检查的转换可能,“未经检查”表示现在必要的。
  • @Jason S - 不知道:检查您正在使用的实现的源代码。我更喜欢它,因为它减少了您收到警告的类型参数的数量。
【解决方案2】:

为了包装枚举对象,我们需要枚举的实际类型,就像在你的包装方法中一样(将其作为类型参数就足够了)。但是为此编译器需要推断类型,在这种情况下似乎这是不可能的。

问题在于,虽然Enum&lt;E&gt; 的每个实例实际上都是E 类型,但Enum&lt;E&gt; 实际上与编译器的E 不兼容。随之而来的是&lt;E extends Enum&gt;&lt;E extends Enum&lt;E&gt;&gt;是不兼容的类型变量声明,因此我们无法推断出参数

<E extends Enum<E>> E getInstance(Class<E> c, String name)

当使用Class&lt;?&gt; 类型的参数调用时。

也就是说,我找到了一种只出现一个未经检查的警告的方法,这显然是错误的:

public class GenericEnum2 {
    ...
    public static void main(String[] args) {

        ...
        for(...) {
            ...
            try
            {
                Enum<?> e = getEnum(className);
                EnumWrapper<?> wrapper = makeWrapper(e);
                list.add(wrapper);
            }
            ...
        }
        ...
    }

    /**
     * casts an instance of a Enum to its right type.
     */
    static <E extends Enum<E>> E cast(Enum<E> e) {
        // @SuppressWarning("unchecked")
        E result = (E)e;
        return result;
    }

    /**
     * makes a wrapper for an enum instance.
     * @see EnumWrapper#wrap
     */
    static <E extends Enum<E>> EnumWrapper<E> makeWrapper(Enum<E> e) {
        return EnumWrapper.wrap(cast(e));
    }

    ...

}

直接将 EnumWrapper.wrap(cast(e)) 放入 main 方法不起作用 - javac 无法推断 wrap 将接受与返回的强制类型相同的类型。

可以通过将 getEnum 方法拆分为多个方法并进行类似的强制转换来避免某些 SuppressWarnings,但我并没有真正尝试过。

【讨论】:

    猜你喜欢
    • 2023-03-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-10-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多