Domanda

Supponiamo che io sto usando un'interfaccia con un parametro di tipo generico

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

L'intenzione è che il tipo T è astratta:. Si impone un vincolo di tipo sulle implementazioni di Foo, ma il codice del client non importa esattamente che cosa è T

Questo non è un problema nel contesto di un metodo generico:

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

Ma supponiamo che io voglio rompere il lavoro di doStuff, risparmiando qualche stato in una classe di Bar. In questo caso, mi sembra di essere necessario aggiungere il parametro tipo di Foo a Bar.

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

  /* ... */

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

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

Questo è un po 'strano, dal momento che il parametro di tipo T non viene visualizzato nell'interfaccia pubblica del Bar (vale a dire, non è incluso in qualsiasi parametro di metodo o tipo di ritorno). Esiste un modo per "quantificare T via"? Vale a dire, posso organizzare per il parametro T da nascondere nell'interfaccia di Bar, come di seguito?

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
  ... 
}
È stato utile?

Soluzione

Il tuo problema è simile a quello risolto da una cattura " aiuto", ma non sono sicuro che possa essere applicato al tuo secondo esempio in cui vengono utilizzati due metodi separati. Il tuo primo metodo doStuff potrebbe sicuramente essere meglio scritto come public void doStuff(Foo<?> foo), dato che funziona a prescindere dal tipo di parametro Foo. Quindi, il modello di "cattura aiuto" sarebbe utile.


Aggiornamento: dopo armeggiare un po ', estendendo l'idea di aiutante cattura di Goetz, sono arrivato fino a questo. Al suo interno, sembra un po 'disordinato; dal di fuori, non si sospetta di nulla.

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

Altri suggerimenti

Per essere utile, ad un certo punto si sta per impostare il campo foo. A quel punto si dovrebbe sapere (o essere in grado di catturare) T. Vorrei suggerire di fare che nel costruttore, e quindi avrebbe senso per Bar di avere un parametro generico. Si potrebbe anche usare un'interfaccia in modo codice client non ha bisogno di vedere il tipo. Tuttavia, presumo che non si ha intenzione di prendere il mio consiglio e vuole veramente una setFoo. Quindi, è sufficiente aggiungere un punto alla realizzazione commutabile:

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

Perché non hanno una gerarchia a tre livelli:

abstract class Foo

abstract class FooImplBase<T> extends Foo

class Bar extends FooImplBase<String>

I clienti sanno solo Foo, che non contiene alcun metodi generici. Introdurre i metodi generici avete bisogno in FooImplBase<T> e poi la classe concreta deriva da esso.

Quindi nel tuo esempio startStuff() e endStuff() sarebbero astratto in Foo e implementato in FooImplBase<T>. Suona come potrebbe funzionare nella vostra situazione reale? Sono d'accordo che è un po 'ingombrante.

Si sta definendo la classe Bar. Due cose sono vere ...

1) Non v'è alcun tipo parametrico coinvolti nel bar. Cioè, il foo e membri t hanno un unico tipo, dicono U, che è fissato per la definizione. Se stai passato un Foo che si prevede di assegnare pippo, deve essere un Foo . Se tutto questo è vero, allora sì, non è parte dell'interfaccia pubblica - tutto ha un tipo specifico. Poi, io non sono sicuro di cosa si intende per "quantificare", come non ci sono variabili di tipo libero. Se vuoi dire universalmente quantificare, come si concilia il fatto che Bar non ha battitura parametrico, in modo da avere ricevuto in data tipo concreto per ciascuno dei suoi membri?

2) V'è un tipo parametrico coinvolti in Bar. Esso non può essere ovviamente l'interfaccia pubblica, ma forse si passa in un Foo , in modo che si desidera avere una classe Bar un'istanza con più di un solo tipo. Poi, come detto, questo è l'interfaccia pubblica, ed è necessario fare Bar parametrico a T con i generici. Questo vi dà una qualche forma di quantificazione universale per la definizione, "Per tutti i tipi T, questa definizione è vero".

Da qualche parte si deve stato deciso, che tipo che si desidera utilizzare per la 'T' all'interno della classe Bar. Quindi, o si deve scegliere che nella definizione di Bar (che sostituisce Foo Foo dal all'interno della definizione di classe), o si lascia fino al cliente della classe Bar:. In questo caso si deve procedere Bar generico

Se si vuole avere un'interfaccia a Bar non basandosi su T e essere in grado di scegliere diversi tipi di T, è necessario utilizzare un'interfaccia non generica o una classe base astratta, come in:

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

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

Se davvero si vuole trarre qualche ire, si può mettere la classe Bar all'interno dell'interfaccia Foo, e succhiare il T di quel modo. Vedere questo articolo per ulteriori informazioni sulle classi all'interno interfacce . Forse questo è l'unico caso in cui questo ha un senso?

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

Il parametro T in questo caso diventa inutile per quanto Bar è interessato, in quanto verrà cancellata per oggetto al momento della compilazione. Così si potrebbe così "risparmiare la fatica", e di fare la cancellazione anticipata:

public class Bar {

    private Foo<Object> foo;
    private Object t;

  ... 
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top