如何对List的每个元素执行操作并返回结果(当然不影响原始元素)?

时间:2011-07-06 23:54:50

标签: java list generics

如何在Java中编写一个静态方法,该方法将获取List,对每个元素执行操作并返回结果(当然不会影响原始文件)?

例如,如果我想在每个元素中添加2,那么...具体的返回类型必须相同,例如如果我的List是一个值为1,2,3的LinkedList,我应该返回一个值为3,4,5的LinkedList。类似地,对于ArrayList,Vector,Stack等,它们都是列表。

我可以看到如何使用多个if (lst instanceof LinkedList) ...等...更好的方式?

import java.util.List;

public class ListAdd {       

    static List<Integer> add2 (List<Integer> lst) {

    ...

        return result;
    }
}

8 个答案:

答案 0 :(得分:6)

已经有很多答案,但我想向您展示一种不同的方式来思考这个问题。

在函数式编程领域,您要执行的操作称为map。这是我们在函数式语言中一直都在做的事情。

M<A>成为某种容器(在您的情况下,M将是ListA将是Integer;但是,容器可以是很多其他的东西)。假设您有一个函数,它将A转换为B s,即f: A -> B。让我们编写类型为F<A, B>的函数,以使用更接近Java的符号。请注意,您可以拥有A = B,如您提供的示例所示(A = B = Integer)。

然后,操作map定义如下:

M<B> map(M<A>, F<A, B>)

也就是说,操作将返回M<B>,大概是将F<A, B>应用于A中的每个M<A>

在实践中......

Google开发了一个名为Guava的精彩图书馆,它为Java带来了许多功能习惯。

在Guava中,map操作称为transform,它可以在任何Iterable上运行。它还有更具体的实现,可直接在列表,集等上使用。

使用Guava,您要编写的代码如下所示:

static List<Integer> add2(List<Integer> ns) {
  return Lists.transform(ns, new Function<Integer, Integer>() {
    @Override Integer apply(Integer n) { return n + 2; }
  }
}

这很简单。

此代码不会触及原始列表,它只会提供一个新列表,根据需要计算其值(也就是说,除非需要,否则不会计算新创建的列表的值 - 它被称为< em>懒惰的操作)。

作为最后的考虑因素,您无法绝对确定您能够返回完全相同的List实现。正如许多其他人所指出的那样,除非有一个非常具体的原因,否则你不应该真正关心。这就是为什么List是一个接口,你不关心实现。

答案 1 :(得分:2)

从根本上说,List界面并不能保证您有办法复制它。

您可能会对各种技巧感到满意:

  • 在传入的clone()上使用List,虽然它可能会抛出,或者(因为它在Object中受到保护)根本无法访问
  • 使用反射在传入的List
  • 中查找公共无参数构造函数
  • 尝试序列化和反序列化以执行“深度克隆”
  • 创建某种工厂并构建有关如何复制代码可能遇到的每种不同类型List的知识(如果它是由unmodifiableList()创建的包装器,或者由{{1}支持的一些古怪的自定义实现}}?)
  • 如果所有其他方法都失败了,要么投掷,要么返回RandomAccessFileArrayList,因为缺少更好的选择

答案 2 :(得分:1)

使用接口(在本例中为List)的重点是抽象实现隐藏在接口后面的事实。

然而,您的意图很明确:Clonable接口支持创建具有相同状态的新实例。您的List可能未定义此界面。

重新考虑这种情况通常是一个好主意:为什么你需要在这个地方克隆List这个类?您的列表创建者不应该负责克隆列表吗?或者不应该知道类型的来电者确保他通过他的列表的克隆?

或许,如果您按照定义的那样查找语义,则可以实现所有受支持的List

static Vector<Integer> addTwo(Vector<Integer> vector) {
    Vector<Integer> copy = null; // TODO: copy the vector
    return addTwo_mutable(copy);
}
static ArrayList<Integer> addTwo(ArrayList<Integer> aList) {
    ArrayList<Integer> copy = null; // TODO: copy the array list
    return addTwo_mutable(copy);
}
static LinkedList<Integer> addTwo(LinkedList<Integer> lList) {
    LinkedList<Integer> copy = null; // TODO: copy the linked list
    return addTwo_mutable(copy);
}

private <T extends List<Integer>> static T addTwo_mutable(T list) {
    return list; // TODO: implement
}

甚至,当你不支持数据类型时,你会得到一个很好的编译器错误,指出的方法不存在。

(未经测试的代码)

答案 3 :(得分:1)

您可以使用反射在lst.getClass()的结果上查找公共零参数构造函数,然后调用它()以获取您将结果放入其中的List。 Java Collections Framework建议Collection的任何派生都提供零参数构造函数。这样,您的结果我们与参数的运行时类相同。

答案 4 :(得分:1)

这是一个既不复制也不修改原始列表的变体。相反,它用另一个对象包装原始列表。

public List<Integer> add2(final List<Integer> lst) {
    return new AbstractList<Integer>() {
        public int size() {
            return lst.size();
        }
        public Integer get(int index) {
            return 2 + lst.get(index);
        }
    };
}

返回的列表不可修改,但只要原始列表发生更改,它就会更改。 (这实现了基于索引访问的迭代器,因此链表的速度会很慢。然后根据AbstractSequentialList更好地实现它。)

当然,结果列表显然与原始列表不是同一类

仅当您真正需要原始列表的只读两个视图时才使用此解决方案,而不是如果您想要具有类似属性的修改后的副本。

答案 5 :(得分:0)

好吧有人提到反思。这似乎是一个优雅的解决方案:

import java.util.*;
public class ListAdd {

    static List<Integer> add2 (List<Integer> lst) throws Exception {

        List<Integer> result = lst.getClass().newInstance();
        for (Integer i : lst) result.add(i + 2);

        return result;
    }
}

简洁,但它引发了一个检查异常,这不太好。

另外,如果我们也可以在混凝土类型上使用这种方法,那就更好了,例如如果a是值为1,2,3的ArrayList,我们可以调用add2(a)并获得ArrayList回来吗?因此,在改进版本中,我们可以使签名通用:

static <T extends List<Integer>> T add2 (T lst) {
    T res;
    try {
        res = (T) lst.getClass().newInstance();

    } catch (InstantiationException e) {
        throw new IllegalArgumentException(e);
    } catch (IllegalAccessException e) {
        throw new RuntimeException(e);
    }

    for (Integer i : lst) res.add(i + 2);
    return res;
}    

我认为抛出一个运行时异常是最糟糕的选择,如果传入一个没有nullary construcor的列表。我没有看到确保它的方法。 (Java 8类型注释可能会拯救?)返回null会有点无用。

使用此签名的缺点是我们无法返回ArrayList默认,因为我们可以作为抛出异常的替代方法,因为返回类型保证与传入的类型相同。但是,如果用户实际上想要一个ArrayList(或其他一些默认类型),他可以创建一个ArrayList副本并使用该方法。

如果有API设计经验的人读到这个,我会很有兴趣知道你对哪个是更好的选择的看法:1)返回一个需要明确地回归到原始类型的List,但是能够返回一个不同的具体类型,或者2)确保返回类型是相同的(使用泛型),但是如果(例如)传入没有nullary构造函数的单例对象会有异常吗?

答案 6 :(得分:0)

只是为了向您展示在一般情况下您不想做的事情,请考虑以下课程:

final class MyList extends ArrayList<Integer> {
  private MyList() {
    super.add(1);
    super.add(2);
    super.add(3);
  }
  private static class SingletonHolder {
    private static final MyList instance = new MyList();
  }
  public static MyList getInstance() {
    return SingletonHolder.instance;
  }
}

它是一个单例(顺便说一句,也是一个懒惰的,线程安全的单例),它的唯一实例可以从MyList.getInstance()获得。您不能可靠地使用反射(因为构造函数是私有的;对于您使用反射,您必须依赖专有的,非标准的,非可移植的API,或者由于SecurityManager而可能破坏的代码)。因此,您无法使用不同的值返回此列表的新实例。

它也是final,所以你不能归还它的孩子。

此外,可以覆盖将修改列表的ArrayList的每个方法,以便它实际上是一个不可变的单例。

现在,为什么要返回完全相同的List实现?

答案 7 :(得分:-1)

编辑:我认为遵循基于反射的代码应该满足OP的要求:

static List<Integer> add2 (List<Integer> lst) throws Exception {
    Class<?> cl = lst.getClass();
    Constructor<?> con = cl.getConstructor(int.class);
    @SuppressWarnings("unchecked")
    List<Integer> copy = (List<Integer>) con.newInstance(lst.size());
    for (Integer i : lst)
        copy.add(i.intValue()+2);
    return copy;
}

测试

int[] intA = {5, 8, 9, 11};
List<Integer> l = new Vector<Integer>();
for (int i: intA)
    l.add(i);
System.out.println("original: " + l.getClass().getCanonicalName() + ", " + l);
List<Integer> n = add2(l);
System.out.println("converted: " + n.getClass().getCanonicalName() + ", " + n);

输出

original: java.util.Vector, [5, 8, 9, 11]
converted: java.util.Vector, [7, 10, 11, 13]