2009-03-30 10 views
8

Supposons que j'utilise une interface avec un paramètre de type génériqueHiding un paramètre de type « local » en Java

interface Foo<T> { 
    T getOne(); 
    void useOne(T t); 
} 

L'intention est que le type T est abstraite: il applique une contrainte de type sur les implémentations de Foo , mais le code client ne se soucie pas exactement de ce qu'est T.

Aucun problème dans le contexte d'une méthode générique:

public <T> void doStuff(Foo<T> foo) { 
    T t = foo.getOne(); 
    /* do stuff */ 
    foo.useOne(t); 
} 

Mais suppose que je veux rompre le travail de doStuff, sauver un état dans une classe Bar. Dans ce cas, il me semble avoir besoin d'ajouter le paramètre de type Foo à Bar.

public class Bar<T> { 
    private Foo<T> foo; 
    private T t; 

    /* ... */ 

    public void startStuff() { 
    t = foo.getOne(); 
    } 

    public void finishStuff() { 
    foo.useOne(t); 
    } 
} 

C'est un peu bizarre, car le paramètre de type T ne figure pas dans l'interface publique de Bar (à savoir, il ne figure pas dans tous les paramètres de la méthode ou le type de retour). Existe-t-il un moyen de "quantifier T"? Par exemple, puis-je faire en sorte que le paramètre T soit masqué dans l'interface de Bar, comme suit?

public class Bar { 
    <T> { // foo and t have to use the same T 
    private Foo<T> foo; 
    private T t; 
    } // T is out of scope 
    ... 
} 
+0

Savez-vous ce que le type T est, lorsque vous écrivez Bar, ou est-il ouvert? – Eddie

+0

Pourriez-vous clarifier, comment Bar obtient foo? S'il l'obtient en tant que paramètre constructeur, alors T apparaît dans son interface. S'il l'obtient à partir d'une autre source, alors T est probablement défini là et dans votre cas c'est quelque chose de concret comme Foo . Comment cela se fait-il à votre façon? –

Répondre

4

Votre problème est similaire à celui résolu par un "capture helper", mais je ne suis pas sûr qu'il peut être appliqué à votre deuxième exemple où Deux méthodes distinctes sont utilisées. Votre première méthode doStuff pourrait certainement être mieux écrit comme public void doStuff(Foo<?> foo), car il fonctionne indépendamment du paramètre de type Foo. Ensuite, le modèle "capture helper" serait utile.


Mise à jour: après bricoler un peu, étendant l'idée de l'aide de la capture de Goetz, je suis venu avec cela. A l'intérieur, ça a l'air un peu en désordre; de l'extérieur, vous ne soupçonneriez rien.

public class Bar { 
    private final Helper<?> helper; 
    public Bar(Foo<?> foo) { 
    this.helper = Helper.create(foo); 
    } 
    public void startStuff() { 
    helper.startStuff(); 
    } 
    public void finishStuff() { 
    helper.finishStuff(); 
    } 
    private static class Helper<T> { 
    private final Foo<T> foo; 
    private T t; 
    private Helper(Foo<T> foo) { 
     this.foo = foo; 
    } 
    static <T> Helper<T> create(Foo<T> foo) { 
     return new Helper<T>(foo); 
    } 
    void startStuff() { 
     t = foo.getOne(); 
    } 
    void finishStuff() { 
     foo.useOne(t); 
    } 
    } 
} 
1

Pourquoi ne pas avoir une hiérarchie à trois niveaux:

abstract class Foo 

abstract class FooImplBase<T> extends Foo 

class Bar extends FooImplBase<String> 

Les clients ne connaissent que Foo, qui ne contient pas de méthodes génériques. Introduisez toutes les méthodes génériques dont vous avez besoin dans FooImplBase<T> puis la classe concrète en dérive. Donc, dans votre exemple startStuff() et endStuff() serait abstrait dans Foo et implémenté dans FooImplBase<T>. Est-ce que cela semble fonctionner dans votre situation réelle? Je suis d'accord que c'est un peu lourd.

1

Vous définissez la classe Bar. Deux choses sont vraies ...

1) Il n'y a pas de type paramétrique impliqué dans la barre. C'est-à-dire que les membres foo et t ont un seul type, disons U, qui est fixé pour la définition. Si vous êtes passé un Foo que vous prévoyez d'attribuer à foo, il doit s'agir d'un Foo < U>. Si tout cela est vrai, alors oui, cela ne fait pas partie de l'interface publique - tout a un type spécifique. Ensuite, je ne suis pas sûr de ce que vous entendez par "quantifier", car il n'y a pas de variables de type libre. Si vous voulez dire quantifier universellement, comment pouvez-vous concilier le fait que Bar n'a pas de typage paramétrique, alors vous devez avoir donné un type concret pour chacun de ses membres?

2) Il existe un type paramétrique impliqué dans la barre. Il ne peut pas être évidemment dans l'interface publique, mais peut-être vous passez dans un Foo < T>, de sorte que vous voulez avoir une classe Bar instanciée avec plus d'un type unique. Puis, comme indiqué, c'est dans l'interface publique, et vous devez faire Bar paramétrique en T avec des génériques. Cela vous donne une forme de quantification universelle pour la définition, "Pour tous les types T, cette définition est vraie".

-2

Le paramètre T dans ce cas devient inutile dans la mesure du bar est concerné, car il sera effacé à l'objet au moment de la compilation.Donc, vous pourriez aussi bien « vous épargner la peine », et faire le début de l'effacement:

public class Bar { 

    private Foo<Object> foo; 
    private Object t; 

    ... 
} 
6

Pour être utile, à un certain point, vous allez définir le champ foo. À ce stade, vous devez savoir (ou être capable de capturer) T. Je suggère de faire cela dans le constructeur, et alors il serait logique pour Bar d'avoir un paramètre générique. Vous pouvez même utiliser une interface pour que le code client n'ait pas à voir le type. Cependant, je suppose que vous n'allez pas suivre mes conseils et que vous voulez vraiment un setFoo. Donc, il suffit d'ajouter un point à la mise en œuvre commutable:

/* pp */ class final BarImpl<T> { 
    private final Foo<T> foo; 
    private T t; 

    BarImpl(Foo<T> foo) { 
     this.foo = foo; 
    } 

    public void startStuff() { 
     t = foo.getOne(); 
    } 

    public void finishStuff() { 
     foo.useOne(t); 
    } 
} 

public final class Bar { 
    private BarImpl<?> impl; 

    /* ... */ 

    // Need to capture this wildcard, because constructors suck (pre-JDK7?). 
    public void setFoo(Foo<?> foo) { 
     setFooImpl(foo); 
    } 
    private <T> void setFooImpl(Foo<T> foo) { 
     impl = new BarImpl<T>(foo); 
    } 

    public void startStuff() { 
     impl.startStuff(); 
    } 

    public void finishStuff() { 
     impl.finishStuff(); 
    } 
} 
+0

Désolé Tom, quand je suis revenu pour éditer ma réponse, je n'ai pas remarqué que vous aviez déjà posté essentiellement la même solution. +1 en l'honneur de St. Brendan et des Vikings. – erickson

0

Quelque part il faut été décidé, quel type que vous souhaitez utiliser pour « T » intérieur de la classe Bar. Donc soit vous devez le choisir dans la définition de Bar (en remplaçant Foo par Foo dans la définition de classe), soit vous le laissez au client de la classe Bar: Dans ce cas Bar doit être rendu générique.

Si vous voulez avoir une interface Bar ne dépendez pas T et pourrez choisir différents types T, vous devez utiliser une interface non-générique ou une classe de base abstraite, comme dans:

interface Bar { 
    void startStuff(); 
    // ... 
} 

class BarImplement<T> { 
    private Foo<T> foo; 
    // ... 
} 
0

Si vous voulez vraiment dessiner quelque chose, vous pouvez placer la classe Bar dans l'interface Foo et aspirer le T de cette façon. Voir this article pour plus d'informations sur les classes à l'intérieur des interfaces. Peut-être que c'est le seul cas où cela a du sens?

interface Foo<T> { 
    T getOne(); 
    void useOne(T t); 

    public class Bar<T> { 
    private Foo<T> foo; 
    private T t; 
    public void startStuff() { 
     t = foo.getOne(); 
    } 
    public void finishStuff() { 
     foo.useOne(t); 
    } 
    } 
} 
Questions connexes