Understanding Java 8 Lambda final finally variable

Datetime:2016-08-23 00:38:48          Topic: Java8           Share

I am a little late to the java 8 party and was trying to quickly get some hands on with lambdas and ran into an issue where I got the error message “Variables used in lambda should be final or effectively final” . I know what final is but what is effectively final.

Here is what I was trying to do. I had a HashMap which had values like “one : 1”, “two : 2”, “three : 3” and wanted to replace the String ${one} with 1 etc.

    public String evaluate(HashMap<String, String> variables){
       String result = "${One}, ${two}, ${three}";
       variables.forEach((k,v)->{
          String regex = "\\$\\{" + k + "\\}";
          result=result.replaceAll(regex, v);
       });
       return result;
    }

When I assigned result=result.replaceAll(regex, v); I got the error message – “ Variables used in lambda should be final or effectively final “. On further research using SO forums and blog I learnt that Java 8 has new concept called “Effectively final” variable. It means that a non-final local variable whose value never changes after initialization is called “Effectively Final”. This concept was introduced because prior to Java 8 we could not use a non-final local variable in an anonymous class. If you have access to a local variable in Anonytmous class, you have to make it final.

When Lambdas was introduced, this restriction was eased. Hence to the need to make local varibale final if it’s not changed once it is initialized as Lambda in itself is nothing but an anonymous class. Java 8 realized the pain of declaring local variable final every time developer used Lambda and introduced this concept and made it unnecessary to make local variables final. So if you see the rule for anonymous class still not changed, it’s just you don’t have to write final keyword every time when using lambdas.

OK. So now I understand what the error means but how do I solve my issue where I want to replace the variables using regex. Surprisingly, before I looked updocumentation and SO forums, I checked the options suggested by IntelliJ. It said – “Transform result variable into final one element array”. As I clicked continue, My code transformed to

    public String evaluate(){
       final String[] result = {"${One}, ${two}, ${three}"};

       variables.forEach((k,v)->{
          String regex = "\\$\\{" + k + "\\}";
          result[0] = result[0].replaceAll(regex, v);
       });
       return result[0];
   }

Now what the heck just happened? Why is a String not allowed to change but an Array is allowed to change? Going back to basics of Java I realized that when we decalre a variable final, it is the reference to the object that is set to final. So in this case when I write

final String result= “abc”;

The reference is STRING OBJECT result —memory location —> “abc”

and because the result is declared final, it cannot change the memory reference and point to memory location who value is say

” DEF”

.

When I write

final String [] result = {“abc”}

In this case, result is pointing to a reference that is a Array and that array has value that reference memory location of String “abc” .

FINAL STRING [] result —- Memory location –> Single Array —–Memory location —> “abc”.

So as you see our result now references the memory location of Array element but the value Array element can change. Remember what is constant here is the reference,

not

its values.

~~ Smell the Java ~~

Dinesh





About List