2015-05-04 10 views
21

eine lokale Variable in forEach Ändern gibt einen Compiler-Fehler:lokale Variable Ändern von innen Lambda

Normalen

int ordinal = 0; 
    for (Example s : list) { 
     s.setOrdinal(ordinal); 
     ordinal++; 
    } 

Mit Lambda

int ordinal = 0; 
    list.forEach(s -> { 
     s.setOrdinal(ordinal); 
     ordinal++; 
    }); 

Jede Idee, wie zu lösen Dies?

+0

Was ist der Compiler-Fehler? Fügen Sie das bitte in Ihre Frage ein und prüfen Sie, ob Sie Ihren Beispielcode eingrenzen können, damit Sie eine vollständige Klassendefinition oder zumindest eine vollständige Methodendefinition einbeziehen können, die den Fehler reproduziert. –

+3

Wenn man bedenkt, dass lambdas im Wesentlichen syntaktischer Zucker für eine anonyme innere Klasse sind, ist meine Intuition, dass es unmöglich ist, eine nicht finale, lokale Variable zu erfassen. Ich würde mich aber gerne irren. – Sinkingpoint

+2

Eine in einem Lambda-Ausdruck verwendete Variable muss effektiv final sein. Sie können eine atomare Ganzzahl verwenden, obwohl sie übertrieben ist, daher wird hier kein Lambda-Ausdruck benötigt. Bleib einfach bei der For-Schleife. –

Antwort

38

einen Wrapper

Mit Java 8+ Verwenden

var wrapper = new Object(){ int ordinal = 0; }; 
list.forEach(s -> { 
    s.setOrdinal(wrapper.ordinal++); 
}); 
+0

java 10+? Gibt es das? – Eduardo

+0

@Eduardo [ja] (http://openjdk.java.net/jeps/286), in der Vorschau. –

5

Das ist ziemlich nahe an einem XY problem. Das heißt, die Frage, die gestellt wird, ist im Wesentlichen, wie eine erfasste lokale Variable von einem Lambda mutiert wird. Die eigentliche Aufgabe besteht jedoch darin, die Elemente einer Liste zu nummerieren.

Meiner Erfahrung nach gibt es in 80% der Fälle eine Frage, wie man ein eingefangenes Lokal innerhalb eines Lambda mutieren kann, es gibt einen besseren Weg, um fortzufahren. Normalerweise handelt es sich dabei Reduktion, aber in diesem Fall die Technik der einen Strom über die Liste Indizes laufen gilt auch:

IntStream.range(0, list.size()) 
     .forEach(i -> list.get(i).setOrdinal(i)); 
+0

Gute Lösung, aber nur wenn 'liste' eine' RandomAccess' Liste ist – ZhekaKozlov

5

Sie tun können, es mit einem regelmäßigen anonymen Klasse statt einer Lambda:

list.forEach(new Consumer<Example>() { 
    int ordinal = 0; 
    public void accept(Example s) { 
     s.setOrdinal(ordinal); 
     ordinal++; 
    } 
}); 
0

eine Alternative zu AtomicInteger (oder jedes andere Objekt der Lage, einen Wert zu speichern) ist ein Array zu verwenden:

final int ordinal[] = new int[ 1 ]; 
list.forEach (s -> s.setOrdinal (ordinal[ 0 ]++)); 

Aber sehen the Stuart's answer: es könnte eine bessere wa sein y, um mit Ihrem Fall umzugehen.

0

Sie können es zur Umgehung des Compilers umschließen, aber bitte beachten Sie, dass Nebenwirkungen in Lambdas nicht empfohlen werden.

die javadoc

Side-effects in behavioral parameters to stream operations are, in general, discouraged, as they can often lead to unwitting violations of the statelessness requirement A small number of stream operations, such as forEach() and peek(), can operate only via side-effects; these should be used with care

0

ich ein etwas anderes Problem hatte zu zitieren. Anstatt eine lokale Variable im forEach zu inkrementieren, musste ich der lokalen Variablen ein Objekt zuweisen.

Ich löste dies, indem ich eine private innere Domain-Klasse definiere, die sowohl die Liste, über die ich iterieren möchte (countryList), als auch die Ausgabe, die ich von dieser Liste bekomme (foundCountry), umschließt. Dann iteriere ich mit Java 8 "forEach" über das Listenfeld, und wenn das gewünschte Objekt gefunden wird, weise ich dieses Objekt dem Ausgabefeld zu.Damit wird einem Feld der lokalen Variablen ein Wert zugewiesen, ohne die lokale Variable selbst zu ändern. Ich glaube, da die lokale Variable selbst nicht geändert wird, beschwert sich der Compiler nicht. Ich kann dann den Wert verwenden, den ich im Ausgabefeld außerhalb der Liste erfasst habe.

Domain Objekt:

public class Country { 

    private int id; 
    private String countryName; 

    public Country(int id, String countryName){ 
     this.id = id; 
     this.countryName = countryName; 
    } 

    public int getId() { 
     return id; 
    } 

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

    public String getCountryName() { 
     return countryName; 
    } 

    public void setCountryName(String countryName) { 
     this.countryName = countryName; 
    } 
} 

Wrapper Objekt:

private class CountryFound{ 
    private final List<Country> countryList; 
    private Country foundCountry; 
    public CountryFound(List<Country> countryList, Country foundCountry){ 
     this.countryList = countryList; 
     this.foundCountry = foundCountry; 
    } 
    public List<Country> getCountryList() { 
     return countryList; 
    } 
    public void setCountryList(List<Country> countryList) { 
     this.countryList = countryList; 
    } 
    public Country getFoundCountry() { 
     return foundCountry; 
    } 
    public void setFoundCountry(Country foundCountry) { 
     this.foundCountry = foundCountry; 
    } 
} 

Iterate Betrieb:

int id = 5; 
CountryFound countryFound = new CountryFound(countryList, null); 
countryFound.getCountryList().forEach(c -> { 
    if(c.getId() == id){ 
     countryFound.setFoundCountry(c); 
    } 
}); 
System.out.println("Country found: " + countryFound.getFoundCountry().getCountryName()); 

Sie könnten die Wrapper-Klasse-Methode "setCountryList()" entfernen und das Feld machen "countryList" final, aber ich habe keine Kompilierungsfehler erhalten, die diese Details unverändert lassen.

1

Wenn Sie auf Java 10 können Sie var dafür verwenden:

var ordinal = new Object() { int value; }; 
list.forEach(s -> { 
    s.setOrdinal(ordinal.value); 
    ordinal.value++; 
});