如何返回作为参数给出的相同Collection类型,但具有不同的元素类型?

时间:2016-06-29 02:37:21

标签: java generics types parameters return

我想这样做:

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

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

5 个答案:

答案 0 :(得分:2)

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

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)

让我们假装您的代码编译,然后传入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;
}

这种魔术适用于类,如ArrayListHashSet。但是,它不适用于由Arrays.asList(...)创建的列表。所以,对于这个方法中的没有很好的解决方法。但是,您可以将责任传递给此方法的调用者。

答案 2 :(得分: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());

答案 3 :(得分: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));
    }
}

答案 4 :(得分:0)

我认为这是两个单独的问题:
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的答案之一提供了一种可能性。

但是,如果您不想使用反射,则可以创建自己的集合,这些集合提供了重新创建相同类型的集合或每个集合对象本身的副本的方法。这实际上非常简单:

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

您需要对所需的每个Collection类执行此操作。现在,您可以在函数中使用这些集合:

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

您仍然可以将您的馆藏传递给其他图书馆。另一个优点是,您可以将其他有用的东西放入集合中,就像泛型参数的实际类对象一样,因此您始终可以强制执行并检索通用参数类型。
唯一的问题是,如果要使用默认集合,则需要将其转换为集合。