2009-03-30 15 views
8

Supponiamo che io sto usando un'interfaccia con un parametro di tipo genericoNascondere un parametro di tipo "locale" in Java

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

L'intenzione è che il tipo T è astratto: si applica un vincolo di tipo sulle implementazioni di Foo , ma il codice cliente non interessa esattamente cosa sia 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 Bar. In questo caso, sembra necessario aggiungere il parametro type 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). C'è un modo per "quantificare T di distanza"? Posso installare il parametro T 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 
    ... 
} 
+0

Sai qual è il tipo T, quando scrivi Bar o è a tempo indeterminato? – Eddie

+0

Potrebbe chiarire, come Bar si piega? Se lo ottiene come parametro costruttore, allora T appare nella sua interfaccia. Se lo ottiene da qualche altra fonte, allora T è probabilmente definito lì e nel tuo caso è qualcosa di concreto come Foo . Come è fatto a modo tuo? –

risposta

4

Il tuo problema è simile a quello risolto da un "capture helper", ma non sono sicuro che può essere applicato al tuo secondo esempio in cui vengono utilizzati due metodi separati. Il tuo primo metodo doStuff potrebbe sicuramente essere scritto meglio come public void doStuff(Foo<?> foo), poiché funziona indipendentemente dal parametro di tipo Foo. Quindi, il pattern "cattura helper" sarebbe utile.


Aggiornamento: dopo armeggiare un po ', estendendo l'idea di aiutante cattura di Goetz, mi si avvicinò con questo. All'interno, sembra un po 'disordinato; dall'esterno, non si sospetterebbe 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); 
    } 
    } 
} 
1

perché non hanno una gerarchia a tre livelli:

abstract class Foo 

abstract class FooImplBase<T> extends Foo 

class Bar extends FooImplBase<String> 

clienti conoscono solo Foo, che non contiene alcun metodi generici. Introduci tutti i metodi generici necessari in FooImplBase<T> e successivamente la classe concreta ne deriva.

Quindi nel tuo esempio startStuff() e endStuff() sarebbe astratto in Foo e implementato in FooImplBase<T>. Sembra che potrebbe funzionare nella tua situazione reale? Sono d'accordo che è un po 'macchinoso.

1

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

1) Non c'è nessun tipo parametrico coinvolto in Bar. Cioè, i membri foo e t hanno un singolo tipo, ad esempio U, che è fissato per la definizione. Se hai passato un Foo che ti aspetti di assegnare a foo, deve essere Foo < U>. Se questo è tutto vero, allora sì, non fa parte dell'interfaccia pubblica - tutto ha un tipo specifico. Quindi, non sono sicuro di cosa intendi per "quantificare", poiché non ci sono variabili di tipo libero. Se intendi quantificare universalmente, come concili il fatto che la barra non abbia una digitazione parametrica, quindi deve essere stato dato un tipo concreto per ciascuno dei suoi membri?

2) In Bar è presente un tipo parametrico. Potrebbe non essere ovviamente nell'interfaccia pubblica, ma forse si passa a un Foo < T>, quindi si desidera creare un'istanza di una classe Bar con più di un singolo tipo. Quindi, come detto, questo è nell'interfaccia pubblica, e devi rendere il parametro Bar in T con i generici. Questo ti dà una qualche forma di quantificazione universale per la definizione, "Per tutti i tipi T, questa definizione è vera".

-2

Il parametro T in questo caso diventa inutile per quanto riguarda Bar, poiché verrà cancellato su Object in fase di compilazione.Così si potrebbe così "risparmiare la fatica", e di fare la cancellazione anticipata:

public class Bar { 

    private Foo<Object> foo; 
    private Object t; 

    ... 
} 
6

Per essere utile, ad un certo punto si imposta il campo foo. A quel punto dovresti sapere (o essere in grado di catturare) T. Vorrei suggerire di farlo nel costruttore, e quindi avrebbe senso per Bar avere un parametro generico. Potresti anche usare un'interfaccia in modo che il codice cliente non debba vedere il tipo. Tuttavia, presumo che tu non abbia intenzione di prendere il mio consiglio e davvero voglia un 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(); 
    } 
} 
+0

Scusa Tom, quando sono tornato per modificare la mia risposta in qualche modo non ho notato che avevi già pubblicato essenzialmente la stessa soluzione. +1 in onore di San Brendan e dei Vichinghi. – erickson

0

Da qualche parte si deve stato deciso, che tipo che si desidera utilizzare per la 'T' all'interno della classe Bar. Quindi o devi sceglierlo nella definizione di Bar (sostituendo Foo by Foo all'interno della definizione della classe) o lasciarlo al client della classe Bar: In questo caso la Barra deve essere resa generica.

Se si vuole avere un'interfaccia a Bar non fa affidamento 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; 
    // ... 
} 
0

Se vuoi davvero attirare un po 'di rabbia, puoi mettere la classe Bar nell'interfaccia Foo e succhiare il T di quel modo. Vedere this article per ulteriori informazioni sulle classi all'interno delle interfacce. Forse questo è l'unico caso in cui ciò 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); 
    } 
    } 
} 
Problemi correlati