使用AtomicInteger在Stream中进行索引是一种合法的方式吗?

Nik*_*las 4 java iteration java-8 java-stream

我想得到一个答案,指出下面在一个非常简单的例子中描述的以下想法通常被认为是坏的并且知道它的弱点的原因.

我有一句话,我的目标是让每一秒都变成大写.我对这两个案例的出发点完全相同:

String sentence = "Hi, this is just a simple short sentence";
String[] split = sentence.split(" ");
Run Code Online (Sandbox Code Playgroud)

传统和程序的做法是:

StringBuilder stringBuilder = new StringBuilder();
for (int i=0; i<split.length; i++) {
    if (i%2==0) {
        stringBuilder.append(split[i]);
    } else {
        stringBuilder.append(split[i].toUpperCase());
    }
    if (i<split.length-1) { stringBuilder.append(" "); }
}
Run Code Online (Sandbox Code Playgroud)

当想要使用,由于lambda表达式中使用的有效final或final变量约束,使用受到限制.我必须使用数组及其第一个也是唯一的索引来解决这个问题,我在问题的第一个评论中建议如何在Java Stream中增加一个值.这是一个例子:

int index[] = {0};
String result = Arrays.stream(split)
    .map(i -> index[0]++%2==0 ? i : i.toUpperCase())
    .collect(Collectors.joining(" "));
Run Code Online (Sandbox Code Playgroud)

是的,这是一个糟糕的解决方案,我听到一些隐藏在我无法找到的问题评论中的好理由(如果你提醒我其中一些,如果可能的话我会投票两次).但是,如果我使用AtomicInteger它会产生什么影响呢?它是一种安全好的方式,与前一种相比没有副作用吗?

AtomicInteger atom = new AtomicInteger(0);
String result = Arrays.stream(split)
    .map(i -> atom.getAndIncrement()%2==0 ? i : i.toUpperCase())
    .collect(Collectors.joining(" "));
Run Code Online (Sandbox Code Playgroud)

无论它看起来多么丑陋,我都要求描述可能的弱点及其原因.我不关心性能,而是第二解决方案的设计和可能的弱点.

请不要将AtomicInteger与多线程问题相匹配.我使用这个类,因为它以我需要的方式接收,递增和存储该值.

正如我在答案中经常说的那样,"Java Stream-API"并不是万能的子弹.我的目标是探索并找到这句话适用的边缘,因为我发现最后一个片段与StringBuilder片段相比非常清晰,可读和简洁.

编辑:是否存在适用于上述代码段的任何替代方法以及在使用Stream-API进行迭代时需要使用项目和索引的所有问题?

Tur*_*g85 10

软件包文档java.util.stream说明:

通常,不鼓励行为参数对流操作的副作用,因为它们通常会导致无意中违反无国籍要求以及其他线程安全危险.

[...]

副作用的排序可能令人惊讶.即使管道被约束以产生与流源的遭遇顺序一致的结果(例如,IntStream.range(0,5).parallel().map(x -> x*2).toArray()必须产生[0, 2, 4, 6, 8]),也不保证将映射器函数应用于各个元素的顺序,或者什么线程为给定元素执行任何行为参数.

这意味着元素可能无序处理,因此您的Stream解决方案可能会产生错误的结果.

这是(至少对我而言)反对你的两个Stream解决方案的杀手论点.

通过消除过程,我们只剩下"传统解决方案".老实说,我认为这个解决方案没有任何问题.如果你想摆脱for-loop,你可以使用foreach-loop 重写这段代码:

boolean toUpper = false; // 1st String is not capitalized
for (String word : splits) {
    stringBuilder.append(toUpper ? word.toUpperCase() : word);
    toUpper = !toUpper;
}
Run Code Online (Sandbox Code Playgroud)

对于流式化(据我所知)正确的解决方案,请看看Octavian R.的答案.


你的问题."流的限制"是基于意见的.

问题的答案在此结束.其余的是我的意见,应该被视为这样.


在Octavian R.的解决方案中,我们创建了一个人工索引集IntStream,然后用于访问String[].对我来说,这比简单的for- 或 - foreach循环具有更高的认知复杂性,在这种情况下,我认为使用流而不是循环没有任何好处.


Oct*_* R. 6

在Java中,与Scala相比,您必须具有创造性.一个没有突变的解决方案就是这个:

String sentence = "Hi, this is just a simple short sentence";
String[] split = sentence.split(" ");
String result = IntStream.range(0, split.length)
                         .mapToObj(i -> i%2==0 ? split[i].toUpperCase():split[i])
                         .collect(Collectors.joining(" "));
System.out.println(result);
Run Code Online (Sandbox Code Playgroud)

在Java流中,您应该避免突变.使用AtomicInteger的解决方案很难看,这是一个不好的做法.

亲切的问候!