简体   繁体   中英

How to use non-final variable inside Java8 streams/filters?

In my use case I would like to update the value of a variable and reference the same in next iteration in streams.

But the java compiler is giving me error. Here is my code

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

I am trying to get the values from the list to a string until the length [till this iteration] <= maxlength

could someone help me fix this? I am getting Local variables referenced from a lambda expression must be final or effectively final error.

Your variable must be final / effectively final to use it inside lambda. You can still accomplish your goal by using a final 'container object' like an array - specifically for your example, long[1] or AtomicLong would work well - the reference is final, but you can change out the contents.

Example based on your code:

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

Note, you can also simplify your filter as follows:

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

You should use a loop and break; once your condition is fulfilled. It is faster because it can bail out early (the stream would traverse the whole list) and does not violate the specification of Stream.filter which requires that the passed predicate must be stateless .

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

predicate - a non-interfering, stateless predicate to apply to each element to determine if it should be included

I'm not sure that Stream is the right tool when doing stateful operations but anyway here is a solution that works but looks a bit 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));
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM