lokale variabelen verwezen vanuit een lambda expressie moet uiteindelijke of definitieve effectief

Ik heb een JavaFX 8 programma (voor JavaFXPorts kruis platfrom) vrij veel ingelijst om te doen wat ik wil, maar kwam een ​​stap te kort. Het programma leest een tekstbestand, telt de lijnen om een ​​willekeurige reeks vast te stellen, kiest een willekeurig nummer uit die reeks en leest die lijn in voor weergave.

The error is: local variables referenced from a lambda expression must be final or effectively final
        button.setOnAction(e -> l.setText(readln2));

Ik ben een beetje nieuw voor Java, maar is lijkt of ik gebruik Lambda of niet naar het volgende willekeurige regelig display in Label lhebben, mijn button.setOnAction(e -> l.setText(readln2));lijn verwacht een statische waarde.

Enig idee hoe ik kan tweaken wat ik moet gewoon maken de volgende waarde van de var readln2 elke keer als ik druk op de knop op het scherm weer te geven?

Bij voorbaat dank en hier is mijn code:

String readln2 = null;
in = new BufferedReader(new FileReader("/temp/mantra.txt"));
long linecnt = in.lines().count();
int linenum = rand1.nextInt((int) (linecnt - Low)) + Low;
try {
    //open a bufferedReader to file 
    in = new BufferedReader(new FileReader("/temp/mantra.txt"));
    while (linenum > 0) {
        //read the next line until the specific line is found
        readln2 = in.readLine();
        linenum--;
    }
    in.close();
} catch (IOException e) {
    System.out.println("There was a problem:" + e);
}
Button button = new Button("Click the Button");
button.setOnAction(e -> l.setText(readln2));
//  error: local variables referenced from a lambda expression must be final or effectively final

Antwoord 1, autoriteit 100%

Je kunt gewoon de waarde van readln2kopiëren naar een finalvariabele:

   final String labelText = readln2 ;
    Button button = new Button("Click the Button");
    button.setOnAction(e -> l.setText(labelText));

Als je elke keer een nieuwe willekeurige regel wilt pakken, kun je de betreffende regels in de cache plaatsen en een willekeurige selecteren in de gebeurtenishandler:

Button button = new Button("Click the button");
Label l = new Label();
try {
    List<String> lines = Files.lines(Paths.get("/temp/mantra.txt"))
        .skip(low)
        .limit(high - low)
        .collect(Collectors.toList());
    Random rng = new Random();
    button.setOnAction(evt -> l.setText(lines.get(rng.nextInt(lines.size()))));
} catch (IOException exc) {
    exc.printStackTrace();
}
// ...

Of je kunt het bestand gewoon opnieuw lezen in de gebeurtenishandler. De eerste techniek is (veel) sneller maar kan veel geheugen in beslag nemen; de tweede slaat geen bestandsinhoud op in het geheugen, maar leest een bestand elke keer dat de knop wordt ingedrukt, waardoor de gebruikersinterface niet meer reageert.

De fout die je krijgt, vertelt je in feite wat er mis was: de enige lokale variabelen waartoe je toegang hebt vanuit een lambda-expressie zijn ofwel final(aangeduid als final, wat betekent dat ze moet exact één keer een waarde worden toegewezen) of “effectief definitief” (wat in feite betekent dat u ze definitief kunt maken zonder andere wijzigingen in de code).

Uw code kan niet compileren omdat readln2een waarde meerdere keren (in een lus) heeft toegewezen, dus het kan niet worden aangegeven final. Dus je kunt het niet openen in een lambda-expressie. In de bovenstaande code zijn de enige variabelen die in de Lambda zijn geopend l, lines, en rng, die allemaal “effectief definitief” zijn als zij Kunnen een waarde precies één keer worden toegewezen. (U kunt ze definitief verklaren en de code zou nog steeds compileren.)


Antwoord 2, Autoriteit 18%

De fout die u hebt aangetroffen, betekent dat elke variabele die u binnen een Lambda-uitdrukkingen in een Lambda-uitdrukkingen definitief of effectief definitief moet zijn. Zie hier voor het verschil dit antwoord: verschil tussen definitief en effectief definitief

Het probleem in uw code is de volgende variabele

String readln2 = null;

De variabele wordt later aangegeven en toegewezen, de compiler kan niet detecteren of het eenmaal of meerdere keren wordt toegewezen, dus het is niet effectief definitief.

De eenvoudigste manier om dit op te lossen is om een ​​wikkelobject te gebruiken, in dit geval een snaarteproperty in plaats van een tekenreeks. Deze wrapper wordt slechts één keer toegewezen en is dus effectief definitief:

StringProperty readln2 = new SimpleStringProperty();
readln2.set(in.readLine());
button.setOnAction(e -> l.setText(readln2.get()));

Ik heb de code ingekort om alleen de relevante onderdelen te tonen.


Antwoord 3, Autoriteit 5%

Ik passeer het buitenste object regelmatig in interface-implementatie op deze manier:
1. Maak een objecthouder,
2. Stel deze objecthouder in met een gewenste staat,
3. Wijzig de binnenvariabelen in de objecthouder,
4. Haal die variabelen op en gebruik ze.

Hier is een voorbeeld van Vaadin:

Object holder : 
    public class ObjectHolder<T> {
    private T obj;
    public ObjectHolder(T obj) {
        this.obj = obj;
    }
    public T get() {
        return obj;
    }
    public void set(T obj) {
        this.obj = obj;
    }
}

Ik wil knopbijschriften doorgeven die extern als volgt zijn gedefinieerd:

String[] bCaption = new String[]{"Start", "Stop", "Restart", "Status"};
String[] commOpt = bCaption;

Vervolgens heb ik een for-lus en wil ik dynamisch knoppen maken en waarden als deze doorgeven:

for (Integer i = 0; i < bCaption.length; i++) {
    ObjectHolder<Integer> indeks = new ObjectHolder<>(i);
    b[i] = new Button(bCaption[i], 
        (Button.ClickEvent e) -> {
            remoteCommand.execute(
                cred, 
                adresaServera, 
                 comm + " " + commOpt[indeks.get()].toLowerCase()
             );
         }
        );
        b[i].setWidth(70, Unit.PIXELS);
        commandHL.addComponent(b[i]);
        commandHL.setComponentAlignment(b[i], Alignment.MIDDLE_CENTER);
  }

Hopelijk helpt dit..

Other episodes