2013-05-07 1 views
1

J'ai rencontré un problème en essayant de spécialiser une classe qui implémente une interface générique dont je voulais hériter de la même interface que la super classe, mais avec un argument de type plus spécifique . L'extrait suivant montre un exemple synthétique mais complet qui ne peut pas être compilé. Le commentaire contient le message d'erreur du compilateur Java.Hériter d'une interface générique plusieurs fois tout en respectant la variance

interface Producer<T> { 
    T get(); 
} 

class NumberProducer implements Producer<Number> { 
    @Override 
    public Number get() { return null; } 
} 

// Producer cannot be inherited with different arguments: <java.lang.Integer> and <java.lang.Number> 
class IntegerProducer extends NumberProducer implements Producer<Integer> { 
    @Override 
    public Integer get() { return null; } 
} 

Au sens PECS, Producer<T> est un producteur , donc Producer<Integer>serait un sous-type de Producer<Number>, mais il n'y a aucun moyen de déclarer que dans la définition de Producer<T>. Java n'autorise pas IntegerProducer à hériter de NumberProducer et Producer<Integer> en même temps que IntegerProducer hériterait alors de Producer<Integer> et Producer<Number> en même temps.

Existe-t-il une approche standard de cette limitation, par ex. un modèle qui résout le même problème sans nécessiter ce type d'héritage?

+0

Pourquoi voudriez-vous mettre en œuvre l'interface IntegerProducer? Vous pouvez remplacer la méthode get() sans implémenter l'interface. –

+0

Pourriez-vous donner un exemple de la façon dont ces classes sont destinées à être utilisées? Je ne suis pas sûr que même la suggestion commune de faire générique 'NumberProducer' fera ce que vous voulez faire. Si vous utilisez 'NumberProducer implémente Producer ', vous ne pouvez rien retourner sauf 'null' de' NumberProducer.get() 'car vous ne savez pas quel sous-type de' Number' est 'T'. C'est-à-dire que vous ne pouvez pas retourner 'new Number()' (je sais que le constructeur n'existe pas, mais supporter avec moi) car 'T' pourrait, en fait, être' Integer'. – millimoose

Répondre

2

Il suffit d'ajouter un paramètre à la super classe:

interface Producer<T> { 
    T get(); 
} 

class NumberProducer<T extends Number> implements Producer<T> { 
    @Override 
    public T get() { return null; } 
} 

class IntegerProducer extends NumberProducer<Integer> { // Implicit: implements Producer<Integer> 
    @Override 
    public Integer get() { return null; } 
} 
+0

C'est à peu près ce que j'ai fait maintenant, merci. J'ai introduit un 'SpecializableNumberProducer ' et j'en ai hérité 'NumberProducer' sans ajouter d'implémentation mais en passant' Number' pour le paramètre type. De cette façon, les utilisateurs de NumberProducer n'ont pas besoin de passer explicitement 'Number' comme paramètre de type à chaque utilisation. La réponse de Ben Schulz a plus de détails sur la mise en œuvre des génériques et ce qui empêche mon code original de fonctionner. – Feuermurmel

0

Si vous voulez vous assurer que T est un sous-type spécifique, vous pouvez utiliser

interface Producer<T extends Number> 

Je ne sais pas ce que reall du producteur est donc je dois deviner.

Mise à jour: Si je comprends bien, alors je dirais, vous devez déclarer une interface qui est un producteur. C'est simple.

De cette interface, je dériverais une nouvelle interface avec le type de base respecitve.

i.e. .:

interface Producer 
{ 
    base functions 
}; 


interface Newproducer<T extends Producer> 
{ 
}; 

Est-ce que vous aviez en tête?

+0

Le problème est que je veux utiliser l'interface 'Producer' pour les types incompatibles avec' Number', par ex. 'String'. Donc, je ne peux pas mettre une limite sur le paramètre de type là. – Feuermurmel

+0

Mise à jour de mon approche. Je ne sais pas si c'est ce que vous aviez en tête. Cela ressemble aussi un peu à une usine, mais sans savoir ce que vous avez l'intention, c'est difficile à dire. – Devolus

1

Supposons que nous ayons une méthode simple gimme.

public static <T> T gimme(Producer<T> p) { return p.get(); } 

Dans le cadre de gimme ne sait rien T. Il peut s'agir de Number, Integer ou de tout autre type de référence. Ainsi, en raison de l'effacement, le compilateur émet un appel d'interface à Producer.get()Object plutôt que l'appel spécifique, par exemple, IntegerProducer.get()Integer. Tous les types qui implémentent Producer<T> avec T != Object implémentent également implicitement Producer.get()Object. Cette implémentation implicite est transmise à l'implémentation spécifique. Cela peut être NumberProducer.get()Number ou IntegerProducer.get()Integer, mais pas les deux. C'est pourquoi vous ne pouvez pas implémenter la même interface deux fois.

D'autres langues permettent cette variance via le site de définition, où Producer<Integer> est un sous-type de Producer<Number>, mais hélas, Java ne fonctionne pas. La solution de contournement commune est également de rendre NumberProducer générique.

+0

Je comprends que l'appel 'p.get()' dans votre extrait sera compilé à un appel à 'Producer.get() Object' qui sera implémenté par' NumberProducer' mais 'IntegerProducer' pourrait juste remplacer cette méthode et Appelez 'NumberProducer.get() Integer' pour que je ne vois pas de problème. – Feuermurmel

+0

@Feuermurmel Dans les langues avec la variance du site de déclaration possible; 'IntegerProducer.get()' remplacerait 'NumberProducer.get()'. En Java, cependant, ils seraient des méthodes surchargées et le compilateur pourrait seulement transmettre à l'un d'entre eux. Donc c'est interdit. –

Questions connexes