par , 12/11/2015 à 17h51 (979 Affichages)
Bonjour,
Pendant certains de mes développements en Java j'ai bien souvent pesté contre l'api java.nio et ses classes Buffer qui ne permettent jamais complètement un chaînage des commandes (ce qui est proposé reste très intéressant néanmoins).
J'ai tenté pas mal de fois de contourner ce problème jusqu'à trouver une astuce avec les types génériques.
Je vais décrire mon raisonnement ci-dessous avec une classe "Buffer" et une méthode "position" comme ce que propose java.nio.Buffer
La première étape est de choisir un nom pour le type générique. On prendra ici "This".
1 2 3
| public class Buffer<This> {
public This position(int position) { return null; }
} |
Ensuite il faut définir ce This de façon à ce que le chaînage puisse se réaliser. On le fera alors étendre la classe Buffer elle même.
Afin de terminer la définition, le type This est réutilisé dans la classe parent de This.
On pourra alors terminer l'implémentation avec un retour de "this" au lieu de "null".
1 2 3
| public class Buffer<This extends Buffer<This>> {
public This position(int position) { return this; }
} |
Dès lors on peut très facilement chaîner nos appels de méthodes.
1 2
| Buffer buffer = new Buffer();
buffer.position(0).position(5); |
Le problème maintenant est sur la déclaration du composant générique à l'utilisation.
Ici, un IDE consciencieux vous dira que le type générique n'est pas défini et qu'il faut le préciser.
Le type Object ne réponds pas au critère car il n'hérite pas de Buffer.
Le type Buffer est correct (Buffer<Buffer>) mais on retrouve exactement le même problème sur la suite de la définition
En java le caractère "?" permet de définir un type générique non défini. Ici il convient parfaitement car bien que nous ne précisons pas de type précis, le compilateur sais au sein de la classe Buffer que "This" est au moins un type "Buffer<This>".
Le code client sera alors le suivant :
1 2
| Buffer<?> buffer = new Buffer<>();
buffer.position(0).position(5); |
Passons maintenant à l'héritage.
Imaginons un type ByteBuffer qui étends Buffer avec une nouvelle méthode "put".
L'astuce ici est de préciser plus finement le paramètre This pour maintenant étendre ByteBuffer et non Buffer.
1 2 3
| public class ByteBuffer<This extends ByteBuffer<This>> extends Buffer<This> {
public This put(byte b) { return this; }
} |
Le code client ne change pas tellement si on garde le même type de départ, à savoir Buffer :
1 2
| Buffer<?> buffer = new ByteBuffer<>();
buffer.position(0).position(5); |
On remarquera que comme nous avons déclaré la variable "buffer" comme étant un "Buffer", nous n'avons pas accès à la méthode "put" et nous gardons la vision d'un objet Buffer.
Lors de l'utilisation du type ByteBuffer, nous pouvons chaîner les opérations de la classe ByteBuffer avec les opérations de la classe Buffer comme suit :
1 2
| ByteBuffer<?> buffer = new ByteBuffer<>();
buffer.position(0).put((byte)5).position(5).put((byte)250); |
Imaginons maintenant un nouveau type de Buffer qui prends en charge n'importe quel type, paramétré, d'élément, ObjectBuffer.
Sa déclaration sera fortement semblable que pour ByteBuffer :
1 2 3
| public class ObjectBuffer<Element, This extends ObjectBuffer<Element, This>> extends Buffer<This> {
public This put(Element e) { return this; }
} |
Il est à noter ici que les autres types paramétrés sont répétés systématiquement. C'est ici que se trouve le coût de notre "This" dans la déclaration de notre classe.
Le code client devra à nouveau omettre le type "This" qu'il ne nous intéresse pas de définir :
1 2
| ObjectBuffer<String, ?> buffer = new ObjectBuffer<>();
buffer.position(0).put("zero").position(5).put("cinq"); |
Cette notation permet aussi d'effectuer du prototypage au lieu d'un objet mutable. On peut imaginer de même qu'au lieu de l'instruction "return this;", l'objet retourné soit une copie.
Beaucoup de réflexion pour ce sucre syntaxique. Mais ce n'est pas la première fois que je relevais ce problème sur l'API java.nio et je pense que d'autres l'ont aussi noté.
Mis à jour 13/11/2015 à 10h15 par Grimly
- Catégories
-
Java