如何使用泛型在Java中从List<?>转换为List<T>?

2022-09-03 12:46:55

在Java中,我如何转换为使用通用方法,以便我可以用单个方法调用替换如下所示的模式:List<?>List<T>

List untypedList = new ArrayList();  // or returned from a legacy method
List<Integer> typedList = new ArrayList<Integer>();
for (Object item: untypedList)
    typedList.add((Integer)item);

请注意,上述代码不会生成任何类型安全警告,理想情况下,您的解决方案也不应生成任何此类警告。

如果该列表具有公共默认构造函数,以下解决方案是否有效?Class<L>

public class ListUtil {
    public static <T, L extends List<T>> L typedList(List<?> untypedList, Class<T> itemClass, Class<L> listClass) {
        L list = null;
        try {
            list = listClass.newInstance();
        } catch (InstantiationException e) {
        } catch (IllegalAccessException e) {
        }
        for (Object item: untypedList)
            list.add(itemClass.cast(item));
        return list;
    }
}

(请注意,抛出 或 如果 实例没有公共默认构造函数。如果该方法不能正确处理这些异常,可能会出现什么问题?listClass.newInstance()InstantiationExceptionIllegalAccessExceptionClass<L>

笔记:

  • T是结果列表中每个项的类型。
  • L是我希望创建的列表的类型(扩展 )。List<T>
  • untypedList是“非类型化”输入列表,实际上与 相同。List<Object>
  • itemClass表示 的运行时类。T
  • listClass表示 的运行时类。L

答案 1

我会使用Guava及其Iterables.filter(Iterable,Class)方法以及该类中的工厂方法,如下所示:Lists

List<?> original = ...;
List<String> typed = Lists.newArrayList(
   Iterables.filter(original, String.class));

这将实际检查原始列表中的每个对象,并且生成的列表将仅包含作为给定类型(在本例中)或其子类型的实例的那些元素。我真的不认为让用户为结果类型提供a并尝试通过反射来实例化它是没有意义的。StringClassList


答案 2

与其传入要实例化的列表的类型,为什么不直接传入要填充的空 Collection<T>?这为 API 的用户提供了更大的灵活性,因为使用默认构造函数并不总是理想的。(例如,也许我想要一个提供预期元素数量的 Set,或者我想要一个提供比较器的排序列表)。

另外,作为旁注,您应该始终编程到最通用的接口。在这种情况下,您的输入只需要一个可迭代对象,并且您的输出是一个集合。

鉴于此,我会这样写方法——

  public static <T, C extends Collection<T>> C typesafeAdd(Iterable<?> from, C to, Class<T> listClass) {
    for (Object item: from) {
      to.add(listClass.cast(item));
    }
    return to;
  }

则调用代码如下所示:

  public static void main(String[] args) {
    List<?> untypedStringList = LegacyApi.getStringList();
    List<String> typesafeStringList = typesafeAdd(untypedStringList, new ArrayList<String>(), String.class);
  }

2 评论在这里:

  • 如果您真的可以信任LegacyApi(或任何为您提供非类型化列表的东西)仅将包含预期类型的集合返回给您,那么您可以执行未经检查的强制转换并禁止显示它。这应该本地化到尽可能小的范围。即:创建类似TypesafeLegacyApiWrapper的东西,它将调用委托给LegacyApi。
  • 如果您有更复杂的方法签名,此方法签名仍会崩溃。例如,如果您有一个 List<List<String>>则此方法不起作用。