将Java枚举值多态转换为字符串列表

时间:2009-08-11 15:45:16

标签: java generics reflection enums polymorphism

我有一些辅助方法可以将枚举值转换为适合HTML <select>元素显示的字符串列表。我想知道是否可以将这些重构为单一的多态方法。

这是我现有方法之一的示例:

/**
 * Gets the list of available colours.
 * 
 * @return the list of available colours
 */
public static List<String> getColours() {
  List<String> colours = new ArrayList<String>();

  for (Colour colour : Colour.values()) {
    colours.add(colour.getDisplayValue());  
  }

  return colours;
}

我仍然是Java泛型的新手,所以我不确定如何将泛型枚举传递给该方法,并且也在for循环中使用它。

请注意,我知道有问题的枚举都会有getDisplayValue方法,但不幸的是它们没有共享定义它的常见类型(我不能介绍一个),所以我猜必须反思性地访问......?

提前感谢您的帮助。

10 个答案:

答案 0 :(得分:17)

使用Class#getEnumConstants()很简单:

static <T extends Enum<T>> List<String> toStringList(Class<T> clz) {
     try {
        List<String> res = new LinkedList<String>();
        Method getDisplayValue = clz.getMethod("getDisplayValue");

        for (Object e : clz.getEnumConstants()) {
            res.add((String) getDisplayValue.invoke(e));

        }

        return res;
    } catch (Exception ex) {
        throw new RuntimeException(ex);
    }
}

这不是完全类型安全的,因为你可以使用没有getDisplayValue方法的枚举。

答案 1 :(得分:10)

您可以将此方法放在某个实用程序类中:

public static <T extends Enum<T>> List<String> getDisplayValues(Class<T> enumClass) {
    try {
        T[] items = enumClass.getEnumConstants();
        Method accessor = enumClass.getMethod("getDisplayValue");

        ArrayList<String> names = new ArrayList<String>(items.length);
        for (T item : items)
            names.add(accessor.invoke(item).toString()));

        return names;
    } catch (NoSuchMethodException ex) {
        // Didn't actually implement getDisplayValue().
    } catch (InvocationTargetException ex) {
        // getDisplayValue() threw an exception.
    }
}

来源:Examining Enums

答案 2 :(得分:3)

你可以在这做两件事。第一种(更简单,因此更好)的方法就是让你的getStrings()方法获取某个接口的列表,并让你的枚举实现该接口:

public interface DisplayableSelection {
  public String getDisplayValue();
}

private static List<String> getStrings (Collection<DisplayableSelection> things) {
  List<String> retval = new ArrayList<String>();
  for (DisplayableSelection thing : things) {
    retval.add(thing.getDisplayValue());
  }
}

private static List<String> getColours () {
  return getStrings(Colour.values());
}

如果您真的关心内部类型是Enum,您还可以使用所有枚举类型自动子类化内置类型Enum的事实。所以,对于你的例子(免责声明:我认为这个编译,但实际上没有尝试过):

public interface DisplayableEnum {
  public String getDisplayValue();
}

private static <T extends Enum<T> & DisplayableEnum > List<String> getDisplayValues(Class<T> pClass) {
  List<String> retval = new ArrayList<String>();
  for (DisplayableSelection thing : pClass.getEnumConstants()) {
    retval.add(thing.getDisplayValue());
  }
}

private static List<String> getColours () {
  return getStrings(Colour.class);
}

如果您想要执行特别需要枚举的操作(例如,出于某种原因使用EnumMap或EnumSet),则第二种形式可能很有用;否则,我会选择第一个(因为使用该方法,您还可以使用非枚举类型,或仅使用枚举的子集)。

答案 3 :(得分:3)

这种方法避免了反思:

  public static interface Displayer<T> {
    String displayName(T t);
  }

  public static <T> List<String> getDisplayNames(Iterable<? extends T> stuff,
      Displayer<T> displayer) {
    List<String> list = new ArrayList<String>();
    for (T t : stuff) {
      list.add(displayer.displayName(t));
    }
    return list;
  }

...但是对于您想要显示的所有内容需要单独的类型:

  enum Foo {
    BAR("BAR"), BAZ("BAZ");
    private final String displayName;

    private Foo(String displayName) {
      this.displayName = displayName;
    }

    public String getDisplayName() {
      return displayName;
    }
  }

  public static void main(String[] args) {
    Displayer<Foo> fooDisplayer = new Displayer<Foo>() {
      public String displayName(Foo foo) {
        return foo.getDisplayName();
      }
    };

    System.out.println(getDisplayNames(Arrays.asList(Foo.BAR, Foo.BAZ),
        fooDisplayer));
  }

在这种情况下,使用匿名类型,但它可能是无状态单例或某些。

答案 4 :(得分:2)

我会使用java.util.ResourceBundle一个捆绑文件,该文件会映射到您的枚举的toString(也许是类名)值,这样您的代码就会变成:

bundle.getString(enum.getClass().getName() + enum.toString());

答案 5 :(得分:1)

以下是我建议的方法:

首先是实用程序类中的辅助方法和静态内部类:

    @SuppressWarnings("unchecked")
    public static <T> T generateProxy(Object realObject, Class<?>... interfaces) {
        return (T) Proxy.newProxyInstance(realObject.getClass().getClassLoader(), interfaces, new SimpleInvocationHandler(realObject));
    }


    private static class SimpleInvocationHandler implements InvocationHandler {
        private Object invokee;

        public SimpleInvocationHandler(Object invokee) {
            this.invokee = invokee;
        }

        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable {
            method = invokee.getClass().getMethod(method.getName(), method.getParameterTypes());
            if (!method.isAccessible()) {
                method.setAccessible(true);
            }
            try {
                return method.invoke(invokee, args);
            } catch (InvocationTargetException e) {
                throw e.getTargetException();
            }
        }
    }

然后将它与你的枚举结合起来:

   interface DisplayableEnum {
       String getDisplayValue();
   }

private static List<String> getFromEnum(Class<? extends Enum<?>> displayableEnum) {
    List<String> ret = new ArrayList<String>();
    for (Enum e : displayableEnum.getEnumConstants()) {
        DisplayableEnum de = generateProxy(e, DisplayableEnum.class);
        ret.add(de.getDisplayValue());
    }
    return ret;
}

如果性能是一个产生如此多代理对象的问题,那么我会沿着制作实现DisplayableEnum的可变类的路径,该类可以随着每个枚举常量(一种flyweight模式)而改变,并在那里有一个调用处理程序对于它的真实对象更灵活,并在每次循环中调用正确的对象。

答案 6 :(得分:0)

  

请注意,我知道枚举   问题都会有   getDisplayValue方法,但是   不幸的是他们没有分享   定义它的常见类型(和我   不能介绍一个),所以我猜   必须访问   反射性...?

你猜对了。另一种方法是让enums通过返回显示值来实现toString() - 但是如果你不能让它们实现一个接口,那么我想这也是不可能的。

答案 7 :(得分:0)

我在第一次响应时以这种方式编辑方法,它没有问题,没有实现任何接口

public static <T extends Enum<T>> List<String> getDisplayValues(
        Class<T> enumClass) {
    try {
        T[] items = enumClass.getEnumConstants();
        Method accessor = enumClass.getMethod("toString");

        ArrayList<String> names = new ArrayList<String>(items.length);
        for (T item : items)
            names.add(accessor.invoke(item).toString());

        return names;
    } catch (NoSuchMethodException ex) {
        // Didn't actually implement getDisplayValue().
        Log.e(TAG, "getDisplayValues [" + ex+"]");
    } catch (InvocationTargetException ex) {
        // getDisplayValue() threw an exception.
        Log.e(TAG, "getDisplayValues [" + ex+"]");
    } catch (IllegalAccessException ex) {
        // getDisplayValue() threw an exception.
        Log.e(TAG, "getDisplayValues [" + ex+"]");
    }
    return null;
}

答案 8 :(得分:-1)

(抱歉,这是C#。我没有看到问题是针对Java的。)

public string[] GetValues<T>()
{
    return Enum.GetNames(typeof(T));
}

对于Java,当然,所有枚举类型仍然继承自java.util.Enum,因此您可以编写:

public string[] getValues<T extends Enum<T>>()
{
    // use reflection on T.class
}

由于java.util.Enum实际上没有实现values(),我认为反射是唯一的方法。

答案 9 :(得分:-4)

来吧伙计..它并不那么难。我正在使用字符串比较..但您可以根据需要比较对象类型。

public static <T extends Enum<T>> Map<T, String>  Initialize_Map(Class<T> enumClass) {
  Map<T, String> map = new HashMap<T, String>();
  for (T val : enumClass.getEnumConstants()) {
    map.put(val, val.toString() + (val.toString().equals("ENUM_ELMT") ? " (appended)" : ""));
  }
  return map;       
}