Java 8:计算 lambda 迭代的首选方法?
Posted
技术标签:
【中文标题】Java 8:计算 lambda 迭代的首选方法?【英文标题】:Java 8: preferred way to count iterations of a lambda? 【发布时间】:2015-05-01 16:23:26 【问题描述】:我经常遇到同样的问题。我需要计算 lambda 的运行次数,以便在 lambda 之外使用。
例如:
myStream.stream().filter(...).forEach(item -> ... ; runCount++);
System.out.println("The lambda ran " + runCount + "times");
问题是 runCount 必须是 final
,所以它不能是 int
。它不能是Integer
,因为它是不可变的。我可以将其设为类级别变量(即字段),但我只需要在这段代码中使用它。
我知道有多种方法,我只是想知道您的首选解决方案是什么?
您是使用AtomicInteger
还是数组引用或其他方式?
【问题讨论】:
@Sliver2009 不,不是。 @Florian 你必须在这里使用AtomicInteger
。
【参考方案1】:
为了便于讨论,让我重新格式化您的示例:
long runCount = 0L;
myStream.stream()
.filter(...)
.forEach(item ->
foo();
bar();
runCount++; // doesn't work
);
System.out.println("The lambda ran " + runCount + " times");
如果您真的需要在 lambda 中增加一个计数器,典型的做法是将计数器设为 AtomicInteger
或 AtomicLong
,然后调用其中一个增量方法就可以了。
您可以使用单元素 int
或 long
数组,但如果流并行运行,则会出现竞争条件。
但请注意,流以forEach
结尾,这意味着没有返回值。您可以将 forEach
更改为 peek
,它会传递项目,然后计算它们:
long runCount = myStream.stream()
.filter(...)
.peek(item ->
foo();
bar();
)
.count();
System.out.println("The lambda ran " + runCount + " times");
这有点好,但还是有点奇怪。原因是forEach
和peek
只能通过副作用来完成它们的工作。 Java 8 新兴的函数式风格是为了避免副作用。我们通过将计数器的增量提取到流上的count
操作中做了一点。其他典型的副作用是将项目添加到集合中。通常这些可以通过使用收集器来替换。但在不知道您要做什么实际工作的情况下,我无法提出更具体的建议。
【讨论】:
应该注意的是,一旦count
实现开始使用SIZED
流的快捷方式,peek
就会停止工作。对于filter
ed 流,这可能永远不会成为问题,但如果有人稍后更改代码,可能会产生巨大的惊喜……
声明 final AtomicInteger i = new AtomicInteger(1);
并在您的 lambda 中某处使用 i.getAndAdd(1)
。停下来,记住int i=1; ... i++
曾经多么美好。
如果 Java 将在数字类(包括 AtomicInteger
)上实现诸如 Incrementable
之类的接口,并将诸如 ++
之类的运算符声明为看起来很花哨的函数,我们就不需要运算符重载和仍然有非常可读的代码。【参考方案2】:
作为同步麻烦 AtomicInteger 的替代方法,可以使用 整数数组 代替。只要 对数组的引用没有分配另一个数组(这就是重点),它可以用作 final 变量,而 values strong> 的字段可以任意改变。
int[] iarr = 0; // final not neccessary here if no other array is assigned
stringList.forEach(item ->
iarr[0]++;
// iarr = 1; Error if iarr gets other array assigned
);
【讨论】:
如果你想确保引用没有被分配另一个数组,你可以将 iarr 声明为最终变量。但正如@pisaruk 指出的那样,这不会并行工作。 我认为,对于简单的foreach
直接在集合上(没有流),这是一个足够好的方法。谢谢!!
这是最简单的解决方案,只要您不并行运行。【参考方案3】:
AtomicInteger runCount = 0L;
long runCount = myStream.stream()
.filter(...)
.peek(item ->
foo();
bar();
runCount.incrementAndGet();
);
System.out.println("The lambda ran " + runCount.incrementAndGet() + "times");
【讨论】:
请edit 提供更多信息。纯代码和“试试这个”的答案是discouraged,因为它们不包含可搜索的内容,也没有解释为什么有人应该“试试这个”。我们在这里努力成为知识的资源。 你的回答让我很困惑。你有两个变量都命名为runCount
。我怀疑你打算只拥有其中一个,但哪一个?
我发现 runCount.getAndIncrement() 更合适。很好的答案!
AtomicInteger
帮助了我,但我会用new AtomicInteger(0)
初始化它
1) 此代码无法编译:流没有返回 long 的终端操作 2) 即使可以,'runCount' 值将始终为 '1':- 流没有终端操作,因此永远不会调用 peek() lambda 参数 - System.out 行在显示之前增加运行计数【参考方案4】:
你不应该使用 AtomicInteger,除非你有充分的理由使用,否则你不应该使用东西。而使用 AtomicInteger 的原因可能是只允许并发访问等。
当涉及到您的问题时;
Holder 可用于在 lambda 中保持和递增它。之后你可以通过调用runCount.value得到它
Holder<Integer> runCount = new Holder<>(0);
myStream.stream()
.filter(...)
.forEach(item ->
foo();
bar();
runCount.value++; // now it's work fine!
);
System.out.println("The lambda ran " + runCount + " times");
【讨论】:
JDK 中有几个 Holder 类。这个好像是javax.xml.ws.Holder
。
真的吗?为什么?
我同意 - 如果我知道我没有在 lamda/stream 中执行任何并发操作,为什么我要使用旨在满足并发性的 AtomicInteger - 这可能会引入锁定等,即多年前,JDK 引入了一组新的集合及其不执行任何锁定的迭代器的原因 - 为什么要负担一些降低性能的能力,例如在许多情况下不需要锁定时锁定。
Stream.forEach
是明确的不确定性,因此使用 Holder
可能会或可能不会工作,具体取决于底层流。
@GeroldBroser 关于调用线程也是不确定的:“对于任何给定的元素,可以在库选择的任何时间和任何线程中执行操作”。这可能会导致竞争条件导致结果无效。【参考方案5】:
对我来说,这成功了,希望有人觉得它有用:
AtomicInteger runCount = new AtomicInteger(0);
myStream.stream().filter(...).forEach(item -> runCount.getAndIncrement());
System.out.println("The lambda ran " + runCount.get() + "times");
getAndIncrement()
Java 文档说明:
以原子方式递增当前值,具有记忆效应 由 VarHandle.getAndAdd 指定。等价于 getAndAdd(1)。
【讨论】:
我有 ``` AtomicInteger rowCount = new AtomicInteger(0); items.stream() .map(x -> (String.format("%3s. %-45s - count: %s", rowCount.getAndIncrement(), x.getName(), x.getSize()))) .forEach(System.out::println); ```【参考方案6】:另一种选择是使用 apache commons MutableInt。
MutableInt cnt = new MutableInt(0);
myStream.stream()
.filter(...)
.forEach(item ->
foo();
bar();
cnt.increment();
);
System.out.println("The lambda ran " + cnt.getValue() + " times");
【讨论】:
链接服务:org.apache.commons.lang.mutable.MutableInt
【参考方案7】:
如果你不想创建一个字段,因为你只需要在本地,你可以将它存储在一个匿名类中:
int runCount = new Object()
int runCount = 0;
myStream.stream()
.filter(...)
.peek(x -> runCount++)
.forEach(...);
.runCount;
很奇怪,我知道。但它确实将临时变量排除在局部范围之外。
【讨论】:
这里到底发生了什么,需要更多解释一下 @MrCholo 这是一个initializer block。它在构造函数之前运行。 @MrCholo 不,它是一个实例初始化器。 @MrCholo 匿名类不能有显式声明的构造函数。 @OlivierGrégoire 我承认这乍一看令人困惑,但我不确定您为什么认为存在性能问题。这里的大多数解决方案都涉及对象创建。【参考方案8】:另一种方法(如果您希望计数仅在某些情况下增加,例如操作成功时很有用)是这样的,使用mapToInt()
和sum()
:
int count = myStream.stream()
.filter(...)
.mapToInt(item ->
foo();
if (bar())
return 1;
else
return 0;
)
.sum();
System.out.println("The lambda ran " + count + "times");
正如 Stuart Marks 所说,这仍然有些奇怪,因为它并没有完全避免副作用(取决于 foo()
和 bar()
正在做什么)。
另一种在 lambda 中增加可在其外部访问的变量的方法是使用类变量:
public class MyClass
private int myCount;
// Constructor, other methods here
void myMethod()
// does something to get myStream
myCount = 0;
myStream.stream()
.filter(...)
.forEach(item->
foo();
myCount++;
);
在这个例子中,在一种方法中使用类变量作为计数器可能没有意义,所以我会警告不要这样做,除非有充分的理由。如果可能,保留类变量final
有助于线程安全等方面(有关使用final
的讨论,请参阅http://www.javapractices.com/topic/TopicAction.do?Id=23)。
为了更好地了解 lambda 的工作原理,https://www.infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hood 进行了详细介绍。
【讨论】:
else
缺少“
”,if...else
可以缩短为 return bar() ? 1 : 0;
【参考方案9】:
reduce也可以,你可以这样使用
myStream.stream().filter(...).reduce((item, sum) -> sum += item);
【讨论】:
【参考方案10】:对我来说,这是最优雅的方式。
long count = list.stream()
.peek(/* do your stuff here */)
.long();
JDK 9,10 中存在一个错误,导致上述解决方案无法正常工作,但您可以按如下方式解决它。 https://bugs.openjdk.java.net/browse/JDK-8198356
long count = list.stream()
.peek(/* do your stuff here */)
.collect(Collectors.counting());
【讨论】:
【参考方案11】:AtomicInteger runCount = new AtomicInteger(0);
elements.stream()
//...
.peek(runCount.incrementAndGet())
.collect(Collectors.toList());
// runCount.get() should have the num of times lambda code was executed
【讨论】:
【参考方案12】:也可以使用enum
。特别是如果您在一次迭代中有多个计数器:
import java.util.Arrays;
class LambdaCounter
enum CountOf
NO,
OK,
ERROR;
private int count;
// can be named inc(), instead of the Greek capital Delta,
// which stands for the math increment operator '∆' <https://en.wikipedia.org/wiki/%E2%88%86>
synchronized int Δ( final int... times )
if ( times.length <= 0 )
return ++count; // increase by 1
return count += Arrays.stream( times ).sum(); // increase by arguments
// can be named val[ue](), instead of the Greek capital Xi,
// which stands for the math identity operator '≡' <https://en.wikipedia.org/wiki/Triple_bar>
int Ξ()
return count;
public static void main( final String[] args )
Arrays.stream( new int[] 1, 2, 3, 4, 5, 6, 7 )
.forEach( i ->
CountOf.NO.Δ();
@SuppressWarnings( "unused" )
final int LHS_DUMMY =
i % 2 == 0
? CountOf.OK.Δ()
: CountOf.ERROR.Δ();
);
System.out.printf( "No: %d, OK: %d, Error: %d, Error.inc(38): %d, Error.inc(4, 4): %d%n",
CountOf.NO.Ξ(), CountOf.OK.Ξ(), CountOf.ERROR.Ξ(), CountOf.ERROR.Δ( 38 ), CountOf.ERROR.Δ( 4, 4 ) );
// Output:
// No: 7, OK: 3, Error: 4, Error.inc(38): 42, Error.inc(4, 4): 50
【讨论】:
以上是关于Java 8:计算 lambda 迭代的首选方法?的主要内容,如果未能解决你的问题,请参考以下文章