Pergunta

Suponha que eu estou usando uma interface com um parâmetro de tipo genérico

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

A intenção é que o tipo T é abstrato: ele impõe uma restrição tipo em implementações de Foo, mas o código do cliente não se importa exatamente o que T é

.

Este não é um problema no contexto de um método genérico:

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

Mas suponha que eu quero terminar o trabalho de doStuff, poupando algum estado em um Bar classe. Neste caso, parece-me necessário adicionar o parâmetro tipo de Foo para Bar.

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

  /* ... */

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

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

Esta é meio estranho, já que o T tipo de parâmetro não aparecer na interface pública de Bar (ou seja, não está incluído em qualquer parâmetro de método ou tipo de retorno). Existe uma maneira de "T quantificar longe"? Ou seja, eu posso mandar para o T parâmetro a ser escondido na interface de Bar, como no seguinte?

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

Solução

Seu problema é semelhante à resolvido por uma captura " ajudante", mas não tenho certeza de que ele pode ser aplicado para o seu segundo exemplo, onde dois métodos distintos são usados. Seu primeiro método doStuff definitivamente poderia ser melhor escrito como public void doStuff(Foo<?> foo), uma vez que funciona independentemente do parâmetro de tipo Foo. Em seguida, o padrão "captura helper" seria útil.


Update: depois de mexer um pouco, estendendo-se a idéia de auxiliar a captura de Goetz, eu vim com isso. No interior, parece um pouco confuso; do lado de fora, você não iria suspeitar de 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);
    }
  }
}

Outras dicas

Para ser útil, em algum momento, você está indo para definir o campo foo. Nesse ponto, você deve saber (ou ser capaz de captura) T. Eu sugeriria fazer isso no construtor, e então não faria sentido para Bar ter um parâmetro genérico. Você poderia até usar uma interface para o código do cliente não tem que ver o tipo. No entanto, eu suponho que você não está indo para tomar o meu conselho e realmente quer um setFoo. Então, basta adicionar um ponto à implementação comutável:

/* 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 que não ter uma hierarquia de três camadas:

abstract class Foo

abstract class FooImplBase<T> extends Foo

class Bar extends FooImplBase<String>

Os clientes só sei sobre Foo, que não contém quaisquer métodos genéricos. Introduzir quaisquer métodos genéricos que você precisa em FooImplBase<T> e depois os deriva classe concreta a partir dele.

Assim, no seu exemplo startStuff() e endStuff() seria resumo em Foo e implementado em FooImplBase<T>. Será que isso soe como ele poderia funcionar em sua situação real? Concordo que é um pouco pesado.

Você está definindo a classe Bar. Duas coisas são verdadeiras ...

1) Não há nenhum tipo paramétrico envolvido em Bar. Ou seja, o foo e membros t têm um único tipo, digamos, U, que é fixo para a definição. Se você está aprovou uma Foo que você espera para atribuir a foo, ele deve ser um Foo . Se tudo isso é verdade, então sim, não é parte da interface pública - tudo tem um tipo específico. Então, eu não tenho certeza do que você quer dizer com "quantificar", já que não existem variáveis ??tipo livres. Se você quer dizer universalmente quantificar, como você concilia o fato de que Bar tem nenhum datilografar paramétrico, então deve ter sido dado um tipo concreto de cada um de seus membros?

2) Existe um tipo paramétrico envolvido em Bar. Pode não ser, obviamente, na interface pública, mas talvez você passar em um Foo , assim que você quer ter uma classe Bar ser instanciado com mais de um único tipo. Então, como foi dito, este é na interface pública, e você precisa para fazer Bar paramétrico em T com os genéricos. Isto dá-lhe alguma forma de quantificação universal para a definição, "Para todos os tipos T, esta definição é verdadeira".

Em algum lugar deve foi decidido, que tipo você deseja usar para a 'T' dentro da classe Bar. Então, ou você deve escolheu na definição de Bar (substituindo Foo Foo por dentro da definição de classe) ou você deixá-lo até o cliente da classe Bar:. Neste caso Bar deve ser feita genérico

Se você quer ter uma interface para Bar não depender de T e ser capaz de escolher tipos diferentes de T, você deve usar uma interface não genérica ou uma classe base abstrata, como em:

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

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

Se você realmente quiser tirar algumas ira, você pode colocar a classe Bar dentro da interface de Foo, e chupar o T de que maneira. Consulte este artigo para obter mais informações sobre classes dentro de interfaces . Talvez este é o único caso em que faz 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);
    }
  }
}

O parâmetro T, neste caso, torna-se inútil na medida em Bar está em causa, uma vez que serão apagados a objeto em tempo de compilação. Assim, você poderá também "salvar o problema", e fazer o apagamento início:

public class Bar {

    private Foo<Object> foo;
    private Object t;

  ... 
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top