【发布时间】:2015-08-26 23:38:15
【问题描述】:
在 Java8 流中,我可以修改/更新其中的对象吗?
例如。 List<User> users:
users.stream().forEach(u -> u.setProperty("value"))
【问题讨论】:
标签: java java-8 java-stream
在 Java8 流中,我可以修改/更新其中的对象吗?
例如。 List<User> users:
users.stream().forEach(u -> u.setProperty("value"))
【问题讨论】:
标签: java java-8 java-stream
是的,您可以修改流中对象的状态,但通常您应该避免修改流的源的状态。从流包文档的non-interference 部分我们可以读到:
对于大多数数据源,防止干扰意味着确保在流管道执行期间根本不修改数据源。值得注意的例外是其源是并发集合的流,这些集合专门设计用于处理并发修改。并发流源是那些
Spliterator报告CONCURRENT特征的源。
这样就好了
List<User> users = getUsers();
users.stream().forEach(u -> u.setProperty(value));
// ^ ^^^^^^^^^^^^^
// \__/
但这在大多数情况下不是
users.stream().forEach(u -> users.remove(u));
//^^^^^ ^^^^^^^^^^^^
// \_____________________/
并可能抛出ConcurrentModificationException 甚至其他意外异常,例如 NPE:
List<Integer> list = IntStream.range(0, 10).boxed().collect(Collectors.toList());
list.stream()
.filter(i -> i > 5)
.forEach(i -> list.remove(i)); //throws NullPointerException
【讨论】:
Iterator,它会在迭代时阻止修改列表(删除/添加新元素),并且流和 for-each 循环都在使用它。你可以尝试使用像for(int i=0; ..; ..) 这样的简单循环,它没有这个问题(但当其他线程修改你的列表时不会阻止你)。您也可以使用list.removeAll(Collection)、list.removeIf(Predicate) 等方法。 java.util.Collections 类也有一些有用的方法,例如 addAll(CollectionOfNewElements,list)。
filter()
要对流的源进行结构修改,正如 Pshemo 在他的回答中提到的那样,一种解决方案是使用主列表中的项目创建 Collection 的新实例,例如 ArrayList;遍历新列表,并在主列表上执行操作。
new ArrayList<>(users).stream().forEach(u -> users.remove(u));
【讨论】:
要摆脱 ConcurrentModificationException 使用 CopyOnWriteArrayList
【讨论】:
List<Something> - 你不知道那是否是一个CopyOnWriteArrayList。所以这更像是一个平庸的评论,但不是一个真正的答案。
恕我直言,功能方式是:
import static java.util.stream.Collectors.toList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
public class PredicateTestRun {
public static void main(String[] args) {
List<String> lines = Arrays.asList("a", "b", "c");
System.out.println(lines); // [a, b, c]
Predicate<? super String> predicate = value -> "b".equals(value);
lines = lines.stream().filter(predicate.negate()).collect(toList());
System.out.println(lines); // [a, c]
}
}
在这个解决方案中,原始列表没有被修改,但应该在一个新列表中包含您的预期结果,该列表可在与旧列表相同的变量下访问
【讨论】:
您可以只filter() 然后map() 您的结果,而不是创建奇怪的东西。
这更具可读性和确定性。 Streams 只会在一个循环中完成。
【讨论】:
如前所述 - 您无法修改原始列表,但您可以将项目流式传输、修改和收集到新列表中。这是如何修改字符串元素的简单示例。
public class StreamTest {
@Test
public void replaceInsideStream() {
List<String> list = Arrays.asList("test1", "test2_attr", "test3");
List<String> output = list.stream().map(value -> value.replace("_attr", "")).collect(Collectors.toList());
System.out.println("Output: " + output); // Output: [test1, test2, test3]
}
}
【讨论】:
您可以使用removeIf 有条件地从列表中删除数据。
例如:- 如果你想从一个列表中删除所有偶数,你可以这样做。
final List<Integer> list = IntStream.range(1,100).boxed().collect(Collectors.toList());
list.removeIf(number -> number % 2 == 0);
【讨论】:
这可能有点晚了。但这是其中一种用法。这是为了获取文件数的计数。
创建一个指向内存的指针(在这种情况下是一个新的 obj)并修改对象的属性。 Java 8 流不允许修改指针本身,因此如果您将 count 声明为变量并尝试在流中递增,它将永远不会工作并首先引发编译器异常
Path path = Paths.get("/Users/XXXX/static/test.txt");
Count c = new Count();
c.setCount(0);
Files.lines(path).forEach(item -> {
c.setCount(c.getCount()+1);
System.out.println(item);});
System.out.println("line count,"+c);
public static class Count{
private int count;
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
@Override
public String toString() {
return "Count [count=" + count + "]";
}
}
【讨论】:
是的,您同样可以修改或更新列表中对象的值:
users.stream().forEach(u -> u.setProperty("some_value"))
但是,上述语句将对源对象进行更新。在大多数情况下这可能是不可接受的。
幸运的是,我们确实有另一种方法:
List<Users> updatedUsers = users.stream().map(u -> u.setProperty("some_value")).collect(Collectors.toList());
它会返回一个更新的列表,而不妨碍旧列表。
【讨论】: