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

576

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

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

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

Vou deixar isso em aberto por um tempo para ver se alguém apresenta algo dramaticamente diferente do Artigo Artima de Ian Robertson .

David Citron
fonte
2
Acabei de testar o desempenho no dispositivo Android. 10000 operações e: 8-9 ms obtém o novo SomeClass (), 9-11 ms obtêm o Factory <SomeClass> .createInstance () e 64-71 ms efetuam a reflexão mais curta: SomeClass z = SomeClass.class.newInstance (). E todos os testes foram em um único bloco try-catch. Reflexão newInstance () lança 4 exceções diferentes, lembra? Então eu decidi usar padrão de fábrica
Deepscorn
Veja também: stackoverflow.com/a/5684761/59087
Dave Jarvis
4
Com o Java 8, agora você pode passar uma referência de construtor ou um lambda, o que torna esse problema bastante trivial para contornar. Veja minha resposta abaixo para obter detalhes.
Daniel Pryden
Acho que é uma má idéia escrever esse código, são as formas mais elegantes e legíveis de resolver o problema subjacente.
Krzysztof Cichocki
1
@DavidCitron "por um tempo" ele disse ... Faz onze anos desde então ...
MC Emperor

Respostas:

332

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

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

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

Justin Rudd
fonte
11
Sim, eu vi essa solução, mas só funciona se você já tiver uma referência a um objeto de classe do tipo que deseja instanciar.
David Citron
11
Sim, eu sei. Seria bom se você pudesse fazer E.class mas que simplesmente dá-lhe Object.class por causa de apagamento :)
Justin Rudd
6
Essa é a abordagem correta para esse problema. Geralmente não é o que você deseja, mas é o que você recebe.
Joachim Sauer
38
E como você chama o método createContents ()?
Alexis Dufrenoy 11/08
8
Esta não é mais a única maneira de fazer isso, existe uma maneira melhor agora que não requer a passagem de uma Class<?>referência usando o Guava e o TypeToken; veja esta resposta para obter o código e os links!
129

Não sei se isso ajuda, mas quando você subclasse (incluindo anonimamente) um tipo genérico, as informações do tipo estão disponíveis 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ê subclassifica Foo, 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 dá muito trabalho e funciona apenas para subclasses. Pode ser útil embora.

Noé
fonte
2
Sim, isso é bom, especialmente se a classe genérica é abstrato, você pode fazer isso nas subclasses concretas :)
Pierre Henry
Este método também funciona se a classe Foonão for abstrata. Mas por que funciona apenas em subclasses anônimas de Foo? Suponha que façamos Fooconcreto (deixamos de fora abstract), por que new Foo<Bar>();resultará em erro, enquanto new Foo<Bar>(){};não? (Exceção: "A classe não pode ser convertida em ParameterizedType")
Tim Kuipers
2
O @TimKuipers <E>em class Foo<E>não está limitado a qualquer tipo particular. Você vai ver o comportamento excepcional, sempre que Enão é estaticamente ligado, como em: new Foo<Bar>(), new Foo<T>() {...}ou class Fizz <E> extends Foo<E>. O primeiro caso não é estaticamente vinculado, é apagado no momento da compilação. O segundo caso substitui outra variável de tipo (T) no lugar de Emas ainda não está vinculada. E, no último caso, deve ser óbvio que Eainda não está vinculado.
William
4
Um exemplo de ligação estaticamente ao parâmetro type seria class Fizz extends Foo<Bar>- nesse caso, os usuários de Fizzobtêm algo que é a Foo<Bar>e não podem ser nada além de a Foo<Bar>. Portanto, nesse caso, o compilador pode codificar essas informações nos metadados da classe Fizze disponibilizá-las como um ParameterizedTypecódigo de reflexão. Quando você cria uma classe interna anônima como new Foo<Bar>() {...}se estivesse fazendo a mesma coisa, exceto que Fizzo compilador gera um nome de classe "anônimo" que você não saberá até que a classe externa seja compilada.
William Price
4
Deve-se observar que isso não funcionará se os argumentos de tipo também forem um ParameterizedType. Por exemplo Foo<Bar<Baz>>,. Você criará uma instância da ParameterizedTypeImplqual não pode ser criada explicitamente. Portanto, é uma boa ideia verificar se getActualTypeArguments()[0]está retornando a ParameterizedType. Se for, você deseja obter o tipo bruto e criar uma instância disso.
esmagar
106

No Java 8, você pode usar a Supplierinterface funcional para conseguir isso com bastante facilidade:

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::newnessa linha é uma referência de construtor .

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

SomeContainer<BigInteger> bigIntegerContainer
    = new SomeContainer<>(() -> new BigInteger(1));
Daniel Pryden
fonte
6
Um bom. Evita a reflexão e a necessidade de tratar exceções.
Bruno Leite
2
Tão legal. Infelizmente para usuários do Android, isso requer nível de API 24 ou superior.
Michael Updike
1
De acordo com menos barulho e abordagem funcional esta será aceite resposta na minha opinião
Tomas
2
… E não é diferente desta resposta ainda mais antiga, mostrando que o padrão técnico por trás dela é ainda mais antigo que o suporte do Java a expressões lambda e referências a métodos, enquanto você pode usar esse código mais antigo com elas depois de atualizar seu compilador…
Holger
Seria possível colocar apenas SomeContainer stringContainer = new SomeContainer(String::new);?
Aaron Franke
87

Você precisará de algum tipo de fábrica abstrata de um tipo ou de 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();
    }
}
Tom Hawtin - linha de orientação
fonte
..e como Factory.create () se parece?
OhadR
6
@OhadR Factory<>é uma interface e, portanto, não há corpo. O ponto é que você precisa de uma camada de indiretos para passar o buck para métodos que "conhecem" o código necessário para construir uma instância. É muito melhor fazer isso com código normal, em vez de metalinguístico Classou Constructorporque a reflexão traz todo um mundo de mágoa.
Tom Hawtin - defina
2
Agora dias você pode criar uma instância de fábrica com uma expressão de referência de método como este:SomeContainer<SomeElement> cont = new SomeContainer<>(SomeElement::new);
Lii
24
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());
    }

}
Lars Bohl
fonte
3
Uma boa abordagem por esse código pode causar ClassCastException se você usar no genérico um tipo genérico. Então você recupera o argumento actualType Você deve verificar se ele também é ParamterizedType e, em caso afirmativo, retornar seu RawType (ou algo melhor que isso). Outro problema é quando estendemos mais do que uma vez que esse código também lança o ClassCastExeption.
Damian Leszczyński - Vash
3
Causado por: java.lang.ClassCastException: sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl não pode ser transmitido para java.lang.Class
juan
@ DamianLeszczyński-Vash também falhará com, por exemploclass GenericHome<T> extends Home<T>{}
Holger
22

Se você precisar de uma nova instância de um argumento de tipo dentro de uma classe genérica, faça com que seus construtores exijam 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ática) do que a abordagem Super Type Token (STT) de Robertson.
  • Muito mais eficiente que a abordagem STT (que consumirá seu celular no café da manhã).

Contras:

  • Não é possível passar a classe para um construtor padrão (e é por isso que o Foo é final). Se você realmente precisa de um construtor padrão, sempre pode adicionar um método setter, mas lembre-se de ligar mais tarde.
  • A objeção de Robertson ... Mais barras do que uma ovelha negra (embora especificar a classe do argumento de tipo mais uma vez não o mate exatamente). E, contrariamente às afirmações de Robertson, isso não viola o principal DRY, porque o compilador garantirá a correção do tipo.
  • Não é inteiramente Foo<L>prova. Para iniciantes ... newInstance()lançará um wobbler se a classe de argumentos do 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. Porém, não é grande coisa (considerando a sobrecarga de desempenho ultrajante do STT).
R2D2M2
fonte
22

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()) {};
    }
}

Obviamente, 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 .

Lukasz Stelmach
fonte
6
Essa solução funciona para um conjunto limitado de casos, assim como a resposta de @ noah com reflexão. Eu tentei todos eles hoje ... E terminei passando uma instância da classe de parâmetros para a classe parametrizada (para poder chamar .newInstance ()). Deficiência muito grande de "genéricos" ... novo Foo <Bar> (Bar.class); ... classe Foo <T> {final privado Class <T> mTFactory; Foo (Classe <T> tClass) {mTFactory = tClass; ...} instância T = tFactory.newInstance (); }
yvolk
isso funciona em todos os casos, até mesmo métodos de fábrica estáticos que usam parâmetros genéricos
13

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

E createContents(Callable<E> makeone) {
     return makeone.call(); // most simple case clearly not that useful
}
Ingo
fonte
6
Tecnicamente, você não está passando uma função, está passando um objeto de função (também conhecido como um functor ).
precisa saber é o seguinte
3
Ou talvez para superar a captura de Exceptionuso Supplier<E>.
Martin D
10

Do tutorial em Java - restrições sobre genéricos :

Não é possível criar instâncias dos 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 de 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);
Sergiy Sokolenko
fonte
6

Aqui está uma opção que eu criei, que 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 este construtor (mas requer uma instância de E):

@SuppressWarnings("unchecked")
public Container(E instance) {
    this.clazz = (Class<E>) instance.getClass();
}
Mike Stone
fonte
Sim, isso funciona da mesma maneira, mesmo sem genéricos - com os genéricos, a instanciação desse contêiner se torna um pouco redundante (você precisa especificar o que "E" é duas vezes).
David Citron
bem, isso é o que acontece quando você usa Java e genéricos ... eles não são bonitos, e existem severas limitações ...
Mike Stone
6

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();
    }

}
jb.
fonte
6

Infelizmente, o Java não permite o que você deseja 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 de 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);
Neepsnikeep
fonte
Você poderia, por favor, me dizer por que está com voto negativo quando faz isso? Não vejo por que a solução oficial é uma solução ruim. Obrigado.
Neepsnikeep
Eu acho que você descer votos, porque a sua resposta é essencialmente o mesmo que o de Justin Rudd: stackoverflow.com/a/75254/103412
Torsten
5

Quando você trabalha com E em tempo de compilação, você realmente não se importa com o tipo genérico real "E" (você usa reflexão ou trabalha com a classe base do tipo genérico); portanto, deixe a subclasse fornecer a 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();
    }
}
Ira
fonte
4

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.

hichris123
fonte
15
E como você obtém o nome exato da classe do tipo genérico em tempo de execução?
David Citron
Você precisaria salvá-lo usando uma instância dessa classe. Possível, embora dificilmente conveniente. Se o seu genérico tiver um membro do tipo E (ou T ou o que for), obter o nome binário é justo foo.getClass().getName(). De onde vem essa instância? Atualmente, estou passando um para um construtor no projeto em que estou trabalhando agora.
Mark Storer
3

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

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

No meu caso, não posso passar parâmetros para o createContentsmétodo Minha solução é usar extensões, 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 que não consigo passar parâmetros.

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

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

Usar reflexão criar 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 em erro de tempo de compilação.

Se Song
fonte
3

Use a TypeToken<T>classe:

public class MyClass<T> {
    public T doSomething() {
        return (T) new TypeToken<T>(){}.getRawType().newInstance();
    }
}
cacheoff
fonte
Se você usa o Goiaba em vez do GSON, é um pouco diferente:(T) new TypeToken<T>(getClass()){}.getRawType().newInstance();
Jacob van Lingen
2

Eu pensei que poderia fazer isso, mas muito decepcionado: 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 é a que possui o [*].

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

Luigi R. Viggiano
fonte
2

Uma melhoria da resposta de @ Noah.

Razão para mudança

a] É mais seguro se mais de um tipo genérico for usado, caso você tenha alterado a ordem.

b] Uma assinatura de tipo genérico de classe muda de tempos em tempos para que você não se surpreenda 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

Código de uma linha

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

o que você pode fazer é -

  1. Primeiro declare a variável dessa classe genérica

    2.Em seguida, faça um construtor e instanciar esse objeto

  2. Em seguida, use-o onde quiser

exemplo-

1

private Class<E> entity;

2

public xyzservice(Class<E> entity) {
        this.entity = entity;
    }



public E getEntity(Class<E> entity) throws InstantiationException, IllegalAccessException {
        return entity.newInstance();
    }

3)

E e = getEntity (entidade);

Sudhanshu Jain
fonte
0

Como você disse, você realmente não pode fazer isso devido ao apagamento do tipo. Você pode fazer isso usando reflexão, mas requer muito código e muito tratamento de erros.

Adam Rosenfield
fonte
Como você faria isso usando reflexão? O único método que vejo é Class.getTypeParameters (), mas que retorna apenas os tipos declarados, não os tipos de tempo de execução.
David Citron
Você está falando sobre isso? artima.com/weblogs/viewpost.jsp?thread=208860
David Citron
0

Se você quer dizer new E() , é impossível. E eu acrescentaria que nem sempre é correto - como você sabe se E tem um construtor público sem argumentos? Mas você sempre pode delegar a criação para alguma outra classe que sabe como criar uma instância - 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++;    
  }
}
Pavel Feldman
fonte
0
return   (E)((Class)((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();
Rachid
fonte
1
Isso não funciona no meu exemplo na pergunta original. A superclasse para SomeContaineré simples Object. Portanto, this.getClass().getGenericSuperclass()retorna a Class(classe java.lang.Object), não a ParameterizedType. Na verdade, isso já foi indicado pela resposta dos colegas stackoverflow.com/questions/75175/… .
David Citron
1
Totalmente errado: Exception in thread "main" java.lang.ClassCastException: java.lang.Class não pode ser convertido para java.lang.reflect.ParameterizedType
Aubin
0

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 );
   }
}
Bogdan Veliscu
fonte
4
Totalmente errado: Exception in thread "main" java.lang.ClassCastException: java.lang.Class não pode ser convertido para java.lang.reflect.ParameterizedType
Aubin
0

Existem várias bibliotecas que podem ser resolvidas Epor você usando técnicas semelhantes às discutidas no artigo de Robertson. Aqui está uma implementação createContentsque 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 () resolva para uma subclasse de SomeContainer e falhará caso contrário, já que o valor parametrizado real de E será apagado no tempo de execução, se não for capturado em uma subclasse.

Jonathan
fonte
0

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

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

Essa abordagem funciona apenas se SomeContainerfor subclassificada, portanto o valor real de Eé capturado em uma definição de tipo:

class SomeStringContainer extends SomeContainer<String>

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

Jonathan
fonte
-1

Você pode com um carregador de classe e o nome da classe, eventualmente, alguns parâmetros.

final ClassLoader classLoader = ...
final Class<?> aClass = classLoader.loadClass("java.lang.Integer");
final Constructor<?> constructor = aClass.getConstructor(int.class);
final Object o = constructor.newInstance(123);
System.out.println("o = " + o);
Roald
fonte
isso é pior do que apenas passar o objeto de classe
newacct
Você não precisa fazer referência explícita ao carregador de classes.
Stefan Reich
-1

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

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

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ão alguns exemplos de uso. @Lars Bohl mostrou apenas uma maneira significativa de obter genéricos reificados via 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 TypeReferencesempre uso {}quando instância é criada, fazendo esta classe abstrata: public abstract class TypeReference<T>. Eu não fiz isso, apenas para mostrar o caso de teste apagado.

Alexandr
fonte