9

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);
nervosol
  • 1,295
  • 3
  • 24
  • 46
  • 1
    A `Collection` has no order, if you want a specific element, you'll probably want to use an `ArrayList` instead. – DennisW Mar 10 '15 at 10:47
  • 3
    `lastname`is `final` and you cannot assign a value more than once. So you cannot use it in a loop on the left side. – nkr Mar 10 '15 at 10:48
  • 1
    See http://stackoverflow.com/questions/4732544/why-are-only-final-variables-accessible-in-anonymous-class – Alexis C. Mar 10 '15 at 10:52
  • ... and http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-4.html Chapter 7 - Variable capture – Alexis C. Mar 10 '15 at 10:54
  • 1
    @Alexis C.: Please, use http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html there are almost two years of development between these versions. – Holger Mar 10 '15 at 11:08
  • @Holger I got this link in a blog post of 2013 :/ But you're right it's not very up-to-date. – Alexis C. Mar 10 '15 at 11:13
  • 1
    Are you sure that “lambda forces you to use single element array”? I never experienced lambdas doing that to me… – Holger Mar 10 '15 at 17:57
  • 1
    Lambda no more "forces" you to use a single element array than unemployment "forces" you to rob a liquor store. – Brian Goetz Aug 04 '17 at 15:21

2 Answers2

14

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();
Community
  • 1
  • 1
tobias_k
  • 81,265
  • 12
  • 120
  • 179
  • This one is probably more efficient, as you don't need to process all the elements of the stream: `String lastName = collec.stream().skip(collec.size()-1).findFirst().get().getName()` – Alexis C. Mar 10 '15 at 11:00
6

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";
Community
  • 1
  • 1
Javier
  • 12,100
  • 5
  • 46
  • 57