4 votes

Les paramètres de type paramétrés?

Je suis en train de créer une bibliothèque avec un conteneur qui libère des instances de ses objets contenus selon les descripteurs qui lui sont passés. J'aimerais que le descripteur détermine le type de l'objet retourné, mais le descripteur peut spécifier un type borné. Comment puis-je implémenter cela? Par exemple, le plus proche que je puisse obtenir est :

/*Bloc 1 - Première tentative. Compile, mais force l'utilisateur à effectuer une conversion forcée */
interface ItemDescriptor {
    Class getType();
}

interface ArchiveContainer> {
    Iterable getDescriptors();
    I getItem(D descriptor);
}

//Implémentations
class ChannelItemDescriptor implements ItemDescriptor
{
    final Class type;

    ChannelItemDescriptor(Class type) {
        this.type = type;
    }

    @Override Class getType() {return type;}
}

class ChannelArchive implements ArchiveContainer> {
    @Override ByteChannel getItem(ChannelItemDescriptor descriptor) {...}
}

Le code ci-dessus compile, mais le problème est que la méthode getItem de ChannelArchive peut également retourner des SeekableByteChannel. L'utilisateur de cette bibliothèque le sait au moment de la compilation (car il connaît le paramètre de type du descripteur), donc j'essaie d'éviter d'ajouter un paramètre de méthode de type Class pour forcer l'utilisateur à convertir explicitement la valeur retournée en SeekableByteChannel lorsque nécessaire. Je n'arrive pas à comprendre comment faire en sorte que getItem renvoie un sous-type spécifique de ByteChannel

/*Bloc 2 - Code de test*/
ChannelArchive archive = ...;
ChannelItemDescriptor desc = ...;
ChannelItemDescriptor otherDesc = ...;
SeekableByteChannel sbc = archive.getItem(desc);
SeekableByteChannel sbc = archive.getItem(otherDesc); //Devrait échouer à la compilation, ou compiler avec un avertissement
ByteChannel bc = archive.getItem(otherDesc);

Je pourrais ajouter un paramètre Class à chaque méthode, mais le code de la méthode ignorerait complètement le paramètre de méthode Class! Son seul but serait d'aider le compilateur à inférer les types. Je pense que cela rendrait le code tellement obscur que ce serait plus facile pour l'utilisateur d'utiliser les vérifications instanceof et les conversions.

J'ai essayé ceci :

/*Bloc 3 - Tentative échouée.*/
class ChannelArchive implements ArchiveContainer> {
    // Ne compilera pas, getItem ne remplace pas
    @Override  II getItem(ChannelItemDescriptor descriptor) {...}
}

Mais cela ne fonctionne pas : ChannelArchive n'est pas abstrait et ne remplace pas la méthode abstraite getItem(ChannelItemDescriptor) dans ArchiveContainer. Je suppose que c'est parce que le second paramètre de type a une éradication de type différente de ?

J'ai aussi essayé ceci, qui compile :

/*Bloc 4 - Presque assez spécifique*/
interface ArchiveContainer> {
    Iterable getDescriptors();
    > II getItem(DD descriptor);
}

class ChannelArchive implements ArchiveContainer> {
    @Override > II getItem(DD descriptor) {...}
}

Même s'il compile, cela ne fonctionnerait pas vraiment parce que j'ai besoin d'un ChannelItemDescriptor à l'intérieur de cette méthode, et la conversion résultante contredirait l'objectif de l'utilisation de la sûreté accrue des types génériques.

Je ne vois pas pourquoi je ne peux pas le faire, car les bons types sont connus au moment de la compilation. Ce dont j'ai vraiment besoin dans cette interface ArchiveContainer est un paramètre de type paramétré, comme : >. Qu'est-ce que je fais de mal?

REMARQUE : Je n'utilise pas réellement ByteChannel et SeekableByteChannel, mais ce que j'utilise est assez similaire.


À ruakh, j'ai opté pour le code du bloc 4. Dans mon cas, il est extrêmement peu probable que l'utilisateur envoie une mauvaise sous-classe d'ItemDescriptor dans un appel à un getItem, surtout parce que les descripteurs sont tous renvoyés par le conteneur lui-même via getDescriptors!


1voto

ruakh Points 68789

Je pense que ce code, qui est (presque?) le même que votre troisième tentative, est aussi bon que vous pouvez obtenir :

// dans ArchiveContainer:
> II getItem(DD descriptor);

// dans ChannelArchive:
public >
    II getItem(DD descriptor)
    { ... }

Les génériques offrent un moyen de déclarer une variable de type avec deux bornes supérieures distinctes :

public  Foo fooBar(T t) { ... }

mais apparemment ce n'est pas autorisé lorsque l'une des bornes supérieures est un type paramètre plutôt qu'une classe ou une interface :

Les variables de type ont une borne facultative, T & I1 ... In. La borne se compose soit d'une variable de type, soit d'un type de classe ou d'interface T suivi éventuellement d'autres types d'interface I1, ..., In. […] C'est une erreur de compilation si l'un des types I1 ... In est un type de classe ou une variable de type. [link]

(emphase la mienne). Je ne sais pas pourquoi c'est le cas.

Mais je ne pense pas que cela devrait être un gros problème. Notez que, même après que Map a été transformé en générique en Map, sa méthode get prenait toujours un type Object. Naturellement, cette méthode renverra toujours null si vous passez une référence à un objet qui n'est pas de type K (car un tel objet ne devrait jamais avoir été inséré dans la carte), mais cela ne nuit pas à la sûreté du type.

0voto

GreyBeardedGeek Points 9546

Je sais que cela n'est probablement pas ce que vous voulez entendre, mais même si les génériques Java ressemblent syntaxiquement aux modèles C++, ils diffèrent beaucoup dans leur fonctionnement.

Recherchez java type erasure dans votre moteur de recherche préféré. Juste parce qu'un type est connu à la compilation ne signifie malheureusement pas que le type est récupérable lors de l'exécution, ou même lors des phases de compilation ultérieures.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X