Pregunta

Supongamos que yo estoy usando una interfaz con un parámetro de tipo genérico

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

La intención es que el tipo T es abstracto:. Hace cumplir una restricción de tipo de implementaciones de Foo, pero el código de 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, el ahorro de un estado en un Bar clase. En este caso, me parece que tenga que añadir 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 T parámetro de tipo 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 una manera de "cuantificar T distancia"? Es decir, puedo disponer la T parámetro que se oculta en la interfaz de Bar, como en el siguiente?

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
  ... 
}
¿Fue útil?

Solución

Su problema es similar al resuelto por una captura " ayudante", pero no estoy seguro de que se puede aplicar a su segundo ejemplo en el que se utilizan dos métodos distintos. Su primer método doStuff definitivamente podría estar mejor escrito como public void doStuff(Foo<?> foo), ya que funciona independientemente del parámetro de tipo Foo. Entonces, el patrón de "captura de ayudante" sería útil.


Actualización: Después de juguetear un poco, extendiendo la idea de ayudante de captura de Goetz, se me ocurrió esto. En el interior, se ve un poco desordenado; desde el exterior, no sospecharía 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);
    }
  }
}

Otros consejos

Para ser útil, en algún momento se va a establecer el campo foo. En ese momento usted debe saber (o ser capaz de capturar) T. Yo sugeriría hacer que en el constructor, y entonces tendría sentido que Bar para tener un parámetro genérico. Incluso se puede utilizar una interfaz para que el código del cliente no tiene que ver el tipo. Sin embargo, supongo que no va a tomar mi consejo y realmente quiere 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();
    }
}

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

abstract class Foo

abstract class FooImplBase<T> extends Foo

class Bar extends FooImplBase<String>

Los clientes sólo saben de Foo, que no contiene ningún métodos genéricos. Introducir cualquiera de los métodos genéricos que necesita en FooImplBase<T> y luego la clase concreta se deriva de ella.

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

Se está definiendo la clase bar. Dos cosas son ciertas ...

1) No hay ningún tipo paramétrico involucrado en barra. Es decir, el foo y miembros de T tienen un solo tipo, dicen U, que se fija para la definición. Que si le pasan un Foo que espera asignar a foo, que debe ser un Foo . 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 entendemos por "cuantificar", ya que no hay variables de tipo libre. Si se refiere a cuantificar universalmente, ¿cómo se concilia el hecho de que no tiene ninguna barra de tipificación paramétrico, por lo que debe haber sido dado un tipo concreto para cada uno de sus miembros?

2) Existe un tipo paramétrico involucrado en barra. Puede que no sea obvio en la interfaz pública, pero tal vez se pasa en un Foo , por lo que quieren tener una clase de barra de una instancia con más de un solo tipo. Entonces, como se ha dicho, esto es en la interfaz pública, y hay que hacer Bar paramétrica en T con los genéricos. Esto le da algún tipo de cuantificación universal para la definición, "Para todos los tipos T, esta definición es verdadera".

En algún lugar tiene que ha decidido, el tipo que desea utilizar para la 'T' dentro de la clase bar. Así que, o se debe elegir en la definición de barra (en sustitución de Foo Foo por dentro de la definición de clase) o lo deja a discreción del cliente de la clase de barras:. En este caso debe hacerse Bar genérica

Si usted quiere tener una interfaz para Bar no depender de 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;
  // ...
}

Si realmente quiere sacar un poco la ira, puede poner la clase bar dentro de la interfaz de Foo, y chupar el T de esa manera. Ver este artículo para obtener más información sobre las clases dentro de las interfaces . Tal vez este es el único caso en que tenga 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);
    }
  }
}

El parámetro T en este caso se vuelve inútil en lo que se refiere al bar, ya que se borrará al objeto 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;

  ... 
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top