如何在Java8的流/过滤器中使用非final变量?

4

在我的使用场景中,我想要更新一个变量的值,并在下一次迭代中引用它。

但是Java编译器报错了。以下是我的代码:

static String convertList(        
  List<Map.Entry<String, String>> map,         
  String delimiter,            
  long maxLength        
) {          
    long currentLength = 0L;          
    return map.stream()
    .map(e->e.getKey() + "=" + e.getValue())        
    .filter(p->{                
      long pLength = p.getBytes(StandardCharsets.UTF_8).length;        
      currentLength = currentLength + pLength;        
      if (currentLength <= maxLength) {         
        return true;        
      } else {
        return false;        
      }
    })
  .collect(Collectors.joining(delimiter));        
}

我正在尝试将列表中的值转换为字符串,直到长度[till this iteration] ≤ maxlength。

有人能帮我修复这个问题吗?我遇到了“lambda表达式引用的本地变量必须是final或有效final”的错误。


3
这正是流不打算处理的内容。顺便说一句,像 if (currentLength <= maxLength) { return true; } else { return false; } 这样的语句毫无意义。可以改写为 return currentLength <= maxLength; - Holger
3个回答

4

为了在lambda内部使用变量,必须将其声明为final或有效final。您仍然可以通过使用一个final的“容器对象”(如数组)来实现您的目标 - 特别是对于您的示例,long[1]AtomicLong都可以很好地工作 - 引用是final的,但您可以更改内容。

以下是基于您的代码的示例:

final long[] currentLength = new long[1];          
return map.stream()
    .map(e->e.getKey() + "=" + e.getValue())        
    .filter(p->{                
        long pLength = p.getBytes(StandardCharsets.UTF_8).length;        
        currentLength[0] = currentLength[0] + pLength;        
        if (currentLength[0] + maxLength <= maxLength) {         
            return true;        
        } else {
            return false;        
        }
    }).collect(Collectors.joining(delimiter));

请注意,您也可以简化过滤器,如下所示:

    .filter(p->{                
        long pLength = p.getBytes(StandardCharsets.UTF_8).length;        
        currentLength[0] = currentLength[0] + pLength;        
        return (currentLength[0] + maxLength <= maxLength);
    })

谢谢。它像魔法一样奏效。我刚刚注意到我没有将分隔符计入总大小。你有什么建议吗?我对Java8还比较新。 - Dany
我不是完全确定你的意思,但是你的过滤条件看起来有点可疑 - 它几乎总是返回 false(只有当 currentLength 为 0 或负数时才为 true,而它从未为负数)。 - Krease
抱歉,那是一个错误。我已经更新了问题。检查语句是 if (currentLength <= maxLength) { - Dany
收集器在每次迭代中将分隔符连接到最终字符串上。我希望将这些字节计算在总大小限制中。 - Dany

1
你应该使用循环和break;一旦满足条件。这样做更快,因为它可以提前退出(流将遍历整个列表),并且不违反Stream.filter的规范,该规范要求传递的谓词必须是无状态的。

Stream<T> filter(Predicate<? super T> predicate)

predicate - 一个非干扰的、无状态的谓词,应用于每个元素以确定是否应包括在内


我也在寻找同样的答案。你能给我一个解决这个问题的示例代码吗?那将非常有帮助。 - Dany
不,我认为散文应该足够了,因为编写循环在任何编程语言中都是一项非常基本的任务,并且已经被许多教程涵盖。 - the8472
我其实应该给你@the8472一个踩的,因为你最后的评论非常“基础”,你本可以把它写下来。现在我又得去别处寻找解决方案了。 - ivoba

1

我不确定在进行有状态操作时Stream是否是正确的工具,但无论如何,这里有一个可行的解决方案,看起来有点hacky

private static String convertList(List<Map.Entry<String, String>> map, String delimiter, long maxLength) {
    AtomicLong currentLength = new AtomicLong();
    return map.stream()
            .map(e -> e.getKey() + "=" + e.getValue())
            .peek(s -> currentLength.addAndGet(s.getBytes(StandardCharsets.UTF_8).length))
            .filter(p -> currentLength.get() <= maxLength)
            .collect(Collectors.joining(delimiter));
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接