如何在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;
}
}
答案 0 :(得分:6)
已经有很多答案,但我想向您展示一种不同的方式来思考这个问题。
在函数式编程领域,您要执行的操作称为map
。这是我们在函数式语言中一直都在做的事情。
让M<A>
成为某种容器(在您的情况下,M
将是List
,A
将是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
unmodifiableList()
创建的包装器,或者由{{1}支持的一些古怪的自定义实现}}?)RandomAccessFile
或ArrayList
,因为缺少更好的选择答案 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)
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]