Modifying Local Variable from Inside Lambda

Modifying local variable from inside lambda

Use a wrapper

Any kind of wrapper is good.

With Java 10+, use this construct as it's very easy to setup:

var wrapper = new Object(){ int ordinal = 0; };
list.forEach(s -> {
s.setOrdinal(wrapper.ordinal++);
});

With Java 8+, use either an AtomicInteger:

AtomicInteger ordinal = new AtomicInteger(0);
list.forEach(s -> {
s.setOrdinal(ordinal.getAndIncrement());
});

... or an array:

int[] ordinal = { 0 };
list.forEach(s -> {
s.setOrdinal(ordinal[0]++);
});

Note: be very careful if you use a parallel stream. You might not end up with the expected result. Other solutions like Stuart's might be more adapted for those cases.

For types other than int

Of course, this is still valid for types other than int.

For instance, with Java 10+:

var wrapper = new Object(){ String value = ""; };
list.forEach(s->{
wrapper.value += "blah";
});

Or if you're stuck with Java 8 or 9, use the same kind of construct as we did above, but with an AtomicReference...

AtomicReference<String> value = new AtomicReference<>("");
list.forEach(s -> {
value.set(value.get() + s);
});

... or an array:

String[] value = { "" };
list.forEach(s-> {
value[0] += s;
});

How I can use an external variable in a lambda expression

You can use StringBuffer

private String getStringActiveRooms(@NotNull ArrayList<Chat_room> c){
final StringBuffer i = new StringBuffer();
c.forEach( (chat_room) -> i.append(chat_room.getName() + "[" + chat_room.activeUsers() + "/" + chat_room.maxUsers() + "]" + ", "));

return i.toString();

}

In addition you have to know that using StringBuffer is better from a performance point of view: String are immutable this means that if you have a String i every iteration i + "something" creates a new String object in the heap space allocating memory for a temporary variable

Set a local variable value from inside a lambda

You have two alternatives here:

  • Create an class to have this attribute, so the object from it will be final, but not the instance variables
  • Make the variable class scope (be careful with multithreading)

These solutions work, but maybe it's not the best option, so to have some inspiration see: Lambdas: local variables need final, instance variables don't

Variable used in lambda expression should be final or effectively final

A final variable means that it can be instantiated only one time.
in Java you can't reassign non-final local variables in lambda as well as in anonymous inner classes.

You can refactor your code with the old for-each loop:

private TimeZone extractCalendarTimeZoneComponent(Calendar cal,TimeZone calTz) {
try {
for(Component component : cal.getComponents().getComponents("VTIMEZONE")) {
VTimeZone v = (VTimeZone) component;
v.getTimeZoneId();
if(calTz==null) {
calTz = TimeZone.getTimeZone(v.getTimeZoneId().getValue());
}
}
} catch (Exception e) {
log.warn("Unable to determine ical timezone", e);
}
return null;
}

Even if I don't get the sense of some pieces of this code:

  • you call a v.getTimeZoneId(); without using its return value
  • with the assignment calTz = TimeZone.getTimeZone(v.getTimeZoneId().getValue()); you don't modify the originally passed calTz and you don't use it in this method
  • You always return null, why don't you set void as return type?

Hope also these tips helps you to improve.

Modify Local Variables Inside Kotlin Lambdas

In Android Studio you can use Tools -> Kotlin -> Show Kotlin Bytecode, and then Decompile to see what the corresponding Java code might look like.

You'll then see that color actually turns into a final IntRef color = new IntRef();, where IntRef is a class in the Kotlin runtime that has a public int element member holding the actual value.

Since color is a final reference to an object, your OnCheckedChangeListener can capture it just fine. The member int is not final however, which is why you can assign to it (the compiler turns your color = index into color.element = index;).

C++: Change variables captured in lambda from outside

To answer your question, no it is not possible to change local variables of a function/functor (which is what lambda is) from the outside.

Think of lambda as "regular" functions whose name you don't have. Just like for regular functions you can't change local variable values from the outside (params passed by value), you can't do it with lambda too. This is a necessary requirement for security & predictability of the result.

If you have a problem where you need to do this, as others have suggested use [&] syntax.

(There exists https://www.eecs.umich.edu/courses/eecs588.w14/static/stack_smashing.pdf but I think that relies on knowledge of compiler/system implementation and usually gets into undefined behaviour)

Lambdas: local variables need final, instance variables don't

The fundamental difference between a field and a local variable is that the local variable is copied when JVM creates a lambda instance. On the other hand, fields can be changed freely, because the changes to them are propagated to the outside class instance as well (their scope is the whole outside class, as Boris pointed out below).

The easiest way of thinking about anonymous classes, closures and labmdas is from the variable scope perspective; imagine a copy constructor added for all local variables you pass to a closure.

Why don't instance fields need to be final or effectively final to be used in lambda expressions?

Instance variables are stored in the heap space whereas local variables are stored in the stack space. Each thread maintains its own stack and hence the local variables are not shared across the threads. On the other hand, the heap space is shared by all threads and therefore, multiple threads can modify an instance variable. There are various mechanisms to make the data thread-safe and you can find many related discussions on this platform. Just for the sake of completeness, I've quoted below an excerpt from http://web.mit.edu/6.005/www/fa14/classes/18-thread-safety/

There are basically four ways to make variable access safe in
shared-memory concurrency:

  • Confinement. Don’t share the variable between threads. This idea is called confinement, and we’ll explore it today.
  • Immutability. Make the shared data immutable. We’ve talked a lot about immutability already, but there are some additional constraints
    for concurrent programming that we’ll talk about in this reading.
  • Threadsafe data type. Encapsulate the shared data in an existing threadsafe data type that does the coordination for you. We’ll talk
    about that today.
  • Synchronization. Use synchronization to keep the threads from accessing the variable at the same time. Synchronization is what you
    need to build your own threadsafe data type.

Why variable used in lambda expression should be final or effectively final

I'd like to preface this answer by saying what I show below is not actually how lambdas are implemented. The actual implementation involves java.lang.invoke.LambdaMetafactory if I'm not mistaken. My answer makes use of some inaccuracies to better demonstrate the point.


Let's say you have the following:

public static void main(String[] args) {
String foo = "Hello, World!";
Runnable r = () -> System.out.println(foo);
r.run();
}

Remember that a lambda expression is shorthand for declaring an implementation of a functional interface. The lambda body is the implementation of the single abstract method of said functional interface. At run-time an actual object is created. So the above results in an object whose class implements Runnable.

Now, the above lambda body references a local variable from the enclosing method. The instance created as a result of the lambda expression "captures" the value of that local variable. It's almost (but not really) like you have the following:

public static void main(String[] args) {
String foo = "Hello, World!";

final class GeneratedClass implements Runnable {

private final String generatedField;

private GeneratedClass(String generatedParam) {
generatedField = generatedParam;
}

@Override
public void run() {
System.out.println(generatedField);
}
}

Runnable r = new GeneratedClass(foo);
r.run();
}

And now it should be easier to see the problems with supporting concurrency here:

  1. Local variables are not considered "shared variables". This is stated in §17.4.1 of the Java Language Specification:

    Memory that can be shared between threads is called shared memory or heap memory.

    All instance fields, static fields, and array elements are stored in heap memory. In this chapter, we use the term variable to refer to both fields and array elements.

    Local variables (§14.4), formal method parameters (§8.4.1), and exception handler parameters (§14.20) are never shared between threads and are unaffected by the memory model.

    In other words, local variables are not covered by the concurrency rules of Java and cannot be shared between threads.

  2. At a source code level you only have access to the local variable. You don't see the generated field.

I suppose Java could be designed so that modifying the local variable inside the lambda body only writes to the generated field, and modifying the local variable outside the lambda body only writes to the local variable. But as you can probably imagine that'd be confusing and counterintuitive. You'd have two variables that appear to be one variable based on the source code. And what's worse those two variables can diverge in value.

The other option is to have no generated field. But consider the following:

public static void main(String[] args) {
String foo = "Hello, World!";
Runnable r = () -> {
foo = "Goodbye, World!"; // won't compile
System.out.println(foo);
}
new Thread(r).start();
System.out.println(foo);
}

What is supposed to happen here? If there is no generated field then the local variable is being modified by a second thread. But local variables cannot be shared between threads. Thus this approach is not possible, at least not without a likely non-trivial change to Java and the JVM.

So, as I understand it, the designers put in the rule that the local variable must be final or effectively final in this context in order to avoid concurrency problems and confusing developers with esoteric problems.



Related Topics



Leave a reply



Submit