Question

Supposons que je utilise une interface avec un paramètre de type générique

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 ce T est

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 Bar de classe. Dans ce cas, il me semble nécessaire d'ajouter le paramètre de type de 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);
  }
}

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). Est-il un moyen de « quantifier T loin »? À savoir, je peux prendre des dispositions pour le paramètre T à cacher dans l'interface de Bar, comme dans ce qui 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
  ... 
}
Était-ce utile?

La solution

Votre problème est similaire à celui résolu par une capture " aide », mais je ne suis pas sûr qu'elle peut être appliquée à votre deuxième exemple où deux méthodes distinctes sont utilisées. Votre première méthode de doStuff pourrait certainement être mieux écrit public void doStuff(Foo<?> foo), car il fonctionne indépendamment du paramètre de type Foo. Ensuite, le modèle « d'aide de capture » 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, il semble un peu désordonné; de l'extérieur, vous ne soupçonnez 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);
    }
  }
}

Autres conseils

Pour être utile, à un moment donné, vous allez définir le champ foo. À ce moment-là que vous devez savoir (ou être en mesure de capturer) T. Je vous conseille de faire que dans le constructeur, et il serait logique pour Bar d'avoir un paramètre générique. Vous pouvez même utiliser une interface si le code client ne doit pas voir le type. Cependant, je suppose que vous n'allez pas prendre mes conseils et je veux 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();
    }
}

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 Foo, qui ne contient pas de méthodes génériques. Présentez des méthodes génériques dont vous avez besoin dans FooImplBase<T> puis la classe concrète en dérive.

Dans votre exemple startStuff() et endStuff() seraient abstraits dans Foo et mis en œuvre FooImplBase<T>. Est-ce que le son comme il pourrait fonctionner dans votre situation réelle? Je suis d'accord, il est un peu lourd.

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

1) Il n'y a pas de type paramétrique impliqué dans la barre. Autrement dit, 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 attendez d'assigner à foo, il doit être un Foo . Si cela est vrai, alors oui, il ne fait pas partie de l'interface publique - tout a un type spécifique. Alors, je ne suis pas sûr de ce que vous entendez par « quantifier », car il n'y a pas de variables libres. Si vous voulez dire quantifiez universellement, comment conciliez-vous le fait que la barre n'a pas taper paramétrique, donc doit avoir été donné un type concret pour chacun des membres est tout?

2) Il existe un type paramétrique impliqué dans la barre. Il peut ne pas être évident dans l'interface publique, mais peut-être vous passer dans un Foo , de sorte que vous voulez avoir instancier une classe de bar avec plus d'un seul type. Puis, comme il est dit, cela est dans l'interface publique, et vous devez faire paramétrique Bar en T avec les génériques. Cela vous donne une certaine forme de quantification universelle pour la définition, « Pour tous les types T, cette définition est vrai ».

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 choisir dans la définition de Bar (remplaçant Foo Foo dans la définition de la classe) ou vous le laissez au client de la classe Bar. Dans ce bar de cas doit être fait générique

Si vous voulez avoir une interface Bar ne pas compter sur T et être en mesure de 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;
  // ...
}

Si vous voulez vraiment tirer quelque ire, vous pouvez mettre la classe Bar dans l'interface Foo, et sucer le T de cette façon. Voir cet article pour plus d'informations sur les classes à l'intérieur des interfaces . Peut-être cela est le cas où cela fait 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);
    }
  }
}

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 l'effacement anticipé:

public class Bar {

    private Foo<Object> foo;
    private Object t;

  ... 
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top