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

17

我有一些辅助方法,可以将枚举值转换为适合由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个回答

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方法的枚举。


不使用 getDisplayValue 方法。 - John Topley
修改后,请检查异常处理。 - dfa
1
如果这是一个经常使用的方法和/或您有很多枚举类型,那么您可能希望在类初始化期间将该数组计算一次作为静态数组,而不是每次重新创建它。 - Chris Kessel
你把他的ArrayList改成了LinkedList。据我所知,两者的区别在于前者在随机查找方面性能更高,而后者在随机插入或删除方面性能更高。由于他的列表最初是一个枚举,他(很可能)需要在随机索引处查找列表...那么ArrayList不会更快吗? - martin jakubik
我会在这个方法中添加methodName字符串参数,以便它可以用于Enum的任何方法。 - will824

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.
    }
}

来源:枚举类型的探究


不使用 getDisplayValue 方法。 - John Topley
是的,我发帖后意识到了。现在已经可以了。 - John Calsbeek
Displayable是从哪里来的? - John Topley
糟糕,无法添加接口;我将通过反射进行编辑以完成它。 - John Calsbeek
很遗憾,这不是一个选项,正如在对Sbodd的解释和原始问题中所述。 - John Topley

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的事实。因此,对于您的示例(免责声明:我认为这可以编译,但实际上并没有尝试过):
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),那么第二个方法可能很有用;否则,我会选择第一个方法(因为使用该方法,您也可以使用非枚举类型或枚举子集)。


谢谢,但我已经解释了第一个选项对我不可用。 - John Topley
+1 对于第二个选项,因为它通过确保实现 getDisplayValue() 方法来使得接受的答案类型安全,并且消除了反射。 - Jim Morris
第二个选项加1分,无反馈,@Sbodd你真棒;-) 优雅地解决了一个问题,我一直在尝试在Java中绕过它,由于Scala枚举,嗯,实现。 - virtualeyes

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

在这种情况下,使用了一个匿名类型,但它也可以是一个无状态的单例或类似的东西。

2

我建议您使用java.util.ResourceBundle和一个映射到枚举类的toString(也许还包括类名)值的bundle文件来实现此功能,这样您的代码将变得更简洁:

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

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,并且可以随每个枚举常量而改变(一种享元模式),并在那里有一个调用处理程序,它对其真实对象更加灵活,并在每次循环通过时调用正确的对象。

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

0
请注意,我知道问题中的枚举类型都有getDisplayValue方法,但不幸的是它们没有共同的类型来定义它(而且我也不能引入一个),所以我想这将不得不通过反射来访问...?
你猜得对。另一种选择是让所有枚举类型实现toString()方法并返回显示值,但如果你不能让它们实现接口,那么我想这也不可能。

很遗憾,我也做不到,因为toString的输出必须与我无法更改的数据库中的值匹配! - John Topley

-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(),我认为反射是唯一的方法。


你的Java代码甚至无法编译;FYI在Java中使用aClass.getEnumValues()代替Enum.GetNames(typeof(T))。 - dfa

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接