如何返回与参数相同类型的集合,但元素类型不同?

6
我想要做这件事:
public <C extends Collection<?>> C<File> strings_to_files(C<String> strings)
{
    C<File> files = new C<File>();

    for(String string : strings)
    {
        files.add(new File(string)
    }

    return files;
}

我只想让它接受任何字符串集合,并使用文件返回相同的集合类型。有没有办法做到这一点?或者我只是语法错误...


这种方法的使用场景是什么?此外,即使您的代码编译成功,如果C是不可修改的集合类型,它也不会很好地工作。 - Ted Hopp
我只是在编写一个通用函数库,供个人使用。我希望它们能够处理许多不同的情况。但实际上,我认为你是对的,也许我应该使用List而不是Collection?但是这个函数也可以用于Map,而Map并不是List。 - user6243380
“Map”甚至不是“Collection”,所以我不明白您的函数如何也能用于它。 - Ted Hopp
5个回答

3

没有直接的好方法来实现这个。我认为最简单的方法是将目标集合作为第二个参数传递:

public void string_to_files(Collection<String> strings, Collection<File> files) {
    for(String string : strings) {
        files.add(new File(string));
    }
}

客户端代码可以决定它想要返回什么类型的集合。这并不是你所要求的,但这是避免强制转换和抑制警告的最干净的方法。
或者,只需声明该方法返回一个Collection<File>(或特定的Collection实现类型),然后实例化您选择的特定Collection类型即可。

1
我认为这是两个不同的问题:
1. 如何创建一个与传入参数相同类型的新集合。
2. 如何在函数声明中表达调用者保证得到与传入相同类型的集合。

更简单的问题是2:
不可能。由于通用类型本身不能有通用类型参数,因此这是行不通的。您可以将其放入文档中,但函数声明必须如下所示:

/**
 * @return The same type of collection as was passed as argument.
 */
public Collection<File> strings_to_files(Collection<String> strings) {}

调用者将需要将结果强制转换回传递的类型:
ArrayList<File> files = (ArrayList<File>)strings_to_files(new ArrayList<String>());

问题 1:
Tamas Rev 的一个回答提供了一种可能性。

然而,如果您不想使用反射(reflection),您可以创建自己的集合(collections),提供方法在每个集合对象本身上重新创建相同类型或副本的集合。这实际上非常简单:

public interface Collection<E> extends java.util.Collection<E> {

    /**
     * @return Shallow copy with the same collection type as this objects real collection type.
     */
    public Collection<E> copy();

    /**
     * @return New empty instance of the same type as this objects real collections type.
     */
    public <F> Collection<F> newInstance();
}

public interface List<E> extends java.util.List<E>, Collection<E> {
    @Override
    public List<E> copy();
    @Override
    public <F> List<F> newInstance();
}

public class LinkedList<E> extends java.util.LinkedList<E> implements List<E> {
    @Override
    public LinkedList<E> copy() {
        return new LinkedList<E>(this);
    }
    @Override
    public <F> LinkedList<F> newInstance() {
        return new LinkedList<F>();
    }
}

你需要对每个需要的集合类都这样做。现在你可以在你的函数中使用这些集合:
public Collection<File> strings_to_files(Collection<String> strings)
{
    Collection<File> files = strings.<File>newInstance();

    for(String string : strings)
    {
        files.add(new File(string));
    }

    return files;
}

你仍然可以将你的集合传递给其他库。另一个优点是,你可以将其他有用的东西放入你的集合中,比如泛型参数的实际类对象,这样你就可以始终强制执行和检索泛型参数类型。
唯一的问题是,默认集合需要转换为你的集合才能使用。

1
假设你的代码编译通过,并传入了“List”。你将会遇到编译错误,因为“List”是一个接口而不是具体类。
public static List<File> strings_to_files(List<String> strings)
{
    List<File> files = new List<File>(); // compile error, since this is an interface

    for(String string : strings)
    {
        files.add(new File(string));
    }

    return files;
}

你可以通过反射实现一些魔法:
public Collection<File> strings_to_files(Collection<String> strings)
{
    Collection<File> files = null;
    try {
        Constructor ctor = findDefaultConstructor(strings.getClass());
        files = (Collection<File>) 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(...) 创建的列表。因此,在这个方法内部没有好的解决方法。但是,你可以将责任传递给调用这个方法的人。

0

由于除了Map之外,所有标准集合都实现了Collection接口,这意味着它们都符合Collection接口并且可以作为一个Collection处理,所以您真的不需要编写<C extends Collection<?>>。您的函数必须使用其中一个实现了Collection接口的具体类来存储文件,因为您将使用new来初始化它,这意味着它必须是具体的。但是,如果您想隐藏这个细节,只需使用Collection<File>作为返回类型。

public Collection<File> strings_to_files(Collection<String> strings)
{
    Collection<File> 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());

0

你可以像@ted-hopp建议的那样,通过运行时检查来强制执行不变量:

public void strings_to_files(Collection<String> strings, Collection<File> 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));
    }
}

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