单线程应用程序中的 AtomicInteger 和 lambda 表达式



我需要在JButton的ActionListener中修改lambda表达式中的局部变量,由于我无法直接修改它,我遇到了AtomicInteger类型。

我实施了它,效果很好,但我不确定这是一个好的做法,还是解决这种情况的正确方法。

我的代码如下:

newAnchorageButton.addActionListener(e -> {
AtomicInteger anchored = new AtomicInteger();

anchored.set(0);

cbSets.forEach(cbSet ->
cbSet.forEach(cb -> {
if (cb.isSelected())
anchored.incrementAndGet();
})
);

// more code where I use the 'anchored' variable...
}

我不确定这是否是解决这个问题的正确方法,因为我读到AtomicInteger主要用于与并发相关的应用程序,并且这个程序是单线程的,但同时我找不到其他方法来解决这个问题。

我可以简单地使用两个嵌套的for循环来遍历这些数组,但根据sonarlint vscode扩展,我正在尽可能地降低方法的认知复杂性,从理论上讲,留下这些for循环会增加方法的复杂性,从而增加其可读性和可维护性。

用lambda表达式替换for循环降低了认知的复杂性,但也许我不应该太注意它

虽然在单线程代码中足够安全,但最好以功能性的方式对其进行计数,如下所示:

long anchored = cbSets.stream()     // get a stream of the sets
.flatMap(List::stream)          // flatten to list of cb's
.filter(JCheckBox::isSelected)  // only selected ones
.count();                       // count them

我们不改变累加器,而是将扁平流限制为我们感兴趣的流,并要求计数。

不过,更一般地说,在没有可变变量的情况下,总是可以对事物进行汇总或聚合值。考虑:

record Country(int population) { }
countries.stream()
.mapToInt(Country::population)
.reduce(0, Math::addExact)

注意:我们从不更改任何值;相反,我们将每个连续的值与前一个值组合,生成一个新值。可以使用sum(),但我更喜欢reduce(0, Math::addExact)以避免溢出的可能性。

并将其留给循环理论上会增加方法的复杂性,从而增加其可读性和可维护性。

这显然是恶作剧。x.forEach(foo -> bar)并不比for (var foo : x) bar;"认知简单"——你可以直接将每个AST节点从一个映射到另一个。

如果使用一个定义来定义复杂性,得出的结论是其中一个比另一个复杂得多,那么唯一正确的结论是该定义很愚蠢,应该修正或放弃

为了实用:是的,引入AtomicInteger虽然在性能方面不会有任何区别,但确实使方式的代码变得更加复杂。AtomicInteger在代码中的简单存在表明并发性在这里是相关的。事实并非如此,所以你必须添加一条评论来解释你为什么使用它。评论是邪恶的。(它们意味着代码本身并不能说明问题,而且它们不能以任何方式进行测试)。他们往往是最不邪恶的,但他们仍然是邪恶的。

保持基于lambda的代码在认知上易于遵循的一般"技巧"是接受管道:

  • 您编写了一些"形成"流的代码。这可以像list.stream()一样简单,但有时您会对集合集合进行流连接或平面映射
  • 您有一个操作管道,它对流中的单个元素进行操作,而不引用整个元素或任何相邻元素
  • 最后,您进行reduce(使用collectreducemax——一些终止符),以便reduce方法返回您需要的内容

上述模型(另一个答案正好遵循它)往往会导致代码像"旧式"代码一样可读/复杂,而且很少(但有时!)可读性更强,而且明显不那么复杂。偏离它,结果几乎总是要复杂得多——一个明显的失败者。

并非java中的所有for循环都适合上述模型。如果它不合适,那么试图将特定的方桩强行插入圆孔将需要付出很大的努力,而且几乎总是会导致代码明显更糟:要么慢一个数量级,要么认知更复杂。

这也意味着,将完全可读的非流代码重写为基于流的代码实际上永远不"值得";根据一些个人喜好,它充其量会变得可读性高一个百分点,但没有得到普遍认可的显著改进。

关掉那个愚蠢的门楣规则。事实上,它认为上述"不那么"复杂,并且它显然确定for (var foo : x) bar;x.forEach(foo -> bar)"更复杂",这足以证明它的伤害远大于帮助。

我有以下内容要添加到另外两个答案中:

您的代码中有两个通用的良好实践是有问题的:

  1. Lambdas的长度不应超过3-4行
  2. 除了在某些精确的情况下,流操作的lambda应该是无状态的

对于#1,考虑将lambda的代码提取到私有方法,例如,当它变得太长时。您可能会获得可读性,也可能会更好地将UI与业务逻辑分离。

对于#2,您可能并不担心,因为您目前在单个线程中工作,但流可以并行化,并且它们可能并不总是像您认为的那样执行。出于这个原因,在流管道操作中保持代码无状态总是更好的。否则你可能会感到惊讶。

一般来说,流非常好,非常简洁,但有时对好的旧循环也这样做会更好。不要犹豫,回到经典循环。

当Sonar告诉你复杂度太高时,事实上,你应该试着将你的代码分解为更小的方法,改进你的对象的模型,等等

最新更新