janvier
2014
Note : bien que l’exemple suivant porte sur JavaFX 8, le problème se pose exactement a l’identique pour AWT et Swing ou n’importe quoi d’autre qui gère ses listeners à la manière de l’AWT : une liste ou une map qui contient des références qu’on peut ajouter ou retirer.
Une chose très sympathique de JavaFX 8, est la possibilité de remplacer les classes anonymes par des expressions lambda comme c’est déjà possible de le faire en C# depuis de nombreuse années.
Ainsi le code suivant met un callback sur un bouton qui sera appelé quand on clique dessus :
@Override
public void handle(ActionEvent actionEvent) {
System.out.println("Click!");
}
});
Une fois passé en lambda, le code deviendra :
De la même manière on peut avoir un écouteur (listener) de la forme :
@Override
public void changed(ObservableValue observable, Boolean oldValue, Boolean newValue) {
System.out.printf("Disable %b -> %b", oldValue, newValue).println();
}
});
Et on peut le remplacer par l’expression lambda suivante :
En général on y pense pas, mais pour une bonne gestion de la mémoire, encore plus quand on a de nombreux écrans qui vont et viennent dans une même interface (ex: dans un assistant), il faut penser a désenregistrer ses listeners (parfois pour les enregistrer immédiatement dans le nouvel écran). Pour ce faire on va donc conserver une référence vers notre écouteur dans une variable membre de la classe :
monButton.disableProperty.addListener(monEcouteur);
[...]
monButton.disableProperty.removeListener(monEcouteur);
Encore mieux, en JDK 8 on peut utiliser des références de methodes. Ainsi il est tout a fait possible d’écrire :
Jusque là, tout va bien. Cependant là où le bas blesse, c’est que si on essaie de faire :
Si, par la suite, on continue à modifier la valeur de la propriété, on se rendra assez vite compte que l’écouteur n’a pas du tout été désenregistré ! On reçoit toujours les notifications de modification !
Le problème semble venir du fait que, à chaque fois qu’on écrit this::monEcouteur, en fait le compilateur va générer une nouvelle classe anonyme qui va appeler notre fonction.
Et donc quand nous écrivons :
[..]
monButton.disableProperty.removeListener(this::monEcouteur);
En fait, c’est l’équivalent de :
@Override
public void changed(ObservableValue observable, Boolean oldValue, Boolean newValue) {
monEcouteur(observable, oldValue, newValue);
}
});
[..]
monButton.disableProperty.removeListener(new ChangeListener() {
@Override
public void changed(ObservableValue observable, Boolean oldValue, Boolean newValue) {
monEcouteur(observable, oldValue, newValue);
}
});
Il y aura deux classes anonymes distinctes créées et, à priori, leur test equals() échoue ; donc la première n’est jamais retirée de la liste des écouteurs.
Il convient donc au final de faire la chose suivante qui est un poil plus lourde à écrire (et c’est bien dommage) :
System.out.printf("Disable %b -> %b", oldValue, newValue).println();
}
private final ChangeListener monEcouteur = this::impl_monEcouteur;
monButton.disableProperty.addListener(monEcouteur);
[...]
monButton.disableProperty.removeListener(monEcouteur);
Cela résout le problème : on a bien une seule classe anonyme que l’on référence, le test equals() est valide et donc l’écouteur est bien supprime de la propriété.
À noter que le problème ne se pose pas pour les callbacks puisqu’il est toujours possible de nullifier une valeur ou de lui en attribuer une différente :
System.out.println("Click!");
}
monButton.setOnAction(this::impl_onAction);
[...]
monButton.setOnAction(null);
Commentaires récents
- Back from the future… dans
- Back from the future… dans
- Static linking = does not Compute dans
- Paquetage x 2 dans
- Why you little… dans