Utworzyć wystąpienie typu ogólnego w Javie?


576

Czy w Javie można utworzyć instancję typu ogólnego? Myślę na podstawie tego, co widziałem, że odpowiedź brzmi no(z powodu usunięcia typu ), ale byłbym zainteresowany, gdyby ktoś mógł zobaczyć coś, czego mi brakuje.

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

EDYCJA: Okazuje się, że tokenów Super Type można użyć do rozwiązania mojego problemu, ale wymaga to dużo kodu opartego na refleksji, jak wskazały niektóre poniższe odpowiedzi.

Pozostawię to na chwilę otwarte, aby sprawdzić, czy ktoś wymyśli coś dramatycznie innego niż artykuł Artima Iana Robertsona .


2
Właśnie przetestowałem wydajność na urządzeniu z Androidem. 10000 operacji i: 8-9 ms zajmuje nowy SomeClass (), 9-11 ms zajmuje Factory <SomeClass> .createInstance (), a 64-71 ms przyjmuje najkrótsze odbicie: SomeClass z = SomeClass.class.newInstance (). Wszystkie testy odbywały się w bloku pojedynczego próbowania. Refleksja newInstance () generuje 4 różne wyjątki, pamiętasz? Postanowiłem więc użyć fabrycznego wzoru
Deepscorn


4
W Javie 8 możesz teraz przekazać odwołanie do konstruktora lub lambda, co sprawia, że ​​ten problem jest dość trywialny do obejścia. Zobacz moją odpowiedź poniżej, aby uzyskać szczegółowe informacje.
Daniel Pryden

Myślę, że pisanie takiego kodu nie jest dobrym pomysłem, ponieważ są bardziej eleganckie i czytelne sposoby rozwiązania tego problemu.
Krzysztof Cichocki

1
@DavidCitron „na chwilę” powiedział… Od tego czasu minęło jedenaście lat…
MC Emperor

Odpowiedzi:


332

Masz rację. Nie można tego zrobić new E(). Ale możesz to zmienić na

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

To ból. Ale to działa. Zawinięcie go w fabryczny wzór sprawia, że ​​jest trochę bardziej tolerowany.


11
Tak, widziałem to rozwiązanie, ale działa tylko wtedy, gdy masz już odwołanie do obiektu klasy typu, który chcesz utworzyć.
David Citron,

11
Tak, wiem. Byłoby miło, gdybyś mógł wykonać E.class, ale to po prostu daje Object.class z powodu skasowania :)
Justin Rudd

6
To jest właściwe podejście do tego problemu. Zwykle nie jest to, co chcesz, ale to, co dostajesz.
Joachim Sauer,

38
Jak wywołać metodę createContents ()?
Alexis Dufrenoy

8
To nie jest już jedyny sposób, aby to zrobić, istnieje lepszy sposób, który nie wymaga przekazywania Class<?>referencji za pomocą Guava i TypeToken, zobacz tę odpowiedź dla kodu i linków!

129

Nie wiem, czy to pomaga, ale jeśli podklasujesz (w tym anonimowo) typ ogólny, informacje o typie są dostępne poprzez odbicie. na przykład,

public abstract class Foo<E> {

  public E instance;  

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

}

Tak więc, gdy podklasujesz Foo, otrzymujesz instancję Bar np.

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

Ale to dużo pracy i działa tylko w przypadku podklas. Może się przydać.


2
Tak, jest to miłe, zwłaszcza jeśli klasa ogólna jest abstrakcyjna, możesz to zrobić w konkretnych podklasach :)
Pierre Henry

Ta metoda działa również, jeśli klasa Foonie jest abstrakcyjna. Ale dlaczego działa tylko na anonimowych podklasach Foo? Załóżmy, że robimy Fookonkretny (pomijamy abstract), dlaczego spowoduje new Foo<Bar>();błąd, a new Foo<Bar>(){};nie? (Wyjątek: „Nie można rzutować klasy na ParameterizedType”)
Tim Kuipers,

2
@TimKuipers The <E>in class Foo<E>nie jest związany z żadnym konkretnym typem. Pojawi się wyjątkową zachowanie, kiedy Enie jest statycznie związane, na przykład: new Foo<Bar>(), new Foo<T>() {...}, lub class Fizz <E> extends Foo<E>. Pierwszy przypadek nie jest związany statycznie, jest kasowany podczas kompilacji. Drugi przypadek zastępuje inną zmienną typu (T) zamiast, Eale nadal jest niezwiązany. I w ostatnim przypadku powinno być oczywiste, że Ewciąż nie ma ograniczeń.
William Price,

4
Przykładem statycznego wiązania parametru type może być class Fizz extends Foo<Bar>- w tym przypadku użytkownicy Fizzotrzymują coś, co jest Foo<Bar>i nie może być niczym innym jak Foo<Bar>. W takim przypadku kompilator chętnie zakoduje te informacje w metadanych klasy Fizzi udostępni je jako ParameterizedTypekod odbicia. Kiedy tworzysz anonimową klasę wewnętrzną tak new Foo<Bar>() {...}, jakby robiła to samo, tyle że zamiast Fizzkompilatora generuje „anonimową” nazwę klasy, której nie poznasz, dopóki klasa zewnętrzna nie zostanie skompilowana.
William Price,

4
Należy zauważyć, że to nie zadziała, jeśli argumenty typu są również ParameterizedType. Na przykład Foo<Bar<Baz>>. Będziesz tworzyć instancję, ParameterizedTypeImplktórej nie można jawnie utworzyć. Dlatego dobrym pomysłem jest sprawdzenie, czy getActualTypeArguments()[0]zwraca a ParameterizedType. Jeśli tak, to chcesz uzyskać typ raw i zamiast tego utworzyć jego instancję.
zmiażdżyć

106

W Javie 8 możesz użyć Supplierfunkcjonalnego interfejsu, aby osiągnąć to bardzo łatwo:

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

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

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

Zbudowałbyś tę klasę w następujący sposób:

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

Składnia String::neww tym wierszu jest odwołaniem do konstruktora .

Jeśli twój konstruktor przyjmuje argumenty, możesz zamiast tego użyć wyrażenia lambda:

SomeContainer<BigInteger> bigIntegerContainer
    = new SomeContainer<>(() -> new BigInteger(1));

6
Dobry. Unika refleksji i konieczności traktowania wyjątków.
Bruno Leite,

2
Bardzo miło Niestety dla użytkowników Androida wymaga to interfejsu API w wersji 24 lub wyższej.
Michael Updike

1
Według mniej kłopotliwego i funkcjonalnego podejścia, moim zdaniem zostanie to zaakceptowana odpowiedź
Tomas

2
… I nie różni się od tej nawet starszej odpowiedzi pokazującej, że wzorzec techniczny jest jeszcze starszy niż obsługa Java dla wyrażeń lambda i referencji metod, podczas gdy możesz nawet użyć tego starszego kodu po uaktualnieniu kompilatora…
Holger

Czy można by tak po prostu powiedzieć SomeContainer stringContainer = new SomeContainer(String::new);?
Aaron Franke

87

Będziesz potrzebować jakiejś abstrakcyjnej fabryki, aby przekazać złotówkę:

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

..i jak wygląda Factory.create ()?
OhadR

6
@OhadR Factory<>to interfejs, więc nie ma ciała. Chodzi o to, że potrzebujesz warstwy pośrednich, aby przekazać złotówki metodom, które „znają” wymagany kod do budowy instancji. O wiele lepiej jest robić to z normalnym kodem niż z metalingwistycznym Classlub Constructorponieważ odbicie powoduje cały świat bólu.
Tom Hawtin - tackline

2
Teraz możesz utworzyć instancję fabryki z takim wyrażeniem odwołania do metody, jak poniżej: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());
    }

}

3
Dobre podejście tego kodu może powodować wyjątek ClassCastException, jeśli użyjesz w ogólnym typie ogólnym. Następnie odzyskujesz argument actualType. Powinieneś sprawdzić, czy jest to również ParamterizedType, a jeśli tak, to zwróć jego RawType (lub coś lepszego niż to). Innym problemem jest to, że rozszerzamy więcej niż raz, gdy ten kod wygeneruje także ClassCastException.
Damian Leszczyński - Vash

3
Spowodowane przez: java.lang.ClassCastException: sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl nie mogą być oddane do java.lang.Class
juan

@ DamianLeszczyński-Vash również zawiedzie, np.class GenericHome<T> extends Home<T>{}
Holger

22

Jeśli potrzebujesz nowej instancji argumentu typu w klasie ogólnej, poproś konstruktorów, aby zażądali jej klasy ...

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

Stosowanie:

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

Plusy:

  • Znacznie prostsze (i mniej problematyczne) niż podejście Super Type Token (STT) Robertsona.
  • Znacznie bardziej wydajne niż podejście STT (które zje telefon komórkowy na śniadanie).

Cons:

  • Nie można przekazać klasy do domyślnego konstruktora (dlatego Foo jest ostateczny). Jeśli naprawdę potrzebujesz domyślnego konstruktora, zawsze możesz dodać metodę ustawiającą, ale musisz pamiętać, aby zadzwonić później.
  • Sprzeciw Robertsona ... Więcej sztabek niż czarnej owcy (chociaż ponowne podanie klasy argumentów typu nie do końca cię zabije). I wbrew twierdzeniom Robertsona i tak nie narusza to zasady DRY DRIVE, ponieważ kompilator zapewni poprawność typu.
  • Nie do końca Foo<L>dowód. Na początek ... newInstance()wyrzuci wobbler, jeśli klasa argumentów typu nie ma domyślnego konstruktora. W każdym razie dotyczy to wszystkich znanych rozwiązań.
  • Brakuje całkowitej enkapsulacji podejścia STT. Nie jest to jednak wielka sprawa (biorąc pod uwagę oburzające koszty działania STT).

22

Możesz to zrobić teraz i nie wymaga to dużego kodu refleksyjnego.

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

Oczywiście, jeśli trzeba wywołać konstruktora, który będzie wymagał refleksji, ale jest to bardzo dobrze udokumentowane, ta sztuczka nie jest!

Oto JavaDoc dla TypeToken .


6
To rozwiązanie działa w ograniczonym zestawie przypadków, podobnie jak odpowiedź @ noah z refleksją. Próbowałem ich wszystkich dzisiaj ... I skończyłem na przekazaniu instancji klasy parametru do sparametryzowanej klasy (aby móc wywołać .newInstance ()). Bardzo duży niedobór „generycznych” ... nowych Foo <Bar> (Bar.class); ... klasa Foo <T> {prywatny finał Klasa <T> mTFactory; Foo (klasa <T> tClass) {mTFactory = tClass; ...} T instance = tFactory.newInstance (); }
yvolk,

działa to we wszystkich przypadkach, nawet statyczne metody fabryczne, które przyjmują ogólne parametry

13

Pomyśl o bardziej funkcjonalnym podejściu: zamiast tworzyć E z niczego (co jest wyraźnie zapachem kodu), przekaż funkcję, która wie, jak je stworzyć, tj.

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

6
Technicznie rzecz biorąc, nie przekazujesz funkcji, przekazujesz obiekt funkcji (znany również jako funktor ).
Lorne Laliberte

3
A może przezwyciężyć łapania Exceptionużycie Supplier<E>zamiast.
Martin D

10

Z samouczka Java - Ograniczenia dotyczące rodzajów ogólnych :

Nie można utworzyć wystąpień parametrów typu

Nie można utworzyć wystąpienia parametru typu. Na przykład poniższy kod powoduje błąd czasu kompilacji:

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

Aby obejść ten problem, możesz utworzyć obiekt parametru typu poprzez odbicie:

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

Możesz wywołać metodę dołączania w następujący sposób:

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

6

Oto opcja, którą wymyśliłem, może pomóc:

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

EDYCJA: Alternatywnie możesz użyć tego konstruktora (ale wymaga instancji E):

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

Tak, działa to tak samo bez generycznych - generyczne tworzenie instancji tego kontenera staje się nieco zbędne (musisz określić, co to jest „E” dwa razy).
David Citron,

cóż, tak się dzieje, gdy używasz Javy i generycznych ... nie są ładne i istnieją poważne ograniczenia ...
Mike Stone

6

Jeśli nie chcesz wpisywać nazwy klasy dwa razy podczas tworzenia instancji, np .:

new SomeContainer<SomeType>(SomeType.class);

Możesz użyć metody fabrycznej:

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

Jak w:

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

Java niestety nie pozwala na to, co chcesz robić. Zobacz oficjalne obejście :

Nie można utworzyć wystąpienia parametru typu. Na przykład poniższy kod powoduje błąd czasu kompilacji:

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

Aby obejść ten problem, możesz utworzyć obiekt parametru typu poprzez odbicie:

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

Możesz wywołać metodę dołączania w następujący sposób:

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

Czy możesz mi powiedzieć, dlaczego oddajesz głos, kiedy to robisz? Nie rozumiem, dlaczego oficjalne obejście jest złym rozwiązaniem. Dzięki.
Neepsnikeep

Wydaje mi się, że dostajesz mniej głosów, ponieważ twoja odpowiedź jest zasadniczo taka sama jak odpowiedzi Justina Rudda: stackoverflow.com/a/75254/103412
Torsten

5

Kiedy pracujesz z E w czasie kompilacji, tak naprawdę nie obchodzi cię faktyczny typ ogólny „E” (albo używasz refleksji, albo pracujesz z podstawową klasą typu ogólnego), więc pozwól, aby podklasa dostarczyła instancję 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

Możesz użyć:

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

Musisz jednak podać dokładną nazwę klasy, w tym pakiety, np. java.io.FileInputStream. Użyłem tego do stworzenia parsera wyrażeń matematycznych.


15
A jak uzyskać dokładną nazwę klasy typu ogólnego w czasie wykonywania?
David Citron,

Musiałbyś go zapisać za pomocą instancji tej klasy. Wykonalne, choć mało wygodne. Jeśli twój generic miał członka typu E (lub T lub cokolwiek innego), uzyskanie jego nazwy binarnej jest po prostu foo.getClass().getName(). Skąd pochodzi ta instancja? Obecnie przekazuję jednego do konstruktora w projekcie, nad którym teraz pracuję.
Mark Storer

3

Mam nadzieję, że nie jest za późno na pomoc !!!

Java zapewnia bezpieczeństwo typu, tylko obiekt może utworzyć instancję.

W moim przypadku nie mogę przekazać parametrów do createContentsmetody. Moje rozwiązanie używa rozszerzeń zamiast wszystkich poniższych odpowiedzi.

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

To mój przykładowy przypadek, w którym nie mogę przekazać parametrów.

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

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

Używając refleksji stwórz błąd wykonania, jeśli rozszerzysz swoją klasę ogólną o żaden typ obiektu. Aby rozszerzyć typ ogólny na obiekt, przekonwertuj ten błąd na błąd czasu kompilacji.


3

Użyj TypeToken<T>klasy:

public class MyClass<T> {
    public T doSomething() {
        return (T) new TypeToken<T>(){}.getRawType().newInstance();
    }
}

Jeśli użyjesz Guawy zamiast GSON, to jest trochę inaczej:(T) new TypeToken<T>(getClass()){}.getRawType().newInstance();
Jacob van Lingen

2

Myślałem, że mogę to zrobić, ale byłem rozczarowany: to nie działa, ale myślę, że nadal warto się nim dzielić.

Może ktoś może poprawić:

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()+"]");

    }
}

Produkuje:

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)

Linia 26 to ta z [*] .

Jedynym realnym rozwiązaniem jest @JustinRudd


2

Ulepszenie odpowiedzi @ Noego.

Powód do zmiany

za] Jest bezpieczniejszy, jeśli użyjesz więcej niż 1 rodzaj ogólny w przypadku zmiany zamówienia.

b] Sygnatura typu ogólnego typu zmienia się od czasu do czasu, aby nie zaskoczyły Cię niewyjaśnione wyjątki w środowisku wykonawczym.

Solidny kod

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

Lub użyj jednej wkładki

Kod jednoliniowy

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

2

co możesz zrobić to -

  1. Najpierw zadeklaruj zmienną tej klasy ogólnej

    2. Następnie utwórz konstruktor i utwórz instancję tego obiektu

  2. Następnie używaj go w dowolnym miejscu

przykład-

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



0

Jeśli masz na myśli, new E() to jest niemożliwe. I dodałbym, że nie zawsze jest to poprawne - skąd wiesz, że E ma publiczny konstruktor bez argumentów? Ale zawsze możesz delegować tworzenie do innej klasy, która wie, jak utworzyć instancję - może to być Class<E>kod niestandardowy w ten sposób

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

class IntegerFactory implements Factory<Integer>{    
  private static int i = 0; 
  Integer create() {        
    return i++;    
  }
}

0
return   (E)((Class)((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();

1
To nie działa w moim przykładzie w pierwotnym pytaniu. Nadklasa dla SomeContainerjest po prostu Object. Dlatego this.getClass().getGenericSuperclass()zwraca a Class(klasa java.lang.Object), a nie ParameterizedType. Faktycznie zostało to już wskazane w odpowiedzi peer stackoverflow.com/questions/75175/ ....
David Citron,

1
Całkowicie niepoprawny: wyjątek w wątku „main” java.lang.ClassCastException: java.lang.Class nie można rzutować na java.lang.reflect.ParameterizedType
Aubin

0

Możesz to zrobić za pomocą następującego fragmentu:

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

4
Całkowicie niepoprawny: wyjątek w wątku „main” java.lang.ClassCastException: java.lang.Class nie można rzutować na java.lang.reflect.ParameterizedType
Aubin

0

Istnieją różne biblioteki, które można rozwiązać Eza pomocą technik podobnych do omawianych w artykule Robertsona. Oto implementacja createContentsużycia TypeTools do rozwiązania klasy surowej reprezentowanej przez E:

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

Zakłada się, że getClass () przekształca się w podklasę SomeContainer i inaczej się nie powiedzie, ponieważ rzeczywista sparametryzowana wartość E zostanie skasowana w czasie wykonywania, jeśli nie zostanie przechwycona w podklasie.


0

Oto implementacja createContentswykorzystująca TypeTools do rozwiązania klasy surowej reprezentowanej przez E:

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

To podejście działa tylko wtedy, gdy SomeContainerjest podklasowane, więc rzeczywista wartość Ejest przechwytywana w definicji typu:

class SomeStringContainer extends SomeContainer<String>

W przeciwnym razie wartość E jest usuwana w czasie wykonywania i nie można jej odzyskać.


-1

Możesz to zrobić za pomocą programu ładującego klasy i nazwy klasy, ewentualnie niektórych parametrów.

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

jest to gorsze niż tylko przekazanie obiektu klasy
newacct

W ogóle nie musisz jawnie odwoływać się do modułu ładującego klasy.
Stefan Reich

-1

Oto ulepszone rozwiązanie oparte na ParameterizedType.getActualTypeArguments , wspomnianym już przez @ noah, @Lars Bohl i kilku innych.

Pierwsza niewielka poprawa wdrożenia. Fabryka nie powinna zwracać instancji, ale typ. Gdy tylko zwrócisz instancję Class.newInstance(), zmniejsz zakres użycia. Ponieważ tylko konstruktory bez argumentów mogą być wywoływane w ten sposób. Lepszym sposobem jest zwrócenie typu i umożliwienie klientowi wyboru, który konstruktor chce wywołać:

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

  }
}

Oto przykłady użycia. @Lars Bohl pokazał tylko znaczący sposób na uzyskanie potwierdzonej genetyki poprzez rozszerzenie. @ noah tylko poprzez utworzenie instancji za pomocą {}. Oto testy pokazujące oba przypadki:

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

Uwaga: można zmusić klientów TypeReferencezawsze używać {}, gdy instancja jest tworzona przez co ta klasa abstrakcyjna: public abstract class TypeReference<T>. Nie zrobiłem tego, tylko po to, aby pokazać wymazaną skrzynkę testową.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.