如何返回作为参数给出的相同Collection类型,但具有不同的元素类型?
我想做这个:
public <C extends Collection> C strings_to_files(C strings) { C files = new C(); for(String string : strings) { files.add(new File(string) } return files; }
我只是希望它采取任何字符串集合,并返回与文件相同的集合类型。 有没有办法做到这一点? 或者我可能只是语法错误…
没有好办法直接做到这一点。 我认为最简单的方法是将目标集合作为第二个参数传递:
public void string_to_files(Collection strings, Collection files) { for(String string : strings) { files.add(new File(string)); } }
然后,客户端代码可以决定它想要回收的集合类型。 这不符合您的要求,但这是避免必须施放和抑制警告的最简洁方法。
或者,只需声明方法以返回Collection
(或特定的Collection
实现类型)并实例化您选择的特定Collection
类型。
由于除Map
之外的所有标准集合都实现了Collection
接口,这意味着它们都符合Collection
接口并且可以被视为Collection
,因此您不需要编写
。 您的函数必须使用一个实现Collection
的具体类来存储文件,因为您将使用new
来初始化它,这意味着它必须具体。 但是,如果要隐藏此详细信息,只需使用Collection
作为返回类型。
public Collection strings_to_files(Collection strings) { Collection files = new ArrayList<>(); for(String string : strings) { files.add(new File(string)); } return files; }
在Java 8中,函数体可以简化为:
return strings.stream().map(File::new).collect(Collectors.toCollection(ArrayList::new));
要么:
return strings.stream().map(File::new).collect(Collectors.toList());
让我们假装您的代码编译,然后传入List
。 您将遇到编译错误,因为List
是一个接口,而不是具体的类。
public static List strings_to_files(List strings) { List files = new List (); // compile error, since this is an interface for(String string : strings) { files.add(new File(string)); } return files; }
你可以用reflection做一些魔术:
public Collection strings_to_files(Collection strings) { Collection files = null; try { Constructor ctor = findDefaultConstructor(strings.getClass()); files = (Collection ) ctor.newInstance(); } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | SecurityException e) { throw new RuntimeException(e); } for(String string : strings) { files.add(new File(string)); } return files; } private Constructor findDefaultConstructor(Class collectionClass) { for (Constructor ctor : collectionClass.getDeclaredConstructors()) { System.out.println(ctor); if (ctor.getParameterCount() == 0) { ctor.setAccessible(true); return ctor; } } return null; }
这种魔法适用于类,如ArrayList
或HashSet
。 但是,它不适用于Arrays.asList(...)
创建的列表。 所以, 在这种方法中没有很好的解决方案。 但是,您可以将责任传递给此方法的调用者。
您可以像@ ted-hopp建议的那样执行相同操作并使用运行时检查强制执行不变量:
public void strings_to_files(Collection strings, Collection files) { if (!strings.getClass().equals(files.getClass())) { throw new IllegalArgumentException("strings and files must belong to the same collection type"); } for(String string : strings) { files.add(new File(string)); } }
我认为这是两个单独的问题:
1.如何创建一个与参数相同类型的新集合。
2.如何在函数声明中表示调用者被获取以获得与传入的相同类型的集合。
更简单的问题是2:
不可能。 由于generics类型本身不能具有generics类型参数,因此不起作用。 您可以将它放在文档中,但函数声明必须如下所示:
/** * @return The same type of collection as was passed as argument. */ public Collection strings_to_files(Collection strings) {}
调用者必须将结果强制转换为传递的类型:
ArrayList files = (ArrayList )strings_to_files(new ArrayList());
问题1:
Tamas Rev的一个答案提供了一种可能性。
但是,如果您不想使用reflection,则可以创建自己的集合,这些集合提供了在每个集合对象本身上重新创建相同类型集合或副本的方法。 这实际上非常简单:
public interface Collection extends java.util.Collection { /** * @return Shallow copy with the same collection type as this objects real collection type. */ public Collection copy(); /** * @return New empty instance of the same type as this objects real collections type. */ public Collection newInstance(); } public interface List extends java.util.List , Collection { @Override public List copy(); @Override public List newInstance(); } public class LinkedList extends java.util.LinkedList implements List { @Override public LinkedList copy() { return new LinkedList (this); } @Override public LinkedList newInstance() { return new LinkedList (); } }
您需要对所需的每个Collection类执行此操作。 现在您可以在函数中使用这些集合:
public Collection strings_to_files(Collection strings) { Collection files = strings. newInstance(); for(String string : strings) { files.add(new File(string)); } return files; }
您仍然可以将集合传递给其他库。 另一个优点是,您可以将其他有用的东西放入集合中,就像generics参数的实际类对象一样,因此您始终可以强制执行并检索generics参数类型。
唯一的问题是,如果要使用默认集合,则需要将其转换为集合。