Variable is already defined in method lambda
Asked Answered
T

5

19

Consider the following almost compilable Java 8 code:

public static void main(String[] args) {

    LinkedList<User> users = null;
    users.add(new User(1, "User1"));
    users.add(new User(2, "User2"));
    users.add(new User(3, "User3"));

    User user = users.stream().filter((user) -> user.getId() == 1).findAny().get();
}

static class User {

    int id;
    String username;

    public User() {
    }

    public User(int id, String username) {
        this.id = id;
        this.username = username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public int getId() {
        return id;
    }
}

You'll notice User user = users.stream().filter((user) -> user.getId() == 1).findAny().get(); throws a compiler error:

variable user is already defined in method main(String[])

My question is: Why do Lambda expressions consider the variable that is being initialized on the same line as the Lambda expression as already defined? I understand Lambdas look outside themselves for (and use) local variables, so you can't name the variables you use inside the Lambda the same as an outside variable. But why is the variable that is being defined considered already defined?

Tenner answered 31/3, 2014 at 21:56 Comment(7)
Declaration happens before initialization. Your local variable user is defined before user is in the lambda expression.Parameter
@Parameter I think the point is that the lambda expression user argument is actually expected to be used within a totally different context (the evaluation context of the expression), and so why should it be affected by the user variable defined outside?Kissner
The only argument I can think of as of now is that the variable user is part of the closure environment, it is already within the context of the lambda expression, and so, if you declare another variable user, there is a name conflict. However, I still wonder, why was it not simply shadowed. Can this be related to how things are instrumented under the hood?Kissner
@EdwinDalorzo His question is why is the variable that is being defined considered already defined?, and my comment was attempting to explain that.Parameter
You are mixing up declaration and initialization. The variable is declared before it is initialized. And for the lambda within the intializer the variable user is declared though not yet initialized. This is the same for local variables without lambdas, e.g. for int x=x+1; the second x will refer to the variable x which is already declared but the reference is invalid as x is not initialized at that point. There will be no attempt to find an initialized x in an outer scope.Qualls
@Tenner Well, what happened to the Sunrise? I was about to post that the program runs into a NaN.Dominance
@laune, I deleted the question because it was trivial. You can email me your solution at ryan at vantagecp dot comTenner
G
17

Let's go to the Java Language Specification on names and their scopes

The scope of a formal parameter of a method (§8.4.1), constructor (§8.8.1), or lambda expression (§15.27) is the entire body of the method, constructor, or lambda expression.

The scope of a local variable declaration in a block (§14.4) is the rest of the block in which the declaration appears, starting with its own initializer and including any further declarators to the right in the local variable declaration statement.

Then, on the subject of shadowing and obscuring

A local variable (§14.4), formal parameter (§8.4.1, §15.27.1), exception parameter (§14.20), and local class (§14.3) can only be referred to using a simple name, not a qualified name (§6.2).

Some declarations are not permitted within the scope of a local variable, formal parameter, exception parameter, or local class declaration because it would be impossible to distinguish between the declared entities using only simple names.

It is a compile-time error if the name of a local variable v is used to declare a new variable within the scope of v, unless the new variable is declared within a class whose declaration is within the scope of v.

So, in

User user = users.stream().filter((user) -> user.getId() == 1).findAny().get();

, the scope of the variable user is everything after it in that block. Now you are trying to use the name of that variable to declare a new variable within the scope, but not

within a class whose declaration is within the scope of v.

so a compile time error occurs. (It's declared in a lambda expression, not in a class.)

Gatto answered 31/3, 2014 at 23:40 Comment(0)
B
8

look at the code

User user = users.stream().filter((user) -> user.getId() == 1).findAny().get();

The variable name is user and the variable inside the lambda is also user

try changing it to be something like this

User user = users.stream().filter((otherUser) -> otherUser.getId() == 1).findAny().get();
Bueschel answered 31/3, 2014 at 22:0 Comment(2)
Uhm, I think the OP is aware of thatGallimaufry
Yeah, I think the question is why isn't the lambda user variable shadowing the user variable in the closure context? Otherwise this question is boring.Kissner
F
5

Note, this limitation is going to be removed in the future releases. Quote from JEP-302:

Lambda parameters are not allowed to shadow variables in the enclosing scopes. (In other words, a lambda behaves like a for statement - see JLS) This often causes problems, as in the following (very common) case:

Map<String, Integer> msi = ...
...
String key = computeSomeKey();
msi.computeIfAbsent(key, key -> key.length()) //error

Here, the attempt to reuse the name key as a lambda parameter in the computeIfAbsent call fails, as a variable with the same name was already defined in the enclosing context.

It would be desirable to lift this restriction, and allow lambda parameters (and locals declared with a lambda) to shadow variables defined in enclosing scopes. (One possible argument against is readability: if lambda parameters are allowed to shadow, then in the above example, the identifier 'key' means two different things in the two places where it is used, and there seem to be no syntactic barrier to separate the two usages.)

Funderburk answered 9/11, 2017 at 15:12 Comment(1)
That's cool. Slightly less related to the topic at hand, though, in that the identifier in my example doesn't mean two different things because the outer variable is being initialized by the lambda in the same line as the declaration. In other words, the outer variable can only be used once the lambda is finished, thus creating said missing syntactic barrier.Tenner
B
3

It is the same as with any other local variables: you're not allowed to shadow them in more inner {} blocks.

Bellyache answered 31/3, 2014 at 23:35 Comment(0)
B
-1

The question is pretty old, but i thought my answer can add better clarity to the already given answers. Particularly to that of @Sotirios Delimanolis . The lambda assignment in

    User user = users.stream().filter((user) -> user.getId() == 1).findAny().get();

fails for the same reason the following code fails.

    Object e = null;
    try{
      throw new Exception();
    } catch(Exception e) { // compilation fails because of duplicate declaration
      //do nothing
    }

A local variable (§14.4), formal parameter (§8.4.1, §15.27.1), exception parameter (§14.20), and local class (§14.3) can only be referred to using a simple name, not a qualified name (§6.2).

Some declarations are not permitted within the scope of a local variable, formal parameter, exception parameter, or local class declaration because it would be impossible to distinguish between the declared entities using only simple names.

Because lambdas have the same scope of all the things mentioned above, this fails.

Brachium answered 25/9, 2017 at 6:47 Comment(6)
"impossible to distinguish" I disagree. The User user that is being initialized is certainly unusable (e.g., BigDecimal x = x.plus(y) is obviously erroneous). So it doesn't seem like a huge stretch to say that the user variable within the lambda is obviously not referring to the variable that is currently being initialized. And thus the compiler should be able to grant you the ability to use the same name.Tenner
Logically speaking, you are right. But java semantics just doesn't use that kind of an inference in lambdas or any of the declarations. The java compiler couldn't distinguish between a forward reference and a lambda parameter. By the way Impossible to distinguish is from the JLS, if that is what you downvoted the answer for.Brachium
Dude, I know. The question stated clearly that I understand that you can’t, but why they specifically decided to make the scope of variable names so specific as to exclude obvious situations where there is no conflict is still a mystery. Again, clearly, they specifically decided to make this specification but there seems to be no obvious reason why. Your answer adds nothing new. The only kind of new answer left to provide is a situation where the compiler, by limiting you, is preventing an error.Tenner
Can you think of a situation where, if you were allowed to name an inner lambda variable the same thing as the variable being initialized, it would lead to a runtime error?Tenner
Because a lambda is just another block, it doesn't have a context of its own. Section 15.27.2 Lambda Body specifies that 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).Brachium
And for Why a lambda is not as same as an anonymous class, the JVM treats a lambda quite differently from a normal object, it lets you handle them as if they were objects, But the JVM is not required to implement them as such.Brachium

© 2022 - 2024 — McMap. All rights reserved.