2015-03-10 9 views
7

Mam następujące klasy:Dlaczego lambda zmusza mnie do użycia tablicy pojedynczych elementów zamiast końcowego obiektu?

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

i zbieranie przedmiotów. Chciałbym uzyskać nazwę ostatniego przedmiotu w kolekcji. Aby to zrobić, po prostu powtarzam po całej kolekcji i używam jako ostatniej. Problem polega na tym, że nie wiem, dlaczego zmusza mnie do korzystania z jednego elementu tablicy łańcuchów.

Dlaczego muszę używać:

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

zamiast:

final String lastName; 
items.forEach(item -> lastName = item.getName()); 
System.out.println(lastname); 
+1

"Kolekcja" nie ma porządku, jeśli chcesz konkretny element, prawdopodobnie będziesz chciał użyć 'ArrayList'. – DennisW

+3

'lastname'jest' końcowe' i ​​nie można przypisać wartości więcej niż jeden raz. Więc nie możesz go użyć w pętli po lewej stronie. – nkr

+1

Zobacz http://stackoverflow.com/questions/4732544/why-are-online-valge-variables-accessible-in-anonymous-class –

Odpowiedz

13

Nie można zrobić lastNameString, ponieważ zmienna lokalna, która jest używana w lambda (lub anonimowy klasa wewnętrzna) musi być (skutecznie) final (see here), tzn. nie można go zastąpić w każdym wykonaniu lambda w pętli .forEach. Podczas korzystania z tablicy (lub innego obiektu typu wrapper) nie przypisujesz tej wartości nowej wartości, a jedynie zmieniasz jej część, dzięki czemu może ona być ostateczna.

Alternatywnie, można użyć reduce aby przejść do ostatniej pozycji:

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

Albo, jak zauważył w komentarzach, skip pierwszych n-1 elementów i wziąć pierwszy po tym:

String last = items.stream().skip(items.size() - 1).findFirst().get().getName(); 
+0

Ten jest prawdopodobnie bardziej wydajny, ponieważ nie trzeba przetwarzać wszystkich elementów strumienia: 'String lastName = collec.stream(). Skip (collec.size() - 1) .findFirst(). Get () .getName() ' –

4

final w rzeczywistości oznacza, że ​​należy go przypisać tylko raz (i tylko raz, co gwarantuje analiza czasu kompilacji). Na przykład, następujący kod jest nieprawidłowy:

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

W swoim drugim wyrażenia lambda, konsument przypisuje lastName, które zostały zadeklarowane jako final:

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

Ponieważ zmiennych przywoływanych w wyrażeniu lambda musi być effectively final (tj. takie, że słowo kluczowe final może zostać dodane do jego definicji), usunięcie słowa kluczowego final w deklaracji lastName nie pomoże: tutaj lastName jest skutecznie ostateczny, a konsument przypisuje do efektywna zmienna końcowa.

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

Z drugiej strony, w swojej pierwszej wypowiedzi zmienna skutecznie ostateczny jest lastName, który jest tablicą. Następnie można zrobić:

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

Bo tu nie przypisując skutecznie końcowego zmiennej , ale tylko modyfikując jego elementy (należy pamiętać, że to co jest stałe jest odniesienie, a nie jego wartości), ponieważ jest to również Przypadek w następującym przykładzie:

final String[] lastName = new String[1]; 
    lastName[0]="foo"; 
Powiązane problemy