21

I cannot do this in Java:

Optional<String> optStr = Optional.of("foo");
String result;
optStr.ifPresent(s -> result = s);

The doc says that the variables used in lambdas must be effectively final. Then how to extract and store something from a lambda into a variable nicely?

Actually the real use case is more complex.
I want to apply several regular expressions to a string one after another with matcher.replaceAll. I'm doing this in a forEach lambda and wanted to store intermediate results somewhere.

6
  • 1
    What is your whole use-case? What do you want to do with result? Commented Nov 4, 2015 at 9:51
  • 1
    You might want to take a look at orElse / orElseGet Commented Nov 4, 2015 at 9:52
  • Actually the use case is more complex. The lambda expression is larger and I want to assign certain variables inside the lambda Commented Nov 4, 2015 at 9:54
  • 3
    For this particular example, there are obvious alternatives that don't involve leaning on the mutation crutch. Which is also almost certainly true of the "more complex" examples you've got in mind (but haven't shared.) Commented Nov 4, 2015 at 19:07
  • 1
    So you have a stream of regular expressions? And where does the replacement come from? Commented Nov 5, 2015 at 10:18

4 Answers 4

9

The answer is simple: you can't (directly) do that.

A very ugly hack would be to mutate an external object instead of assigning to a variable:

Optional<String> optStr = Optional.of("foo");
StringBuilder sb = new StringBuilder();
optStr.ifPresent(s -> sb.append(s));
String result = sb.toString();

This works because sb is effectively final here

But I want to emphasize the point that this is probably not what you really want to do.

  • If you want to have a default value, use optStr.orElse or optStr.orElseGet instead.
  • If you want to map the result only if it is present, use optStr.map

Since you did not provide your whole use-case, these are just guesses but the key point is that I really do not recommend the above snippet of code: it goes against the concept of functional programming (by mutating an object).

Sign up to request clarification or add additional context in comments.

2 Comments

The use cases are many. For example if you want to increase an outside integer counter in a forEach lambda you can not do that. Or you just want to store some intermediate results from a cyclic calculation. You have to enclose them in dummy objects.
@George Because "increase an outside integer counter in a forEach" sounds a lot like filtering and then counting, which would be a functional way of doing it. Having said that, you could also create your own MutableInteger and increment that object instead. You can't assign but you can mutate.
4

Another way, similar to what Tunaki has written, is to use a single-cell table:

Optional<String> optStr = Optional.of("foo");
String[] temp = new String[1];
optStr.ifPresent(s -> temp[0] = s);
String result = temp[0];

The table object is final, what changes is its content.

Edit: A word of warning though - before using this hacky solution check out the other answers to OP's question, pointing out why it's a bad idea to use this workaround and consider if it's really worth it!

2 Comments

I totally agree with Tunaki, and the fact that using this workaround is a bad idea, I just pointed out that you can 'hack' it other way.
You're absolutely right - I'll edit it pointing to Tunaki's warnings.
3

If the code

Optional<String> optStr = Optional.of("foo");
String result;
optStr.ifPresent(s -> result = s);

was legal, it still was useless, as the variable result is not definitely assigned after the invocation of ifPresent. Java does not allow reading local variables which are only conditionally initialized. So you would need an alternative value for result for the case of an empty Optional, e.g.:

Optional<String> optStr = Optional.of("foo");
String result=null;// or any other default value
optStr.ifPresent(s -> result = s);

But then, if you have defined such a default/fall-back value, you can use the method intended for this purpose:

Optional<String> optStr = Optional.of("foo");
String result=optStr.orElse(null /* or any other default value */);

When you say, you have to initialize more than one variable, it doesn’t change the fact that these variables need to be initialized in either case. Also, there is no benefit in performing the initialization of dependent variables inside a lambda expression passed to the Optional as, after all, the Optional carries only a single value. Everything dependent on that value can get determined after getting that value, independently of the Optional:

Optional<String> optStr = Optional.of("foo");
Type1 variable1;
Type2 variable2;
Type3 variable3;
if(optStr.isPresent()) {
    String s=optStr.get();
    // determine and assign variable1, variable2, variable3 based on s
} else {
    // assign defaults/fall-backs to variable1, variable2, variable3
}

Note that even if your variables are already pre-initialized and you want to mutate them, using if(optional.isPresent()) /* local modifications */ is the simplest way to do it. There’s nothing that works better when you replace this idiom with a lambda expression.

12 Comments

I gave the Optional only for simple illustration. In fact the lambda is arbitrary. Could be a forEach or some custom implementation.
@George: the point doesn’t change. You can’t mutate a local variable using lambda expressions. The correct way to do it will depend on each use case, but we can only answer the question you are actually asking, not the one in your mind.
I have updated the question with the actual use case.
Well, nothing really important has changed. In your original question, you wanted to replace an if statement by lambda expression, now you’re saying that you want to replace a for loop with a lambda expression. The answer still holds. Converting imperative code to use lambda expressions is not an improvement in any way.
very useless answer as it appeared on my search for "assigning value of a loca variable inside lambda in java".
|
2

As per Java Specification, lambda gets the variable values as final from the surrounding context as the lambda is passed to that context at runtime.

The designer of the piece of code which accepts that lambda expression(or Functional Interface) accepts that interface instance\lambda with the faith that its values will not be altered. To strictly make the lambda faithful in this way Java language specification has kept this condition of accessing local context variables as final.

In short, a lambda is NOT supposed to change the state of the context in which it is invoked.

You can use an array to capture values, but still it is not advisable and hopefully java will detect and show warnings for such code in future revisions.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.