2009-03-30 11 views
8

Supongamos que estoy usando una interfaz con un parámetro de tipo genéricoOcultación de un parámetro de tipo "local" en Java

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

La intención es que el tipo T es abstracto: se impone una restricción sobre el tipo de implementaciones Foo , pero al código del cliente no le importa exactamente qué es T.

esto no es problema en el contexto de un método genérico:

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

Pero supongamos que quiero romper el trabajo de doStuff, ahorrando algún estado en una clase Bar. En este caso, parece que necesito agregar el parámetro de tipo de 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); 
    } 
} 

Esto es un poco extraño, ya que el parámetro de tipo T no aparece en la interfaz pública de Bar (es decir, no está incluido en ningún parámetro del método o el tipo de retorno). ¿Hay alguna forma de "cuantificar T de distancia"? Es decir, ¿puedo organizar que el parámetro T esté oculto en la interfaz de Bar, como se muestra a continuación?

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

¿Sabes lo que es el tipo T, al escribir Bar, o es abierto? – Eddie

+0

¿Podría aclarar, cómo consigue Bar foo? Si lo obtiene como parámetro de constructor, entonces T aparece en su interfaz. Si lo obtiene de alguna otra fuente, entonces T probablemente se define allí y en su caso es algo concreto como Foo . ¿Cómo se hace en tu camino? –

Respuesta

4

Su problema es similar al resuelto por un "capture helper", pero no estoy seguro de que se puede aplicar a su segundo ejemplo, donde dos métodos separados son usados. Su primer método doStuff definitivamente podría escribirse mejor como public void doStuff(Foo<?> foo), ya que funciona independientemente del parámetro de tipo Foo. Entonces, el patrón de "capturar ayuda" sería útil.


Actualización: Después de juguetear un poco, extendiendo la idea de ayudante de captura de Goetz, me encontré con esto. En el interior, se ve un poco desordenado; desde el exterior, no sospecharías nada.

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

Por qué no tienen una jerarquía de tres niveles:

abstract class Foo 

abstract class FooImplBase<T> extends Foo 

class Bar extends FooImplBase<String> 

clientes sólo saben de Foo, que no contiene ningún métodos genéricos. Introduzca cualquier método genérico que necesite en FooImplBase<T> y luego la clase concreta deriva de él.

Así que en su ejemplo y startStuff()endStuff() habría resumen en Foo e implementado en FooImplBase<T>. ¿Eso suena como que podría funcionar en tu situación real? Estoy de acuerdo, es un poco engorroso.

1

Usted está definiendo la clase Bar. Dos cosas son ciertas ...

1) No hay ningún tipo paramétrico involucrado en la barra. Es decir, los miembros de foo y t tienen un único tipo, digamos U, que se fija para la definición. Si le pasan un Foo que espera asignar a foo, debe ser un Foo < U>. Si todo esto es cierto, entonces sí, no es parte de la interfaz pública, todo tiene un tipo específico. Entonces, no estoy seguro de lo que quiere decir con "cuantificar", ya que no hay variables de tipo gratuitas. Si te refieres a la cuantificación universal, ¿cómo concilias el hecho de que Bar no tiene tipeo paramétrico, por lo que debe haber dado un tipo concreto para cada uno de sus miembros?

2) Hay un tipo paramétrico involucrado en la barra. Puede que no esté obviamente en la interfaz pública, pero tal vez pase en un Foo < T>, por lo que desea tener una clase de Bar instanciada con más de un tipo. Entonces, como se dijo, esto está en la interfaz pública, y necesita hacer Bar paramétrico en T con genéricos. Esto le da alguna forma de cuantificación universal para la definición, "Para todos los tipos T, esta definición es verdadera".

-2

El parámetro T en este caso se vuelve inútil en lo que a Bar se refiere, ya que se borrará a Object en tiempo de compilación.Por lo que podría así "ahorrarse el trabajo", y hacer el borrado principios:

public class Bar { 

    private Foo<Object> foo; 
    private Object t; 

    ... 
} 
6

Para ser útil, en algún momento establecerá el campo foo. En ese punto, debe saber (o ser capaz de capturar) T. Sugeriría hacer eso en el constructor, y entonces tendría sentido que Bar tuviera un parámetro genérico. Incluso podría usar una interfaz para que el código del cliente no tenga que ver el tipo. Sin embargo, supongo que no seguirás mi consejo y realmente quiero un setFoo. Así que basta con añadir un punto de aplicación conmutable:

/* 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

Lo siento Tom, cuando volví a editar mi respuesta, de alguna manera no me di cuenta de que ya habías publicado esencialmente la misma solución. +1 en honor a San Brendan y los vikingos. – erickson

0

En algún lugar tiene que ha decidido, el tipo que desea utilizar para la 'T' dentro de la clase bar. Entonces, o debe elegirlo en la definición de Bar (reemplazando a Foo por Foo dentro de la definición de clase) o dejarlo en manos del cliente de la clase Bar: en este caso, Bar debe hacerse genérico.

Si usted quiere tener una interfaz con bar no confiando en T y será capaz de elegir diferentes tipos de T, se debe utilizar una interfaz no genérico o una clase base abstracta, como en:

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

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

Si realmente quieres provocar un poco de ira, puedes poner la clase Bar dentro de la interfaz Foo y chupar la T de esa manera. Consulte this article para obtener más información acerca de las clases dentro de las interfaces. Tal vez este es el único caso en que tiene sentido?

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