Instanciando objeto de parâmetro de tipo

90

Eu tenho uma classe de modelo como segue:

class MyClass<T>
{
    T field;
    public void myMethod()
    {
       field = new T(); // gives compiler error
    }
}

Como faço para criar uma nova instância de T em minha classe?

Mercurário
fonte

Respostas:

86

Após a eliminação do tipo, tudo o que se sabe Té que se trata de uma subclasse de Object. Você precisa especificar alguma fábrica para criar instâncias T.

Uma abordagem poderia usar Supplier<T>:

class MyClass<T> {

  private final Supplier<? extends T> ctor;

  private T field;

  MyClass(Supplier<? extends T> ctor) {
    this.ctor = Objects.requireNonNull(ctor);
  }

  public void myMethod() {
    field = ctor.get();
  }

}

O uso pode ser assim:

MyClass<StringBuilder> it = new MyClass<>(StringBuilder::new);

Como alternativa, você pode fornecer um Class<T>objeto e, em seguida, usar reflexão.

class MyClass<T> {

  private final Constructor<? extends T> ctor;

  private T field;

  MyClass(Class<? extends T> impl) throws NoSuchMethodException {
    this.ctor = impl.getConstructor();
  }

  public void myMethod() throws Exception {
    field = ctor.newInstance();
  }

}
Erickson
fonte
Em qual pacote o Supplierestá localizado? `MyClass (Class <? Extends T> impl)` deve declarar `throws NoSuchMethodException` para ser compilado. Sua resposta infelizmente não é amigável para iniciantes em Java.
purucat
@ user927387java.util.function.Supplier
erickson
O fornecedor <T> requer Java 8, JFTR para qualquer lugar que valha a pena.
Fran Marzoa
14

Outra abordagem não reflexiva é usar um padrão híbrido Builder / Abstract Factory.

Em Effective Java, Joshua Bloch examina o padrão Builder em detalhes e defende uma interface Builder genérica:

public interface Builder<T> {
  public T build();
}

Construtores concretos podem implementar esta interface e classes externas podem usar o construtor concreto para configurar o Construtor conforme necessário. O construtor pode ser passado para MyClass como um Builder<T>.

Usando esse padrão, você pode obter novas instâncias de T, mesmo que Ttenha parâmetros de construtor ou exija configuração adicional. Obviamente, você precisará de alguma forma de passar o Builder para MyClass. Se você não pode passar nada para MyClass, então Builder e Abstract Factory estão fora.


fonte
12

Isso pode ser mais pesado do que o que você está procurando, mas também funcionará. Observe que, se você adotar essa abordagem, faria mais sentido injetar a fábrica em MyClass quando ela for construída, em vez de passá-la para seu método cada vez que for chamada.

interface MyFactory<T> 
{
    T newObject();
}

class MyClass<T> 
{
    T field;
    public void myMethod(MyFactory<T> factory)
    {
       field = factory.newObject()
    }
}
Dan Hodge
fonte
1
Abordagem boa e não reflexiva; a reflexão nem sempre é uma opção. myMethod deve ser capaz de aceitar um MyFactory <? estende T>, certo?
Erickson
1
Boa chamada - você vai querer colocar um curinga limitado na fábrica para permitir que objetos do tipo T e subclasses de T sejam criados em myMethod ().
Dan Hodge de
0

Uma opção seria lançá-lo com Object

{field = (T) new Object();}

O campo será inicialmente do tipo Object, mas depois será convertido para o tipo T. Este é feio porque reduzir a conversão a zero é o que o objetivo deve ser para inicializações de objeto. Mas acho que isso vai funcionar.

Zakir
fonte
-2

Class classOfT

        try {
            t = classOfT.newInstance();//new T(); NOTE: type parameter T cannot be instantiated directly
        } catch (Exception e) {
            e.printStackTrace();
        }
Kevendra
fonte
2
Onde essa classe classOfT está declarada?
Fran Marzoa