Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why lambda forces me to use single element array instead of final object?

I have following class:

public class Item{
   private String name;
   //setter getter
}

And Collection of items. I would like to get name of last Item in Collection. To do that I simply iterate over all collection and use last. The problem is I dont know why it forces me to use one element String array.

Why do I have to use:

String[] lastName = {""};
items.forEach(item -> lastName[0] = item.getName());
System.out.println(lastname[0]);

instead of:

final String lastName;
items.forEach(item -> lastName = item.getName());
System.out.println(lastname);
like image 213
nervosol Avatar asked Mar 10 '15 10:03

nervosol


People also ask

How do you use the final variable in lambda?

The local variables that a lambda expression may use are referred to as “effectively final”. An effectively final variable is one whose value doesn't change after it's first assigned. There is no need to explicitly declare such a variable as final, although doing so would not be an error.

Why the variables used in Lambda body should be final or effectively final?

Forcing the variable to be final avoids giving the impression that incrementing start inside the lambda could actually modify the start method parameter.

How do you resolve a variable used in Lambda should be final or effectively final?

The effectively final variables refer to local variables that are not declared final explicitly and can't be changed once initialized. A lambda expression can use a local variable in outer scopes only if they are effectively final.

How do you declare a variable in lambda expression in Java?

A lambda expression can't define any new scope as an anonymous inner class does, so we can't declare a local variable with the same which is already declared in the enclosing scope of a lambda expression. Inside lambda expression, we can't assign any value to some local variable declared outside the lambda expression.


2 Answers

final actually means that you must assign it once (and only once, as guaranteed by compile time analysis). For instance, the following code is invalid:

final String lastName;
List<Item> items = new ArrayList<Item>();
items.add(new Item("only one element"));
for (Item item:items) lastName = item.getName();

In your second lambda expression, the consumer assigns to lastName, which has been declared as final:

final String lastName;
items.forEach(item -> lastName = item.getName());

Since variables referenced in a lambda expression must be effectively final (i.e. such that the final keyword may be added to its definition), deleting the final keyword in the declaration of lastName doesn't help: here lastName is effectively final, and the consumer assigns to an effectively final variable.

String lastName; 
items.forEach(item -> lastName = item.getName());

On the other hand, in your first expression the effectively final variable is lastName, which is an array. Then you can do:

  String[] lastName = {""};
  items.forEach(item -> lastName[0] = item.getName());

Because here you are not assigning to the effectively final variable, but only modifying its elements (remember that what is constant is the reference, not its values), as it is also the case in the following example:

  final String[] lastName = new String[1];
  lastName[0]="foo";
like image 160
Javier Avatar answered Nov 12 '22 20:11

Javier


You can not make lastName a String, because a local variable that's used in a lambda (or anonymous inner class) must be (effectively) final (see here), i.e. you can not overwrite it in each execution of the lambda in the .forEach loop. When using an array (or some other wrapper object), you do not assign a new value to that variable, but only change some aspect of it, thus it can be final.

Alternatively, you could use reduce to skip to the last item:

String lastName = items.stream().reduce((a, b) -> b).get().getName();

Or, as noted in comments, skip the first n-1 elements and take the first after that:

String last = items.stream().skip(items.size() - 1).findFirst().get().getName();
like image 34
tobias_k Avatar answered Nov 12 '22 21:11

tobias_k