2014-12-21 17 views
13

Mam program JavaFX 8 (dla JavaFXPorts cross platfrom), który został w znacznej części sformułowany w ramce, aby zrobić to, co chcę, ale powstał jeden krok. Program odczytuje plik tekstowy, zlicza linie, aby ustalić losowy zakres, wybiera losową liczbę z tego zakresu i odczytuje tę linię w celu wyświetlenia.Zmienne lokalne, do których odwołuje się wyrażenie lambda, muszą być ostateczne lub efektywne.

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

jestem trochę nowy w Javie, ale wydaje się, czy mogę użyć lambda albo nie mieć kolejnej linii losowe wyświetlanie w Label l, mój button.setOnAction(e -> l.setText(readln2)); linia spodziewa wartości statycznej.

Jakieś pomysły, w jaki sposób mogę poprawić to, co mam, aby po prostu nacisnąć następną wartość wyświetlacza var readln2 za każdym razem, gdy naciskam przycisk na ekranie?

góry dzięki i tu jest mój kod:

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 
+0

Najprostszym sposobem rozwiązania tego problemu jest użycie SimpleStringProperty zamiast String do przechowywania 'readln2'. – eckig

+0

Dzięki. Czy mógłbyś trochę rozwinąć? Patrząc na to, nie jestem pewien jak sprawić, by był zgodny ze sposobem, w jaki czytam/korzystam z zewnętrznego pliku. – Jeff

+0

Moim zdaniem, to pytanie powinno zostać ogolone do 4 linii kodu. Ok, 15 linii - w końcu to java! ;-) Być może podczas tego ćwiczenia redukcyjnego odpowiedź stałaby się jasna dla samego plakatu. –

Odpowiedz

7

można po prostu skopiować wartość readln2 do zmiennej final:

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

Jeśli chcesz, aby pobrać nową linię losowo za każdym razem, można buforować kierunki zainteresowania i wybrać losowo jeden w obsługi zdarzeń:

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(); 
} 
// ... 

Albo możesz po prostu ponownie przeczytać plik w module obsługi zdarzeń. Pierwsza technika jest (dużo) szybsza, ale może pochłaniać dużo pamięci; drugi nie zapisuje w pamięci żadnej zawartości pliku, ale czyta plik po każdym naciśnięciu przycisku, co może spowodować brak reakcji interfejsu użytkownika.

Błąd, który zasadniczo wyjaśnił, co było nie tak: jedynymi zmiennymi lokalnymi, do których można uzyskać dostęp z wnętrza wyrażenia lambda są: final (zadeklarowany jako final, co oznacza, że ​​muszą zostać przypisane wartości dokładnie raz) lub "skutecznie ostateczny" (co w zasadzie oznacza, że ​​możesz je uczynić ostatecznymi bez żadnych innych zmian w kodzie).

Twój kod nie kompiluje się, ponieważ readln2 ma przypisaną wartość wiele razy (wewnątrz pętli), więc nie można jej oznaczyć jako final. W związku z tym nie można uzyskać do niego dostępu w wyrażeniu lambda. W powyższym kodzie jedynymi zmiennymi dostępnymi w lambda są l, lines i rng, które wszystkie są "skutecznie końcowe", ponieważ są przypisane wartości dokładnie jeden raz.(Możesz zadeklarować je jako ostateczne, a kod nadal się kompiluje.)

+0

James_D. To też zadziałało dla mnie z zastrzeżeniem, o którym wspomniałem powyżej, że będę również wymyślał sposób, aby zdarzenie click button zapewniało również nową losową linię za każdym razem, gdy kliknę. Mogę po prostu mieć przycisk ponownie uruchomić lub przenieść niektóre z nich, ale. Będąc nowicjuszem w Javie, nawet to zajmie trochę czasu. Czy głosować na twoją odpowiedź, kiedy mogę. – Jeff

+0

James, dziękuję. Dodany nowy blok kodu losowego wygląda dokładnie tak, jak potrzebowałem. Wyjaśnienie też pomogło. Losowa linia za każdym razem wydaje się działać dobrze z moimi plikami testowymi w systemie Windows. Będę majsterkować z nim na Androidzie (używając /storage/emulated/0/temp/mantra.txt jako ścieżki) przez JavaFXPorts, jak wisiał podczas mojej pierwszej próby. W każdym razie naprawdę to doceniam. – Jeff

+1

Jestem ciekawy, * dlaczego * Java wymusza to. Wygląda to na przerwę od normalnego sposobu, w jaki Java obsługuje zamknięcia. –

0

Błąd napotkali Państwo oznacza, że ​​każda zmienna, że ​​masz dostęp do wnętrza ciała wyrażeń lambda musi być ostateczna lub skutecznie finału. Różnicy, zobacz tę odpowiedź tutaj: Difference between final and effectively final

Problem w kodzie jest następująca zmienna

String readln2 = null; 

Zmienna zostanie uznany i przypisany później, kompilator nie może wykryć, jeśli zostanie przydzielony jeden lub wiele razy, więc nie jest to skutecznie ostateczne.

Najprostszym sposobem rozwiązania tego problemu jest użycie obiektu opakowania, w tym przypadku StringProperty zamiast String. Owijka ta zostanie przydzielony tylko raz, a więc jest skutecznie końcowy:

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

ja skrócony kod, aby pokazać tylko odpowiednie części ..

+0

Wielkie dzięki eckig. Podrabiałem składnię zestawu/get. Teraz muszę po prostu dowiedzieć się, jak za każdym razem, gdy kliknę, uzyskać przycisk do przełączania się za pomocą nowej losowej linii. Oddzielny problem jednak. Próbowałem zagłosować, ale jestem tak nowy w Javie, że nie mam jeszcze tej reputacji. Wrócę i będę głosował, kiedy tylko będę mógł. Dzięki jeszcze raz. Bardzo to doceniam. – Jeff

1

Regularnie przekazuję obiekt zewnętrzny do implementacji interfejsu w następujący sposób: 1. Utwórz uchwyt obiektu, 2. Ustaw ten uchwyt obiektu w żądanym stanie, 3. Zmień wewnętrzne zmienne w obiekcie właściciela, 4. Pobierz te zmienne i użyj ich.

Oto jeden przykład z 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; 
    } 
} 

Chcę przekazać podpisy przycisk zewnętrznie zdefiniowane tak:

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

Następny mam dla pętli, a chcesz utworzyć przyciski dynamicznie i przekazuj wartości w następujący sposób:

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); 
    } 

Mam nadzieję, że to pomoże ..

Powiązane problemy