Java lambdas have different variable requirements than anonymous inner classes

馋奶兔 提交于 2019-11-30 00:32:57

问题


I have an anonymous inner class and an equivalent lambda. Why are the variable initialization rules stricter for the lambda, and is there a solution cleaner than an anonymous inner class or initializing it in the constructor?

import java.util.concurrent.Callable;

public class Immutable {
    private final int val;

    public Immutable(int val) { this.val = val; }

    // Works fine
    private final Callable<String> anonInnerGetValString = new Callable<String>() {    
        @Override
        public String call() throws Exception {
            return String.valueOf(val);
        }
    };

    // Doesn't compile; "Variable 'val' might not have been initialized"
    private final Callable<String> lambdaGetValString = () -> String.valueOf(val);
}

Edit: I did run across one workaround: using a getter for val.


回答1:


The chapter on lambda expression bodies states

Unlike code appearing in anonymous class declarations, the meaning of names and the this and super keywords appearing in a lambda body, along with the accessibility of referenced declarations, are the same as in the surrounding context (except that lambda parameters introduce new names).

The transparency of this (both explicit and implicit) in the body of a lambda expression - that is, treating it the same as in the surrounding context - allows more flexibility for implementations, and prevents the meaning of unqualified names in the body from being dependent on overload resolution.

They're more strict because of that.

The surrounding context, in this case, is an assignment to a field and the issue at hand is an access of a field, val, a blank final field, in the right hand side of the expression.

The Java Language Specification states

Each local variable (§14.4) and every blank final field (§4.12.4, §8.3.1.2) must have a definitely assigned value when any access of its value occurs.

An access to its value consists of the simple name of the variable (or, for a field, the simple name of the field qualified by this) occurring anywhere in an expression except as the left-hand operand of the simple assignment operator = (§15.26.1).

For every access of a local variable or blank final field x, x must be definitely assigned before the access, or a compile-time error occurs.

It then goes on to say

Let C be a class, and let V be a blank final non-static member field of C, declared in C. Then:

  • V is definitely unassigned (and moreover is not definitely assigned) before the leftmost instance initializer (§8.6) or instance variable initializer of C.

  • V is [un]assigned before an instance initializer or instance variable initializer of C other than the leftmost iff V is [un]assigned after the preceding instance initializer or instance variable initializer of C.

Your code basically looks like this

private final int val;
// leftmost instance variable initializer, val still unassigned 
private final Callable<String> anonInnerGetValString = ...
// still unassigned after preceding variable initializer
private final Callable<String> lambdaGetValString = ...

The compiler therefore determines that val in unassigned when it's access within the initialization expression for lambdaGetValString.

The rules above apply to the use of a simple name, val, not to a qualified expression, this.val. You can use

final Callable<String> lambdaGetValString = () -> String.valueOf(this.val);



回答2:


This won't compile:

public class Example
{
  private final int x;
  private final int y = 2 * x;

  public Example() {
    x = 10;
  }
}

but this will:

public class Example
{
  private final int x;
  private final int y;

  public Example() {
    x = 10;
    y = 2 * x;
  }
}

and so will this:

public class Example
{
  private final int x = 10;
  private final int y = 2 * x;
}

So it's nothing to do with lambdas. A field that is initialized on the same line that it is declared on is evaluated before the constructor is executed. So at that point, the variable 'val' (or in this example 'x') has not been initialized.




回答3:


In my case, I had a Predicate which was trying to access a private final instance variable. I made the Predicate final as well which fixed it.

Before - Compiler error, this.availableCities might not have been initialized

class Service {
  private final List<String> availableCities;
  Service(List<String> availableCities) {
    this.availableCities = availableCities;
  }
  private Predicate<String> isCityAvailable = city -> this.availableCities.contains(city);
}

After - No more error

class Service {
  private final List<String> availableCities;
  private final Predicate<String> isCityAvailable;
  Service(List<String> availableCities) {
    this.availableCities = availableCities;
    this.isCityAvailable = city -> this.availableCities.contains(city);
  }
}

I think the "may not have been initialized" compiler error has some merit, since otherwise there's nothing stopping you from calling the Predicate from within the constructor before the final instance variable is initialized:

Potentially Why the Compiler Enforces This

class Service {
  private final List<String> availableCities;
  Service(List<String> availableCities, String topCity) {
    boolean isTopCityAvailable = isCityAvailable.test(topCity); // Error: this.availableCities is not initialized yet
    this.availableCities = availableCities;
  }
  private Predicate<String> isCityAvailable = city -> this.availableCities.contains(city);
}


来源:https://stackoverflow.com/questions/38020110/java-lambdas-have-different-variable-requirements-than-anonymous-inner-classes

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!