Frage

Angenommen, ich eine Schnittstelle mit einem generischen Typparameter bin mit

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

Die Absicht ist, dass die Art T abstrakt ist. Es eine Art Einschränkung für Implementierungen von Foo erzwingt, aber der Client-Code genau kümmert sich nicht darum, was T ist

Das ist kein Problem im Rahmen einer allgemeinen Methode:

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

Aber ich nehme an, die Arbeit von doStuff brechen will, einigen Zustand in einer Klasse Bar speichern. In diesem Fall scheinen ich den Typ-Parameter von Foo zu Bar hinzufügen zu müssen.

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

  /* ... */

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

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

Diese Art von seltsam ist, da die Typ-Parameter T nicht in der öffentlichen Schnittstelle von Bar angezeigt wird (das heißt, ist es nicht in jedem Methodenparameter oder Rückgabetyp enthält). Gibt es eine Möglichkeit zu „quantifizieren T away“? Das heißt, kann ich veranlassen, um die Parameter T in der Schnittstelle von Bar versteckt werden, wie im Folgenden?

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
  ... 
}
War es hilfreich?

Lösung

Ihr Problem ist ähnlich, dass von einem "capture gelöst Helfer“, aber ich bin nicht sicher, ob es zu einem zweiten Beispiel angewandt werden kann, wo zwei verschiedene Methoden verwendet werden. Ihre erste doStuff Methode auf jeden Fall besser sein könnte als public void doStuff(Foo<?> foo) geschrieben, da sie unabhängig von Foo Typ-Parametern arbeitet. Dann würden die „capture Helfer“ Muster nützlich sein.


Update: Nach einem wenig Bastelei, die Idee von Goetz Capture Helfern erstreckt, ich kam mit dieser. Im Inneren sieht es ein wenig chaotisch; von außen, würden Sie etwas nicht vermuten.

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);
    }
  }
}

Andere Tipps

Um nützlich zu sein, irgendwann Sie das foo Feld gesetzt werden. An diesem Punkt sollten Sie wissen (oder in der Lage sein zu erfassen) T. Ich würde vorschlagen, dass in dem Konstruktor zu tun, und dann wäre es sinnvoll für Bar einen allgemeinen Parameter zu haben. Man könnte sogar eine Schnittstelle verwenden, so dass Client-Code nicht die Art zu sehen hat. Aber ich nehme an, Sie nicht meinen Rat gehen zu nehmen und wirklich ein setFoo wollen. So fügen Sie einfach einen Punkt umschaltbare Implementierung:

/* 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();
    }
}

Warum nicht haben eine dreistufige Hierarchie:

abstract class Foo

abstract class FooImplBase<T> extends Foo

class Bar extends FooImplBase<String>

Kunden wissen nur über Foo, die keine generische Methoden enthält. Führen Sie keine generische Methoden, die Sie in FooImplBase<T> benötigen und dann die konkrete Klasse leitet sich von ihm.

So in Ihrem Beispiel startStuff() und endStuff() in Foo abstrakt sein würden und in FooImplBase<T> umgesetzt. Klingt das wie könnte es in Ihrer realen Situation arbeiten? Ich bin damit einverstanden, es ist ein bisschen umständlich.

Sie definieren die Bar-Klasse. Zwei Dinge sind wahr ...

1) Es gibt keine parametrische Art in Bar beteiligt. Das heißt, die foo und t-Mitglieder haben einen einzigen Typ, sagen U, das heißt für die Definition festgelegt. Wenn Sie eine Foo geben sind, die Sie foo zuweisen erwarten, es muss ein Foo sein. Wenn das alles wahr ist, dann ja, es ist nicht Teil der öffentlichen Schnittstelle - alles einen bestimmten Typ hat. Dann bin ich nicht sicher, was Sie unter „quantifizieren“, da es keine freien Variablen vom Typ sind. Wenn Sie allgemein quantifizieren meine, wie vereinbaren Sie die Tatsache, dass Bar keine parametrische Eingabe hat, so haben muss einen konkreten Typen für jedes seiner Mitglieder gegeben?

2) Es ist eine parametrische Art in Bar beteiligt. Es ist vielleicht nicht offensichtlich in der öffentlichen Schnittstelle sein, aber vielleicht kommt man in einer Foo , so dass Sie wollen eine Bar Klasse haben mit mehr als einem einzigen Typ instanziert werden. Dann wird, wie erwähnt, ist dies in der öffentlichen Schnittstelle, und Sie müssen Bar parametrischer in T mit Generika machen. Dies gibt Ihnen eine Form der universellen Quantifizierung für die Definition: „Für alle Typen T, diese Definition gilt“.

Irgendwo muss es entschieden worden, welche Art Sie in der Bar-Klasse für ‚T‘ verwenden möchten. Also entweder Sie müssen wählten es in der Definition von Bar (innerhalb der Klassendefinition Foo von Foo ersetzen) oder Sie überlassen es den Kunden der Bar Klasse bis:. In diesem Fall muss Bar generic gemacht werden

Wenn Sie eine Schnittstelle müssen Bar nicht auf T Berufung und der Lage sein, verschiedene Typen für T zu wählen, sollten Sie eine nicht-generische Schnittstelle oder eine abstrakte Basisklasse verwenden, wie in:

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

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

Wenn Sie wirklich etwas ire zeichnen möchten, können Sie die Bar-Klasse in der Foo-Schnittstelle setzen, und saugen das T diese Art und Weise. Siehe dieser Artikel für weitere Informationen über die Klassen innerhalb von Schnittstellen . Vielleicht ist das ein Fall, in dem das Sinn macht?

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);
    }
  }
}

Der Parameter T in diesem Fall nutzlos wird so weit wie Bar betrifft, da sie gelöscht werden bei der Kompilierung auf Objekt. So könnte man auch „sparen Sie sich die Mühe“, und führen Sie die Löschung früh:

public class Bar {

    private Foo<Object> foo;
    private Object t;

  ... 
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top