添加到列表时有没有办法避免循环?
Posted
技术标签:
【中文标题】添加到列表时有没有办法避免循环?【英文标题】:Is there a way to avoid loops when adding to a list? 【发布时间】:2015-05-23 05:44:15 【问题描述】:我想知道这样的代码:
List<String> list = new ArrayList<String>();
for(CustomObject co : objects)
list.add(co.getActualText());
它可以写成不同的吗?我的意思当然是在某些时候会有一个循环,但我想知道是否有我忽略的 API 使用
【问题讨论】:
它叫做 Clojure ;D 你可以在这里阅读 (conj):clojuredocs.org/clojure.core/conj 它也被称为 Scala、Haskell 和……(插入任何强大的编程语言的名称) 【参考方案1】:如果您使用 Java 8,则可以利用 Stream API:
List<String> list = objects.stream()
.map(CustomObject::getActualText)
.collect(Collectors.toList());
【讨论】:
TBH 恕我直言,这对于一个简单的任务来说实际上是不可读的。这不是关于你的答案,而是关于 Java 方法本身 不太同意 Jim,这也是习惯新概念的问题。尽管在 Scala 中使用简单的 val list = objects.map(_.getActualText) 会更漂亮 .NET 人使用 LINQ 已经很多年了,很明显,在几乎所有情况下,它都是比列表构建循环更好的方法。我认为 Java 人只需要四处走走就可以改变心态。此代码本身并非不可读。没有经验的人看不懂。这是一个临时状态。 这就是 .NET 世界的做法。我希望我在使用 java 时有流。【参考方案2】:如果你有 Java 8,那么:
objects.forEach(item -> list.add(item.getActualText()));
虽然内部仍然是一个循环。
编辑一点题外话:IMO 这是最易读和最好的解决方案。为什么不直接使用你可能会问的 foreach。答案:因为这样集合选择了迭代项目的最佳方式。例如,ArrayList 不使用迭代器,因为它比你更清楚:
@Override
public void forEach(Consumer<? super E> action)
Objects.requireNonNull(action);
final int expectedModCount = modCount;
@SuppressWarnings("unchecked")
final E[] elementData = (E[]) this.elementData;
final int size = this.size;
for (int i=0; modCount == expectedModCount && i < size; i++)
action.accept(elementData[i]);
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
【讨论】:
我想补充一点,我已经成长为 Konstantin Yovkov 展示的“功能性”方法。风格感觉很干净,即使它看起来很糟糕并且可能在 java 中表现不佳【参考方案3】:当然,Apache Commons 和 Guava 也提供了在不使用 Java 8 的情况下避免循环的方法。
普通CollectionUtils.collect:
CollectionUtils.collect(objects, Transformer.invokerTransformer("getActualText"), list);
番石榴Lists.transform:
List<String> list = Lists.transform(objects,
new Function<CustomObject, String>()
public String apply(CustomObject co)
return co.getActualText();
);
【讨论】:
在 Java 8 中,通过用 lambda 替换匿名类可以更简洁地编写 Guava 示例:Lists.transform(objects, (co) -> co.getActualText())
【参考方案4】:
虽然显然有点荒谬的建议:您可以通过递归添加循环来避免循环。
void add(List<? super String> receiver, CustomObject[] objects)
addRec(receiver, toAdd, 0, objects.length());
void addRec(List<? super String> receiver, CustomObject[] objects, int start, int end)
if (start + 1 == end)
receiver.add(objects[start].getActualText());
else if (start != end)
int mid = (start + end) / 2;
addRec(receiver, objects, start, mid);
addRec(receiver, objects, mid, end);
【讨论】:
由于 O(n) 操作确实需要动态变化的控制流(即循环),这将是我的答案。在命令式语言中它有点笨拙,但在纯函数式语言中,这就是你所拥有的一切。 +1。【参考方案5】:如果您使用Eclipse Collections,您可以从 Java 8 开始编写以下代码:
MutableList<CustomObject> objects = ...
MutableList<String> result = objects.collect(CustomObject::getActualText);
在 Java 5 - 7 中,您可以通过 collect 方法使用表示 SAM 类型 Function 的匿名内部类。
MutableList<CustomObject> objects = ...
MutableList<String> result = objects.collect(new Function<CustomObject, String>()
public String valueOf(CustomObject object)
return object.getActualText();
);
注意:我是 Eclipse Collections 的提交者
【讨论】:
【参考方案6】:在 Java 8 中使用流会更惯用,但如果您希望它更接近传统的基于循环的方法,您可以使用 forEach
:
objects.forEach(co -> list.add(co.getActualText()) );
【讨论】:
【参考方案7】:为了在相互未知的两种类型的列表之间复制一定范围的数据时获得非常好的效率,必须有一种机制,通过这种机制,“受信任”类型可以要求每个类型公开关联的后备数组使用一系列元素,然后使用批量复制操作将数据从一个移动到另一个。完全用 Java 编写这样一个类是可能的,方法是将 GetArraySource
方法传递给受信任的 ArraySource
类的构造函数,该对象可以用来请求与特定元素关联的支持数组(返回将包括支持数组和其中包含的元素范围)。想要复制的代码将调用GetArraySource
,并将由此返回的ArraySource
传递给目标列表的CopyFromArraySource
方法,然后该方法可以要求ArraySource
将一个或多个项目范围复制到它自己的后备数组中(s )。
如果ArraySource
是Java 提供的一个类,并且Oracle 准确记录了它将如何处理它收到的数组,那么ArrayList
和String
之类的类型就有可能将它们的内容公开为@ 987654330@,或接受来自ArraySource
的外部数据,而不会将其数组不当暴露给任何可能滥用它的代码。
不幸的是,除非 Oracle 将这样的东西合并到 Java 发行版中,否则支持可能太少而无用。让源列表支持一个这样的类,目标支持另一个,以及想要复制操作的代码第三个是不好的。所有三个类都需要支持同一个特定的trusted-array-segment-copy-helper 类。
【讨论】:
我认为您对实际数据有类似 C 的 memcopy 之类的东西。对吗? @Jim:我希望Arrays
中的批量复制操作可能会使用大致相当于memcpy
的东西在本地实现,一旦它确定源数组中不存在任何对象目标数组类型无法容纳 [尝试例如从Cat[]
到Animal[]
的批量复制项目可以简单地直接复制引用,但反之则需要在复制引用之前验证每个引用对象的类型]。以上是关于添加到列表时有没有办法避免循环?的主要内容,如果未能解决你的问题,请参考以下文章
使用 for 循环有没有办法控制哪个 # 循环将值附加到列表中?