La variable utilisée dans l'expression lambda doit être finale ou effectivement finale
Lorsque j'essaie d'utiliser calTz
, cette erreur est indiquée.
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;
}
Une variable final
signifie qu'elle ne peut être instanciée qu'une seule fois. dans Java, vous ne pouvez pas utiliser de variables non finales dans lambda ni dans les classes internes anonymes.
Vous pouvez refactoriser votre code avec l'ancienne boucle 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;
}
Même si je ne comprends pas certains éléments de ce code:
v.getTimeZoneId();
sans utiliser sa valeur de retourcalTz = TimeZone.getTimeZone(v.getTimeZoneId().getValue());
vous ne modifiez pas le calTz
initialement transmis et vous ne l'utilisez pas dans cette méthodenull
, pourquoi ne définissez-vous pas void
comme type de retour?J'espère que ces conseils vous aideront également à vous améliorer.
Bien que d'autres réponses prouvent l'exigence, elles n'expliquent pas pourquoi l'exigence existe.
Le JLS mentionne pourquoi dans §15.27.2 :
La restriction aux variables finales en fait interdit l'accès aux variables locales à changement dynamique, dont la capture risquerait de créer des problèmes de simultanéité.
Pour réduire le risque de bugs, ils ont décidé de s’assurer que les variables capturées ne sont jamais mutées.
À partir d'un lambda, vous ne pouvez pas obtenir de référence pour tout ce qui n'est pas final. Vous devez déclarer un wrapper final de l'extérieur de la lamda pour contenir votre variable.
J'ai ajouté le dernier objet "référence" comme enveloppe.
private TimeZone extractCalendarTimeZoneComponent(Calendar cal,TimeZone calTz) {
final AtomicReference<TimeZone> 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 a un nouveau concept appelé variable "Effectivement finale". Cela signifie qu'une variable locale non finale dont la valeur ne change jamais après l'initialisation s'appelle "Effectivement finale".
Ce concept a été introduit car avant Java 8 , nous ne pouvions pas utiliser une variable locale non finale dans un anonymous class
. Si vous voulez avoir accès à une variable locale dans anonymous class
, vous devez la rendre finale.
Lorsque lambda
a été introduit, cette restriction a été assouplie. D’où la nécessité de créer la variable locale final
si elle n’a pas changé une fois initialisée, car lambda n’est en soi qu’une classe anonyme.
Java 8 a pris la peine de déclarer la variable locale final
à chaque fois que le développeur utilisait lambda
et introduisait ce concept. créer des variables locales final
. Donc, si la règle pour anonymous class
n’a toujours pas changé, il n’est pas nécessaire d’écrire le mot-clé final
à chaque fois lorsque vous utilisez lambdas
.
J'ai trouvé une bonne explication ici
Dans votre exemple, vous pouvez remplacer le forEach
par lamdba par une simple boucle for
et modifier librement toute variable. Ou, probablement, refacturez votre code afin que vous n'ayez besoin de modifier aucune variable. Cependant, j'expliquerai par souci d'exhaustivité ce que l'erreur signifie et comment la contourner.
Spécification du langage Java 8, §15.27.2 :
Toute variable locale, paramètre formel ou paramètre d'exception utilisé mais non déclaré dans une expression lambda doit être soit déclaré final, soit être réellement final ( §4.12.4 ), ou une erreur de compilation se produit lorsque l'utilisation est tentée.
En principe, vous ne pouvez pas modifier une variable locale (calTz
dans ce cas) depuis un lambda (ou une classe locale/anonyme). Pour y parvenir en Java, vous devez utiliser un objet modifiable et le modifier (via une variable finale) à partir du lambda. Un exemple d'objet modifiable ici serait un tableau d'un élément:
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];
}
s'il n'est pas nécessaire de modifier la variable, une solution de contournement générale pour ce genre de problème serait de extraire la partie de code qui utilise lambda et utilise le mot-clé final sur le paramètre de méthode.