La variabile utilizzata nell’espressione lambda dovrebbe essere definitiva o effettivamente definitiva

La variabile utilizzata nell’espressione lambda dovrebbe essere definitiva o effettivamente definitiva

Quando provo a usare calTz mostra questo errore.

 private TimeZone extractCalendarTimeZoneComponent(Calendar cal,TimeZone calTz) { try { cal.getComponents().getComponents("VTIMEZONE").forEach(component->{ VTimeZone v = (VTimeZone) component; v.getTimeZoneId(); if(calTz==null) { calTz = TimeZone.getTimeZone(v.getTimeZoneId().getValue()); } }); } catch (Exception e) { log.warn("Unable to determine ical timezone", e); } return null; } 

Una variabile final significa che può essere istanziata solo una volta. in Java non è ansible utilizzare variabili non finali in lambda e nelle classi interne anonime.

Puoi refactoring il tuo codice con il vecchio ciclo for-each:

 private TimeZone extractCalendarTimeZoneComponent(Calendar cal,TimeZone calTz) { try { for(Component component : cal.getComponents().getComponents("VTIMEZONE")) { VTimeZone v = (VTimeZone) component; v.getTimeZoneId(); if(calTz==null) { calTz = TimeZone.getTimeZone(v.getTimeZoneId().getValue()); } } } catch (Exception e) { log.warn("Unable to determine ical timezone", e); } return null; } 

Anche se non capisco il senso di alcuni pezzi di questo codice:

  • chiami un v.getTimeZoneId(); senza usare il suo valore di ritorno
  • con il compito calTz = TimeZone.getTimeZone(v.getTimeZoneId().getValue()); non si modifica il calTz passato in calTz e non lo si utilizza in questo metodo
  • Restituisci sempre null , perché non imposti void come tipo di reso?

Spero che anche questi suggerimenti ti aiutino a migliorare.

Da un lambda, non puoi ottenere un riferimento a tutto ciò che non è definitivo. Devi dichiarare un wrapper finale al di fuori della lama per mantenere la tua variabile.

Ho aggiunto l’ultimo object “di riferimento” come questo wrapper.

 private TimeZone extractCalendarTimeZoneComponent(Calendar cal,TimeZone calTz) { final AtomicReference reference = new AtomicReference<>(); try { cal.getComponents().getComponents("VTIMEZONE").forEach(component->{ VTimeZone v = (VTimeZone) component; v.getTimeZoneId(); if(reference.get()==null) { reference.set(TimeZone.getTimeZone(v.getTimeZoneId().getValue())); } }); } catch (Exception e) { //log.warn("Unable to determine ical timezone", e); } return reference.get(); } 

Java 8 ha un nuovo concetto chiamato variabile “Efficacemente finale”. Significa che una variabile locale non finale il cui valore non cambia mai dopo l’inizializzazione è chiamata “Effettivamente finale”.

Questo concetto è stato introdotto perché prima di Java 8 , non potevamo usare una variabile locale non finale in una anonymous class . Se vuoi avere accesso a una variabile locale nella anonymous class , devi renderla definitiva.

Quando fu introdotta la lambda , questa restrizione fu attenuata. Di qui la necessità di rendere la variabile locale final se non è cambiata una volta che è inizializzata come lambda in sé non è altro che una class anonima.

Java 8 ha realizzato il dolore di dichiarare la variabile locale final ogni volta che lo sviluppatore ha utilizzato lambda e ha introdotto questo concetto e ha reso superfluo rendere final variabili locali. Quindi, se vedi che la regola per la anonymous class non è ancora cambiata, è solo che non devi scrivere la parola chiave final ogni volta che usi lambdas .

Ho trovato una buona spiegazione qui

Sebbene altre risposte dimostrino il requisito, non spiegano perché esiste il requisito.

Il JLS menziona perché in §15.27.2 :

La restrizione alle variabili finali effettivamente proibisce l’accesso a variabili locali che cambiano dynamicmente, la cui cattura potrebbe introdurre problemi di concorrenza.

Per ridurre il rischio di bug, hanno deciso di garantire che le variabili catturate non venissero mai mutate.

Nel tuo esempio, puoi sostituire forEach con lamdba con un ciclo for semplice e modificare liberamente qualsiasi variabile. O, probabilmente, refactoring il codice in modo che non è necessario modificare le variabili. Tuttavia, spiegherò per completezza cosa significa l’errore e come aggirare il problema.

Java 8 Language Specification, §15.27.2 :

Qualsiasi variabile locale, parametro formale o parametro di eccezione utilizzato ma non dichiarato in un’espressione lambda deve essere dichiarato finale o essere effettivamente definitivo ( §4.12.4 ), oppure si verifica un errore in fase di compilazione in cui viene tentato l’utilizzo.

Fondamentalmente non è ansible modificare una variabile locale ( calTz in questo caso) da una lambda (o una class locale / anonima). Per ottenere ciò in Java, devi usare un object mutabile e modificarlo (tramite una variabile finale) dal lambda. Un esempio di object mutabile qui sarebbe un array di un elemento:

 private TimeZone extractCalendarTimeZoneComponent(Calendar cal, TimeZone calTz) { TimeZone[] result = { null }; try { cal.getComponents().getComponents("VTIMEZONE").forEach(component -> { ... result[0] = ...; ... } } catch (Exception e) { log.warn("Unable to determine ical timezone", e); } return result[0]; } 

se non è necessario modificare la variabile rispetto a una soluzione generale per questo tipo di problema, è necessario estrarre la parte di codice che utilizza lambda e utilizzare la parola chiave final sul parametro metodo.