it-swarm-pt.tech

Criar instância do tipo genérico em Java?

É possível criar uma instância de um tipo genérico em Java? Eu estou pensando com base no que eu vi que a resposta é no (devido ao tipo de apagamento), mas eu estaria interessado se alguém pode ver algo que eu estou faltando:

class SomeContainer<E>
{
    E createContents()
    {
        return what???
    }
}

EDIT: Acontece que Super Type Tokens poderia ser usado para resolver o meu problema, mas requer muito código baseado em reflexão, como algumas das respostas abaixo indicaram.

Deixarei isso em aberto por um tempo para ver se alguém apresenta algo drasticamente diferente do que o de Ian Robertson Artima Article .

525
David Citron

Você está certo. Você não pode fazer new E(). Mas você pode mudá-lo para

private static class SomeContainer<E> {
    E createContents(Class<E> clazz) {
        return clazz.newInstance();
    }
}

É uma dor. Mas isso funciona. Envolvê-lo no padrão de fábrica torna um pouco mais tolerável.

305
Justin Rudd

Não sei se isso ajuda, mas quando você subclasse (incluindo anonimamente) um tipo genérico, a informação do tipo está disponível via reflexão. por exemplo.,

public abstract class Foo<E> {

  public E instance;  

  public Foo() throws Exception {
    instance = ((Class)((ParameterizedType)this.getClass().
       getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();
    ...
  }

}

Então, quando você subclasse Foo, você obtém uma instância de Bar, por exemplo,

// notice that this in anonymous subclass of Foo
assert( new Foo<Bar>() {}.instance instanceof Bar );

Mas é muito trabalho e só funciona para subclasses. Pode ser útil embora.

121
noah

Você precisará de algum tipo de fábrica abstrata de um tipo ou outro para passar o dinheiro para:

interface Factory<E> {
    E create();
}

class SomeContainer<E> {
    private final Factory<E> factory;
    SomeContainer(Factory<E> factory) {
        this.factory = factory;
    }
    E createContents() {
        return factory.create();
    }
}
86
Tom Hawtin - tackline

No Java 8, você pode usar a interface Supplier functional para conseguir isso facilmente:

class SomeContainer<E> {
  private Supplier<E> supplier;

  SomeContainer(Supplier<E> supplier) {
    this.supplier = supplier;
  }

  E createContents() {
    return supplier.get();
  }
}

Você construiria essa classe assim:

SomeContainer<String> stringContainer = new SomeContainer<>(String::new);

A sintaxe String::new nessa linha é uma referência de construtor .

Se seu construtor aceita argumentos, você pode usar uma expressão lambda:

SomeContainer<BigInteger> bigIntegerContainer
    = new SomeContainer<>(() -> new BigInteger(1));
86
Daniel Pryden
package org.foo.com;

import Java.lang.reflect.ParameterizedType;
import Java.lang.reflect.Type;

/**
 * Basically the same answer as noah's.
 */
public class Home<E>
{

    @SuppressWarnings ("unchecked")
    public Class<E> getTypeParameterClass()
    {
        Type type = getClass().getGenericSuperclass();
        ParameterizedType paramType = (ParameterizedType) type;
        return (Class<E>) paramType.getActualTypeArguments()[0];
    }

    private static class StringHome extends Home<String>
    {
    }

    private static class StringBuilderHome extends Home<StringBuilder>
    {
    }

    private static class StringBufferHome extends Home<StringBuffer>
    {
    }   

    /**
     * This prints "String", "StringBuilder" and "StringBuffer"
     */
    public static void main(String[] args) throws InstantiationException, IllegalAccessException
    {
        Object object0 = new StringHome().getTypeParameterClass().newInstance();
        Object object1 = new StringBuilderHome().getTypeParameterClass().newInstance();
        Object object2 = new StringBufferHome().getTypeParameterClass().newInstance();
        System.out.println(object0.getClass().getSimpleName());
        System.out.println(object1.getClass().getSimpleName());
        System.out.println(object2.getClass().getSimpleName());
    }

}
23
Lars Bohl

Se você precisar de uma nova instância de um argumento de tipo dentro de uma classe genérica, faça seus construtores exigirem sua classe ...

public final class Foo<T> {

    private Class<T> typeArgumentClass;

    public Foo(Class<T> typeArgumentClass) {

        this.typeArgumentClass = typeArgumentClass;
    }

    public void doSomethingThatRequiresNewT() throws Exception {

        T myNewT = typeArgumentClass.newInstance();
        ...
    }
}

Uso:

Foo<Bar> barFoo = new Foo<Bar>(Bar.class);
Foo<Etc> etcFoo = new Foo<Etc>(Etc.class);

Prós:

  • Muito mais simples (e menos problemático) do que a abordagem de Super Type Token (STT) de Robertson.
  • Muito mais eficiente do que a abordagem STT (que vai comer o seu celular no café da manhã).

Contras:

  • Não é possível passar Class para um construtor padrão (é por isso que Foo é final). Se você realmente precisa de um construtor padrão, pode sempre adicionar um método setter, mas deve se lembrar de ligar para ela mais tarde.
  • Objeção de Robertson ... Mais Barras que uma ovelha negra (embora especificar a classe de argumento de tipo mais uma vez não o mate exatamente). E, ao contrário das afirmações de Robertson, isso não viola o principal DRY, pois o compilador garantirá a exatidão do tipo.
  • Não totalmente Foo<L>proof. Para iniciantes ... newInstance() lançará um wobbler se a classe de argumento de tipo não tiver um construtor padrão. Isso se aplica a todas as soluções conhecidas de qualquer maneira.
  • Falta o encapsulamento total da abordagem STT. Não é um grande problema embora (considerando a sobrecarga de desempenho ultrajante do STT).
20
R2D2M2

Você pode fazer isso agora e não requer um monte de código de reflexão.

import com.google.common.reflect.TypeToken;

public class Q26289147
{
    public static void main(final String[] args) throws IllegalAccessException, InstantiationException
    {
        final StrawManParameterizedClass<String> smpc = new StrawManParameterizedClass<String>() {};
        final String string = (String) smpc.type.getRawType().newInstance();
        System.out.format("string = \"%s\"",string);
    }

    static abstract class StrawManParameterizedClass<T>
    {
        final TypeToken<T> type = new TypeToken<T>(getClass()) {};
    }
}

É claro que se você precisar chamar o construtor que exigirá alguma reflexão, mas isso está muito bem documentado, esse truque não é!

Aqui está o JavaDoc para TypeToken .

19
user177800

Pense em uma abordagem mais funcional: em vez de criar algum E do nada (que é claramente um cheiro de código), passe uma função que sabe como criar um, ou seja,.

E createContents(Callable<E> makeone) {
     return makeone.call(); // most simple case clearly not that useful
}
12
Ingo

De Tutorial Java - Restrições de Genéricos :

Não é possível criar instâncias de parâmetros de tipo

Você não pode criar uma instância de um parâmetro de tipo. Por exemplo, o código a seguir causa um erro em tempo de compilação:

public static <E> void append(List<E> list) {
    E elem = new E();  // compile-time error
    list.add(elem);
}

Como solução alternativa, você pode criar um objeto de um parâmetro de tipo por meio da reflexão:

public static <E> void append(List<E> list, Class<E> cls) throws Exception {
    E elem = cls.newInstance();   // OK
    list.add(elem);
}

Você pode invocar o método append da seguinte maneira:

List<String> ls = new ArrayList<>();
append(ls, String.class);
8
Sergiy Sokolenko

Aqui está uma opção que eu criei, pode ajudar:

public static class Container<E> {
    private Class<E> clazz;

    public Container(Class<E> clazz) {
        this.clazz = clazz;
    }

    public E createContents() throws Exception {
        return clazz.newInstance();
    }
}

EDIT: Como alternativa, você pode usar esse construtor (mas requer uma instância de E):

@SuppressWarnings("unchecked")
public Container(E instance) {
    this.clazz = (Class<E>) instance.getClass();
}
6
Mike Stone

Se você não quiser digitar o nome da classe duas vezes durante a instanciação, como em:

new SomeContainer<SomeType>(SomeType.class);

Você pode usar o método de fábrica:

<E> SomeContainer<E> createContainer(Class<E> class); 

Como em:

public class Container<E> {

    public static <E> Container<E> create(Class<E> c) {
        return new Container<E>(c);
    }

    Class<E> c;

    public Container(Class<E> c) {
        super();
        this.c = c;
    }

    public E createInstance()
            throws InstantiationException,
            IllegalAccessException {
        return c.newInstance();
    }

}
6
jb.

Java infelizmente não permite o que você quer fazer. Veja a solução oficial :

Você não pode criar uma instância de um parâmetro de tipo. Por exemplo, o código a seguir causa um erro em tempo de compilação:

public static <E> void append(List<E> list) {
    E elem = new E();  // compile-time error
    list.add(elem);
}

Como solução alternativa, você pode criar um objeto de um parâmetro de tipo por meio da reflexão:

public static <E> void append(List<E> list, Class<E> cls) throws Exception {
    E elem = cls.newInstance();   // OK
    list.add(elem);
}

Você pode invocar o método append da seguinte maneira:

List<String> ls = new ArrayList<>();
append(ls, String.class);
5
Neepsnikeep

Quando você está trabalhando com E em tempo de compilação você não se importa com o tipo genérico real "E" (ou você usa reflexão ou trabalha com classe base de tipo genérico) então deixe a subclasse prover uma instância de E. 

Abstract class SomeContainer<E>
{

    abstract protected  E createContents();
    public doWork(){
        E obj = createContents();
        // Do the work with E 

     }
}


**BlackContainer extends** SomeContainer<Black>{
    Black createContents() {
        return new  Black();
    }
}
4
Ira

Você pode usar:

Class.forName(String).getConstructor(arguments types).newInstance(arguments)

Mas você precisa fornecer o nome exato da classe, incluindo pacotes, por exemplo. Java.io.FileInputStream. Eu usei isso para criar um analisador de expressões matemáticas.

3
Jaroslav Smid

Um imporovement da resposta de @ Noah. 

Razão para mudança

a] É mais seguro se mais de 1 tipo genérico for usado no caso de você alterar o pedido.

b] Uma assinatura de tipo genérico de classe muda de tempos em tempos para que você não fique surpreso com exceções inexplicáveis ​​no tempo de execução.

Código Robusto

public abstract class Clazz<P extends Params, M extends Model> {

    protected M model;

    protected void createModel() {
    Type[] typeArguments = ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments();
    for (Type type : typeArguments) {
        if ((type instanceof Class) && (Model.class.isAssignableFrom((Class) type))) {
            try {
                model = ((Class<M>) type).newInstance();
            } catch (InstantiationException | IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

Ou use o forro um

um código de linha

model = ((Class<M>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[1]).newInstance();
2
Amio.io

Eu pensei que poderia fazer isso, mas bastante desapontado: não funciona, mas acho que ainda vale a pena compartilhar. 

Talvez alguém possa corrigir:

import Java.lang.reflect.InvocationHandler;
import Java.lang.reflect.Method;
import Java.lang.reflect.Proxy;

interface SomeContainer<E> {
    E createContents();
}

public class Main {

    @SuppressWarnings("unchecked")
    public static <E> SomeContainer<E> createSomeContainer() {
        return (SomeContainer<E>) Proxy.newProxyInstance(Main.class.getClassLoader(),
                new Class[]{ SomeContainer.class }, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Class<?> returnType = method.getReturnType();
                return returnType.newInstance();
            }
        });
    }

    public static void main(String[] args) {
        SomeContainer<String> container = createSomeContainer();

    [*] System.out.println("String created: [" +container.createContents()+"]");

    }
}

Produz:

Exception in thread "main" Java.lang.ClassCastException: Java.lang.Object cannot be cast to Java.lang.String
    at Main.main(Main.Java:26)
    at Sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at Sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.Java:57)
    at Sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.Java:43)
    at Java.lang.reflect.Method.invoke(Method.Java:601)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.Java:120)

A linha 26 é aquela com o [*].

A única solução viável é a de @JustinRudd

2
Luigi R. Viggiano

Existem várias bibliotecas que podem resolver E para você usando técnicas semelhantes ao que o artigo de Robertson discutiu. Aqui está uma implementação de createContents que usa TypeTools para resolver a classe bruta representada por E:

E createContents() throws Exception {
  return TypeTools.resolveRawArgument(SomeContainer.class, getClass()).newInstance();
}

Isso pressupõe que getClass () resolve uma subclasse de SomeContainer e falhará de outra forma, já que o valor parametrizado real de E será apagado em tempo de execução se não for capturado em uma subclasse.

0
Jonathan

Se você quer dizer new E() Então é impossível. E gostaria de acrescentar que nem sempre é correto - como você sabe se E tem um construtor no-args público? Mas você sempre pode delegar a criação para alguma outra classe que saiba como criar uma instância - ela pode ser Class<E> ou seu código personalizado como este

interface Factory<E>{
    E create();
}    

class IntegerFactory implements Factory<Integer>{    
  private static int i = 0; 
  Integer create() {        
    return i++;    
  }
}
0
Pavel Feldman
return   (E)((Class)((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();
0
Rachid

Você pode conseguir isso com o seguinte snippet:

import Java.lang.reflect.ParameterizedType;

public class SomeContainer<E> {
   E createContents() throws InstantiationException, IllegalAccessException {
      ParameterizedType genericSuperclass = (ParameterizedType)
         getClass().getGenericSuperclass();
      @SuppressWarnings("unchecked")
      Class<E> clazz = (Class<E>)
         genericSuperclass.getActualTypeArguments()[0];
      return clazz.newInstance();
   }
   public static void main( String[] args ) throws Throwable {
      SomeContainer< Long > scl = new SomeContainer<>();
      Long l = scl.createContents();
      System.out.println( l );
   }
}
0
bogdan

Aqui está uma implementação de createContents que usa TypeTools para resolver a classe bruta representada por E:

E createContents() throws Exception {
  return TypeTools.resolveRawArgument(SomeContainer.class, getClass()).newInstance();
}

Essa abordagem só funciona se SomeContainer tiver uma subclasse, de forma que o valor real de E seja capturado em uma definição de tipo:

class SomeStringContainer extends SomeContainer<String>

Caso contrário, o valor de E é apagado em tempo de execução e não é recuperável.

0
Jonathan

Como você disse, você não pode fazer isso por causa do apagamento do tipo. Você pode fazer isso usando reflexão, mas isso exige muito código e muito tratamento de erros.

0
Adam Rosenfield

Espero que não seja tarde demais para ajudar !!!

Java é segurança de tipo, apenas Objeto pode criar instância.

No meu caso, não posso passar parâmetros para o método createContents. Minha solução está usando estende em vez de todas as respostas abaixo.

private static class SomeContainer<E extends Object> {
    E e;
    E createContents() throws Exception{
        return (E) e.getClass().getDeclaredConstructor().newInstance();
    }
}

Este é o meu caso de exemplo no qual não posso passar parâmetros.

public class SomeContainer<E extends Object> {
    E object;

    void resetObject throws Exception{
        object = (E) object.getClass().getDeclaredConstructor().newInstance();
    }
}

Usando reflexo, crie um erro de tempo de execução, se você estender sua classe genérica com nenhum tipo de objeto. Para estender seu tipo genérico para objeto, converta esse erro para compilar o erro de tempo. 

0
Se Song

Aqui está uma solução aprimorada, baseada em ParameterizedType.getActualTypeArguments, já mencionada por @noah, @Lars Bohl e alguns outros. 

Primeira pequena melhoria na implementação. Factory não deve retornar a instância, mas um tipo. Assim que você retornar a instância usando Class.newInstance(), você reduz o escopo de uso. Porque somente construtores sem argumentos podem ser chamados assim. Uma maneira melhor é retornar um tipo e permitir que um cliente escolha qual construtor ele deseja invocar:

public class TypeReference<T> {
  public Class<T> type(){
    try {
      ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();
      if (pt.getActualTypeArguments() == null || pt.getActualTypeArguments().length == 0){
        throw new IllegalStateException("Could not define type");
      }
      if (pt.getActualTypeArguments().length != 1){
        throw new IllegalStateException("More than one type has been found");
      }
      Type type = pt.getActualTypeArguments()[0];
      String typeAsString = type.getTypeName();
      return (Class<T>) Class.forName(typeAsString);

    } catch (Exception e){
      throw new IllegalStateException("Could not identify type", e);
    }

  }
}

Aqui está um exemplo de uso. @Lars Bohl mostrou apenas uma maneira decisiva de se tornar genérico genérico por extensão. @noah apenas através da criação de uma instância com {}. Aqui estão os testes para demonstrar os dois casos:

import Java.lang.reflect.Constructor;

public class TypeReferenceTest {

  private static final String NAME = "Peter";

  private static class Person{
    final String name;

    Person(String name) {
      this.name = name;
    }
  }

  @Test
  public void erased() {
    TypeReference<Person> p = new TypeReference<>();
    Assert.assertNotNull(p);
    try {
      p.type();
      Assert.fail();
    } catch (Exception e){
      Assert.assertEquals("Could not identify type", e.getMessage());
    }
  }

  @Test
  public void reified() throws Exception {
    TypeReference<Person> p = new TypeReference<Person>(){};
    Assert.assertNotNull(p);
    Assert.assertEquals(Person.class.getName(), p.type().getName());
    Constructor ctor = p.type().getDeclaredConstructor(NAME.getClass());
    Assert.assertNotNull(ctor);
    Person person = (Person) ctor.newInstance(NAME);
    Assert.assertEquals(NAME, person.name);
  }

  static class TypeReferencePerson extends TypeReference<Person>{}

  @Test
  public void reifiedExtenension() throws Exception {
    TypeReference<Person> p = new TypeReferencePerson();
    Assert.assertNotNull(p);
    Assert.assertEquals(Person.class.getName(), p.type().getName());
    Constructor ctor = p.type().getDeclaredConstructor(NAME.getClass());
    Assert.assertNotNull(ctor);
    Person person = (Person) ctor.newInstance(NAME);
    Assert.assertEquals(NAME, person.name);
  }
}

Nota: você pode forçar os clientes de TypeReference sempre usar {} quando a instância é criada fazendo esta classe abstract: public abstract class TypeReference<T>. Eu não fiz isso, apenas para mostrar o caso de teste apagado. 

0
Alexandr