Buat instance dari tipe generik di Jawa?

576

Apakah mungkin membuat instance tipe generik di Jawa? Saya berpikir berdasarkan apa yang saya lihat bahwa jawabannya adalah no( karena tipe erasure ), tetapi saya akan tertarik jika ada yang bisa melihat sesuatu yang saya lewatkan:

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

EDIT: Ternyata Token Jenis Super dapat digunakan untuk menyelesaikan masalah saya, tetapi memerlukan banyak kode berbasis refleksi, seperti yang ditunjukkan beberapa jawaban di bawah ini.

Saya akan membiarkan ini terbuka sebentar untuk melihat apakah ada orang yang menemukan sesuatu yang secara dramatis berbeda dari Artikel Artima dari Ian Robertson .

David Citron
sumber
2
Baru saja menguji kinerja pada perangkat Android. 10000 operasi dan: 8-9 ms mengambil SomeClass baru (), 9-11 ms mengambil Factory <SomeClass> .createInstance () dan 64-71 ms mengambil refleksi terpendek: SomeClass z = SomeClass.class.newInstance (). Dan semua tes berada dalam satu blok uji coba. Refleksi newInstance () melempar 4 pengecualian yang berbeda, ingat? Jadi saya memutuskan untuk menggunakan pola pabrik
Deepscorn
Lihat juga: stackoverflow.com/a/5684761/59087
Dave Jarvis
4
Dengan Java 8, Anda sekarang dapat melewati referensi konstruktor atau lambda yang membuat masalah ini cukup sepele untuk diselesaikan. Lihat jawaban saya di bawah untuk detailnya.
Daniel Pryden
Saya pikir ini adalah ide yang buruk untuk menulis kode seperti itu, cara yang lebih elegan dan mudah dibaca untuk menyelesaikan masalah di bawahnya.
Krzysztof Cichocki
1
@DavidCitron "sebentar", katanya ... Sudah sebelas tahun sejak itu ...
MC Emperor

Jawaban:

332

Anda benar. Anda tidak bisa melakukannya new E(). Tapi Anda bisa mengubahnya menjadi

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

Ini menyakitkan. Tapi itu berhasil. Membungkusnya dalam pola pabrik membuatnya sedikit lebih bisa ditoleransi.

Justin Rudd
sumber
11
Ya, saya melihat solusi itu, tetapi hanya berfungsi jika Anda sudah memiliki referensi ke objek Kelas dari jenis yang Anda ingin instantiate.
David Citron
11
Ya aku tahu. Akan lebih baik jika Anda bisa melakukan E.class tapi itu hanya memberi Anda Object.class karena penghapusan :)
Justin Rudd
6
Itu pendekatan yang tepat untuk masalah ini. Biasanya bukan itu yang Anda inginkan, tetapi itulah yang Anda dapatkan.
Joachim Sauer
38
Dan bagaimana Anda memanggil metode createContents ()?
Alexis Dufrenoy
8
Ini bukan lagi satu-satunya cara untuk melakukan ini, ada cara yang lebih baik sekarang yang tidak perlu melewati Class<?>referensi menggunakan Guava dan TypeToken, lihat jawaban ini untuk kode dan tautan!
129

Tidak tahu apakah ini membantu, tetapi ketika Anda mensubklasifikasikan (termasuk secara anonim) jenis generik, informasi jenis tersedia melalui refleksi. misalnya,

public abstract class Foo<E> {

  public E instance;  

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

}

Jadi, ketika Anda subkelas Foo, Anda mendapatkan instance dari Bar misalnya,

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

Tapi ini banyak pekerjaan, dan hanya berfungsi untuk subclass. Meskipun bisa berguna.

Nuh
sumber
2
Ya, ini bagus terutama jika kelas generiknya abstrak, Anda dapat melakukan ini di subkelas beton :)
Pierre Henry
Metode ini juga berfungsi jika kelas Footidak abstrak. Tetapi mengapa itu hanya bekerja pada subclass anonim Foo? Misalkan kita membuat Foobeton (kita tinggalkan abstract), mengapa akan new Foo<Bar>();menghasilkan kesalahan, sedangkan new Foo<Bar>(){};tidak? (Pengecualian: "Kelas tidak dapat dilemparkan ke ParameterizedType")
Tim Kuipers
2
@TimKuipers tersebut <E>di class Foo<E>tidak terikat untuk jenis tertentu. Anda akan melihat perilaku yang luar biasa setiap kali Etidak statis terikat, seperti di: new Foo<Bar>(), new Foo<T>() {...}, atau class Fizz <E> extends Foo<E>. Kasing pertama tidak terikat secara statis, terhapus pada waktu kompilasi. Kasus kedua menggantikan variabel tipe lain (T) sebagai pengganti Etetapi masih tidak mengikat. Dan dalam kasus terakhir harus jelas bahwa Emasih tidak terikat.
William Harga
4
Contoh pengikatan parameter tipe secara statis adalah class Fizz extends Foo<Bar>- dalam hal ini, pengguna Fizzmendapatkan sesuatu yang merupakan Foo<Bar>dan tidak bisa apa-apa selain a Foo<Bar>. Jadi dalam hal ini, kompiler dengan senang hati menyandikan informasi itu ke dalam metadata kelas untuk Fizzdan menjadikannya tersedia sebagai ParameterizedTypekode refleksi. Ketika Anda membuat kelas dalam anonim seperti new Foo<Bar>() {...}itu melakukan hal yang sama, kecuali bukannya Fizzkompiler menghasilkan nama kelas "anonim" yang Anda tidak akan tahu sampai kelas luar dikompilasi.
William Harga
4
Perlu dicatat bahwa ini tidak akan berfungsi jika argumen tipe juga merupakan ParameterizedType. Sebagai contoh Foo<Bar<Baz>>,. Anda akan membuat sebuah instance ParameterizedTypeImplyang tidak dapat dibuat secara eksplisit. Karena itu, ada baiknya memeriksa apakah getActualTypeArguments()[0]mengembalikan a ParameterizedType. Jika ya, maka Anda ingin mendapatkan jenis mentah, dan membuat instance dari itu.
naksir
106

Di Java 8 Anda dapat menggunakan Supplierantarmuka fungsional untuk mencapai ini dengan cukup mudah:

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

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

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

Anda akan membangun kelas ini seperti ini:

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

Sintaks String::newpada baris itu adalah referensi konstruktor .

Jika konstruktor Anda mengambil argumen, Anda dapat menggunakan ekspresi lambda sebagai gantinya:

SomeContainer<BigInteger> bigIntegerContainer
    = new SomeContainer<>(() -> new BigInteger(1));
Daniel Pryden
sumber
6
Bagus Ini menghindari refleksi dan harus memperlakukan pengecualian.
Bruno Leite
2
Baik sekali. Sayangnya untuk pengguna Android, ini membutuhkan API level 24 atau lebih tinggi.
Michael Updike
1
Menurut pendekatan yang kurang rewel dan fungsional, jawaban ini harus diterima menurut saya
Tomas
2
... dan itu tidak berbeda dengan jawaban yang lebih lama ini yang menunjukkan bahwa pola teknis di baliknya bahkan lebih tua daripada dukungan Java untuk ekspresi lambda dan referensi metode sementara Anda bahkan dapat menggunakan kode yang lebih lama dengan mereka begitu Anda meningkatkan kompiler Anda ...
Holger
Apakah mungkin untuk dibenarkan SomeContainer stringContainer = new SomeContainer(String::new);?
Aaron Franke
87

Anda memerlukan beberapa jenis pabrik abstrak untuk mengoper:

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 - tackline
sumber
..dan bagaimana rupa Factory.create ()?
OhadR
6
@OhadR Factory<>adalah antarmuka sehingga tidak ada isi. Intinya adalah Anda memerlukan lapisan tipuan untuk meneruskan tanggung jawab ke metode yang "tahu" kode yang diperlukan untuk membangun sebuah instance. Jauh lebih baik melakukan ini dengan kode normal daripada metalinguistik Classatau Constructorsebagai refleksi membawa seluruh dunia terluka.
Tom Hawtin - tackline
2
Sekarang, Anda dapat membuat instance pabrik dengan ekspresi referensi metode seperti ini: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
sumber
3
Pendekatan yang baik oleh kode ini dapat menyebabkan ClassCastException jika Anda menggunakan generik tipe generik. Kemudian Anda retrive argumen actualType Anda harus memeriksa bahwa itu juga ParamterizedType dan jika demikian kembalikan RawType (atau sesuatu yang lebih baik dari ini). Masalah lain dengan ini adalah ketika kita memperpanjang lebih dari sekali kode ini juga akan membuang ClassCastExeption.
Damian Leszczyński - Vash
3
Disebabkan oleh: java.lang.ClassCastException: sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl tidak dapat dilemparkan ke java.lang.Class
juan
@ DamianLeszczyński-Vash juga akan gagal dengan, misalnyaclass GenericHome<T> extends Home<T>{}
Holger
22

Jika Anda memerlukan contoh baru dari argumen tipe di dalam kelas generik maka buat konstruktor Anda menuntut kelasnya ...

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

Pemakaian:

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

Pro:

  • Jauh lebih sederhana (dan tidak terlalu bermasalah) daripada pendekatan Super Type Token (STT) Robertson.
  • Jauh lebih efisien daripada pendekatan STT (yang akan memakan ponsel Anda untuk sarapan).

Cons:

  • Tidak dapat meneruskan Kelas ke konstruktor default (itulah sebabnya Foo final). Jika Anda benar-benar membutuhkan konstruktor default, Anda selalu dapat menambahkan metode penyetel tetapi kemudian Anda harus ingat untuk meneleponnya nanti.
  • Keberatan Robertson ... Lebih banyak Bar daripada seekor domba hitam (meskipun menentukan kelas argumen tipe sekali lagi tidak akan benar-benar membunuh Anda). Dan bertentangan dengan klaim Robertson, ini tidak melanggar prinsip KERING karena kompiler akan memastikan ketepatan jenis.
  • Tidak sepenuhnya Foo<L>bukti. Sebagai permulaan ... newInstance()akan melempar wobbler jika kelas argumen type tidak memiliki konstruktor default. Ini berlaku untuk semua solusi yang diketahui.
  • Tidak memiliki enkapsulasi total dari pendekatan STT. Bukan masalah besar (mengingat overhead kinerja STT yang keterlaluan).
R2D2M2
sumber
22

Anda dapat melakukan ini sekarang dan tidak memerlukan banyak kode refleksi.

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

Tentu saja jika Anda perlu memanggil konstruktor yang akan memerlukan beberapa refleksi, tetapi itu didokumentasikan dengan sangat baik, trik ini tidak!

Berikut ini adalah JavaDoc untuk TypeToken .

Lukasz Stelmach
sumber
6
Solusi ini berfungsi untuk sekumpulan kasus terbatas, seperti halnya jawaban @ noah dengan refleksi. Saya mencoba semuanya hari ini ... Dan saya berakhir dengan mengirimkan instance dari kelas parameter ke kelas parameter (agar dapat memanggil .newInstance ()). Kekurangan "generik" yang sangat besar ... Foo baru <Bar> (Bar.class); ... kelas Foo <T> {kelas akhir pribadi <T> mTFactory; Foo (Kelas <T> tClass) {mTFactory = tClass; ...} T instance = tFactory.newInstance (); }
yvolk
ini berfungsi dalam semua kasus, bahkan metode pabrik statis yang mengambil parameter umum
13

Pikirkan tentang pendekatan yang lebih fungsional: alih-alih membuat beberapa E dari ketiadaan (yang jelas merupakan bau kode), berikan fungsi yang tahu cara membuatnya, yaitu

E createContents(Callable<E> makeone) {
     return makeone.call(); // most simple case clearly not that useful
}
Ingo
sumber
6
Secara teknis Anda tidak melewati fungsi, Anda melewati objek fungsi (juga dikenal sebagai functor ).
Lorne Laliberte
3
Atau mungkin untuk mengatasi penangkapan Exceptiondigunakan Supplier<E>sebagai gantinya.
Martin D
10

Dari Tutorial Java - Pembatasan Generik :

Tidak Dapat Membuat Contoh Parameter Tipe

Anda tidak dapat membuat turunan dari parameter tipe. Misalnya, kode berikut ini menyebabkan kesalahan waktu kompilasi:

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

Sebagai solusinya, Anda bisa membuat objek parameter tipe melalui refleksi:

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

Anda dapat menggunakan metode append sebagai berikut:

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

Berikut adalah opsi yang saya buat, mungkin membantu:

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: Atau Anda dapat menggunakan konstruktor ini (tetapi membutuhkan instance E):

@SuppressWarnings("unchecked")
public Container(E instance) {
    this.clazz = (Class<E>) instance.getClass();
}
Mike Stone
sumber
Ya, ini bekerja sama bahkan tanpa obat generik - dengan obat generik wadah ini menjadi agak berlebihan (Anda harus menentukan apa "E" dua kali).
David Citron
baik, itulah yang terjadi ketika Anda menggunakan Java dan obat generik ... mereka tidak cantik, dan ada batasan parah ...
Mike Stone
6

Jika Anda tidak ingin mengetikkan nama kelas dua kali selama instantiation seperti di:

new SomeContainer<SomeType>(SomeType.class);

Anda dapat menggunakan metode pabrik:

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

Seperti di:

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.
sumber
6

Sayangnya Java tidak mengizinkan apa yang ingin Anda lakukan. Lihat solusi resmi :

Anda tidak dapat membuat turunan dari parameter tipe. Misalnya, kode berikut ini menyebabkan kesalahan waktu kompilasi:

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

Sebagai solusinya, Anda bisa membuat objek parameter tipe melalui refleksi:

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

Anda dapat menggunakan metode append sebagai berikut:

List<String> ls = new ArrayList<>();
append(ls, String.class);
Neepsnikeep
sumber
Bisakah Anda memberi tahu saya mengapa Anda downvoting saat melakukannya? Saya tidak melihat mengapa solusi resmi adalah solusi yang buruk. Terima kasih.
Neepsnikeep
Saya kira Anda mendapatkan suara, karena jawaban Anda pada dasarnya sama dengan Justin Rudd: stackoverflow.com/a/75254/103412
Torsten
5

Ketika Anda bekerja dengan E pada waktu kompilasi, Anda tidak terlalu peduli dengan tipe generik "E" yang sebenarnya (baik Anda menggunakan refleksi atau bekerja dengan kelas dasar dari tipe generik) jadi biarkan subclass memberikan instance 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
sumber
4

Kamu bisa menggunakan:

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

Tetapi Anda perlu memberikan nama kelas yang tepat, termasuk paket, misalnya. java.io.FileInputStream. Saya menggunakan ini untuk membuat parser ekspresi matematika.

hichris123
sumber
15
Dan bagaimana Anda mendapatkan nama kelas yang tepat dari tipe generik saat runtime?
David Citron
Anda harus menyimpannya menggunakan instance kelas itu. Dapat dilakukan, meskipun sulit. Jika generik Anda memiliki anggota tipe E (atau T atau apa pun), mendapatkan nama biner itu adil foo.getClass().getName(). Dari mana contoh itu berasal? Saya saat ini menyerahkan satu ke konstruktor dalam proyek yang sedang saya kerjakan.
Mark Storer
3

Semoga ini belum terlambat untuk membantu !!!

Java adalah tipe-safety, hanya Object yang bisa membuat instance.

Dalam kasus saya, saya tidak bisa meneruskan parameter ke createContentsmetode. Solusi saya menggunakan ekstensi bukan semua jawaban di bawah.

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

Ini contoh kasus saya yang saya tidak bisa melewati parameter.

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

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

Menggunakan refleksi membuat galat run time, jika Anda memperluas kelas generik Anda tanpa tipe objek. Untuk memperluas tipe generik Anda ke objek, ubah kesalahan ini menjadi kompilasi kesalahan waktu.

Se Song
sumber
3

Gunakan TypeToken<T>kelas:

public class MyClass<T> {
    public T doSomething() {
        return (T) new TypeToken<T>(){}.getRawType().newInstance();
    }
}
cacheoff
sumber
Jika Anda menggunakan Guava, bukan GSON, itu sedikit berbeda:(T) new TypeToken<T>(getClass()){}.getRawType().newInstance();
Jacob van Lingen
2

Saya pikir saya bisa melakukan itu, tetapi cukup kecewa: itu tidak berhasil, tetapi saya pikir itu masih layak untuk dibagikan.

Mungkin seseorang dapat memperbaiki:

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

    }
}

Itu menghasilkan:

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)

Jalur 26 adalah satu dengan [*] .

Satu-satunya solusi yang layak adalah solusi oleh @JustinRudd

Luigi R. Viggiano
sumber
2

Sebuah jawaban penting dari jawaban Nuh.

Alasan untuk berubah

Sebuah] aman jika lebih dari 1 jenis generik digunakan jika Anda mengubah urutan.

b] Tanda tangan tipe generik kelas berubah dari waktu ke waktu sehingga Anda tidak akan terkejut dengan pengecualian yang tidak dijelaskan dalam runtime.

Kode yang Kuat

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

Atau gunakan liner satu

Kode Satu Baris

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

apa yang bisa kamu lakukan adalah -

  1. Pertama mendeklarasikan variabel kelas generik itu

    2.Kemudian buatlah konstruktor dan buat objek itu

  2. Kemudian gunakan di mana pun Anda ingin menggunakannya

contoh-

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

Sudhanshu Jain
sumber
0

Seperti yang Anda katakan, Anda tidak dapat benar-benar melakukannya karena penghapusan tipe. Anda dapat mengurutkannya menggunakan refleksi, tetapi membutuhkan banyak kode dan banyak penanganan kesalahan.

Adam Rosenfield
sumber
Bagaimana Anda melakukannya dengan menggunakan refleksi? Satu-satunya metode yang saya lihat adalah Class.getTypeParameters (), tetapi itu hanya mengembalikan tipe yang dideklarasikan, bukan tipe run-time.
David Citron
Apakah kamu berbicara tentang ini? artima.com/weblogs/viewpost.jsp?thread=208860
David Citron
0

Jika Anda maksud new E() maka itu tidak mungkin. Dan saya akan menambahkan bahwa itu tidak selalu benar - bagaimana Anda tahu jika E memiliki konstruktor tanpa argumen publik? Tetapi Anda selalu dapat mendelegasikan kreasi ke beberapa kelas lain yang tahu cara membuat instance - bisa Class<E>atau kode kustom Anda seperti ini

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

class IntegerFactory implements Factory<Integer>{    
  private static int i = 0; 
  Integer create() {        
    return i++;    
  }
}
Pavel Feldman
sumber
0
return   (E)((Class)((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();
Rachid
sumber
1
Ini tidak berfungsi dalam contoh saya di pertanyaan awal. Superclass SomeContainersederhana Object. Oleh karena itu, this.getClass().getGenericSuperclass()mengembalikan Class(kelas java.lang.Object), bukan a ParameterizedType. Ini sebenarnya sudah ditunjukkan oleh stackoverflow.com/questions/75175/… pertanyaan rekan juga.
David Citron
1
Benar-benar salah: Pengecualian di utas "utama" java.lang.ClassCastException: java.lang.Class tidak dapat dilemparkan ke java.lang.reflect.ParameterizedType
Aubin
0

Anda dapat mencapai ini dengan cuplikan berikut:

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
sumber
4
Benar-benar salah: Pengecualian di utas "utama" java.lang.ClassCastException: java.lang.Class tidak dapat dilemparkan ke java.lang.reflect.ParameterizedType
Aubin
0

Ada berbagai perpustakaan yang dapat menyelesaikan Euntuk Anda menggunakan teknik yang mirip dengan apa yang dibahas artikel Robertson. Inilah implementasi createContentsyang menggunakan TypeTools untuk menyelesaikan kelas mentah yang diwakili oleh E:

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

Ini mengasumsikan bahwa getClass () memutuskan ke subkelas SomeContainer dan akan gagal jika tidak karena nilai parameter aktual E akan dihapus saat runtime jika tidak ditangkap dalam subkelas.

Jonathan
sumber
0

Berikut ini adalah implementasi dari createContentsyang menggunakan TypeTools untuk menyelesaikan kelas mentah yang diwakili oleh E:

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

Pendekatan ini hanya berfungsi jika SomeContainerdisubklasifikasikan sehingga nilai aktual Editangkap dalam definisi tipe:

class SomeStringContainer extends SomeContainer<String>

Kalau tidak, nilai E dihapus saat runtime dan tidak dapat dipulihkan.

Jonathan
sumber
-1

Anda bisa dengan classloader dan nama kelas, akhirnya beberapa parameter.

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
sumber
ini lebih buruk daripada hanya melewati objek kelas
newacct
Anda tidak perlu secara eksplisit merujuk pemuat kelas sama sekali.
Stefan Reich
-1

Berikut adalah solusi yang ditingkatkan, berdasarkan ParameterizedType.getActualTypeArguments, sudah disebutkan oleh @noah, @Lars Bohl, dan beberapa lainnya.

Peningkatan kecil pertama dalam implementasi. Pabrik tidak boleh mengembalikan instance, tetapi tipe. Segera setelah Anda kembali contoh menggunakan Class.newInstance()Anda mengurangi ruang lingkup penggunaan. Karena hanya konstruktor tanpa argumen yang dapat dipanggil seperti ini. Cara yang lebih baik adalah mengembalikan tipe, dan mengizinkan klien untuk memilih, konstruktor mana yang ingin dia panggil:

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

  }
}

Berikut adalah contoh penggunaannya. @ Lars Bohl hanya menunjukkan cara signe untuk mendapatkan genenerik terverifikasi melalui ekstensi. @noah hanya melalui membuat instance dengan {}. Berikut adalah tes untuk menunjukkan kedua kasus:

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

Catatan: Anda dapat memaksa klien TypeReferenceselalu digunakan {}ketika misalnya dibuat dengan membuat kelas ini abstrak: public abstract class TypeReference<T>. Saya belum melakukannya, hanya untuk menunjukkan test case terhapus.

Alexandr
sumber