代码之家  ›  专栏  ›  技术社区  ›  Nikolas Charalambidis

在流中使用atomicinteger进行索引是否合法?

  •  4
  • Nikolas Charalambidis  · 技术社区  · 6 年前

    我想得到一个答案,指出为什么下面在一个非常简单的例子中描述的想法通常被认为是不好的,并且知道它的弱点。

    我有一句话,我的目标是把每一秒都变成大写。我对这两种情况的出发点完全相同:

    String sentence = "Hi, this is just a simple short sentence";
    String[] split = sentence.split(" ");
    

    这个 传统的 程序方法是:

    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(" "); }
    }
    

    何时使用 由于lambda表达式中使用了有效的final或final变量约束,因此使用受到限制。我必须使用使用数组及其第一个也是唯一的索引的变通方法,这是在我问题的第一个注释中建议的。 How to increment a value in Java Stream .示例如下:

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

    是的,这是一个糟糕的解决方案,我在某个地方听到了一些隐藏在我找不到的问题评论中的好理由(如果你提醒我其中的一些,如果可能的话,我会投两次赞成票)。但是如果我用呢 AtomicInteger -它有什么区别吗?它是一种好的、安全的方法,与前一种方法相比没有副作用吗?

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

    不管它看起来有多难看,我要求描述可能的弱点及其原因。我不关心性能,但第二个解决方案的设计和可能的弱点。

    请不要将atomicinteger与多线程问题匹配。我使用这个类是因为它以我在这个示例中需要的方式接收、递增和存储值。

    正如我在回答中经常说的那样 “Java流API” 不是万能的子弹。我的目标是探索并找到这个句子的边缘,因为我发现最后一个片段与 StringBuilder 的代码段。

    编辑:在使用流API进行迭代时,是否存在适用于上述代码段和所有需要同时使用项和索引的问题的替代方法?

    3 回复  |  直到 6 年前
        1
  •  10
  •   Turing85    6 年前

    这个 documentation of the java.util.stream package 声明:

    一般来说,流操作的行为参数中的副作用是不鼓励的,因为它们通常会导致无意中违反无状态要求,以及其他线程安全隐患。

    […]

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

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

    这是(至少对我而言)一场针对你们两人的致命论战 -解决方案。

    通过消除过程,我们只剩下“传统解决方案”。老实说,我认为这个解决方案没有任何问题。如果你想摆脱 for -循环,可以使用 foreach -循环:

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

    对于简化(据我所知)的正确解决方案, take a look at Octavian R.'s answer .


    你的问题。“流的限制”是基于意见的。

    问题的答案到此结束。其余的都是我的意见,应该这样看待。


    在屋大维R的解中,我们通过 IntStream ,然后用于访问 String[] .对我来说,这比简单的 对于 -或 前臂 -在这种情况下,使用流而不是循环没有任何好处。

        2
  •  6
  •   Octavian R.    6 年前

    在爪哇,与斯卡拉相比,你必须具有创造性。一种没有突变的解决方案是:

    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);
    

    在Java流中,应该避免突变。你的原子整数的解决方案很难看,这是一个糟糕的实践。

    亲切的问候!

        3
  •  4
  •   Holger    6 年前

    如中所述 Turing85’s answer 您的流解决方案不正确,因为它们依赖于处理顺序,这是不保证的。这可能会导致今天并行执行的结果不正确,但即使它碰巧用顺序流生成所需的结果,这只是一个实现细节。它不能保证工作。

    除此之外,在重写代码以使用流API时,没有任何优势,因为它的逻辑基本上仍然是一个循环,但被不同的API混淆了。描述新API思想的最佳方法是说您应该 什么 做但不做 怎样 .

    从JavaSyx 9开始,您可以实现与

    String result = Pattern.compile("( ?+[^ ]* )([^ ]*)").matcher(sentence)
        .replaceAll(m -> m.group(1)+m.group(2).toUpperCase());
    

    它表示希望用大写形式替换每一个第二个单词,但不表示如何进行。这取决于图书馆,它可能使用一个 StringBuilder 而不是拆分成字符串数组,但这与应用程序逻辑无关。

    只要您使用Java8,Id就在循环中,即使切换到更新的Java版本,我也会考虑将循环替换为不紧急的更改。

    上面例子中的模式是以与原始代码在单个空格字符上拆分完全相同的方式编写的。通常,i_d编码_替换第二个单词_更像

    String result = Pattern.compile("(\\w+\\W+)(\\w+)").matcher(sentence)
        .replaceAll(m -> m.group(1)+m.group(2).toUpperCase());
    

    当遇到多个空格或其他分隔符时,其行为会有所不同,但通常更接近实际意图。