it-swarm-id.com

Buat instance dari tipe generik di Jawa?

Apakah mungkin membuat instance tipe generik di Jawa? Saya berpikir berdasarkan pada 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 Super Type dapat digunakan untuk menyelesaikan masalah saya, tetapi memerlukan banyak kode berbasis refleksi, seperti yang ditunjukkan oleh beberapa jawaban di bawah ini.

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

525
David Citron

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

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

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

121
noah

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

Di Java 8 Anda dapat menggunakan antarmuka fungsional Supplier untuk mencapai hal ini dengan 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::new pada 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));
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

Jika Anda membutuhkan 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).

Kekurangan:

  • 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 domba hitam (meskipun menentukan kelas argumen tipe sekali lagi tidak akan benar-benar membunuh Anda). Dan bertentangan dengan klaim Robertson, ini tidak melanggar prinsipal DRY karena kompiler akan memastikan ketepatan jenis.
  • Tidak sepenuhnya Foo<L>proof. 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).
20
R2D2M2

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!

Ini adalah JavaDoc untuk TypeToken .

19
user177800

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

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

Dari Tutorial Java - Pembatasan Generik :

Tidak Dapat Membuat Contoh Tipe Parameter

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);
8
Sergiy Sokolenko

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

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

}
6
jb.

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

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

Kamu bisa memakai:

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.

3
Jaroslav Smid

Suatu keharusan dari jawaban Nuh. 

Alasan untuk berubah

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

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

Kode 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();
2
Amio.io

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)

Baris 26 adalah baris dengan [*].

Satu-satunya solusi yang layak adalah solusi oleh @JustinRudd

2

Ada berbagai perpustakaan yang dapat menyelesaikan E untuk Anda menggunakan teknik yang mirip dengan apa yang dibahas artikel Robertson. Berikut adalah implementasi dari createContents yang 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 () menyelesaikan ke subkelas SomeContainer dan akan gagal jika tidak karena nilai parameter aktual E akan dihapus saat runtime jika tidak ditangkap dalam subkelas.

0
Jonathan

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 penciptaan ke beberapa kelas lain yang tahu cara membuat contoh - bisa Class<E> atau kode khusus Anda seperti ini

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

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

Berikut ini adalah implementasi dari createContents yang 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 SomeContainer disubklasifikasi sehingga nilai aktual E ditangkap dalam definisi tipe:

class SomeStringContainer extends SomeContainer<String>

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

0
Jonathan

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.

0
Adam Rosenfield

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 metode createContents. 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. 

0
Se Song

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 mengembalikan instance 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 ia 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 TypeReference selalu menggunakan {} ketika instance dibuat dengan membuat kelas ini abstrak: public abstract class TypeReference<T>. Saya belum melakukannya, hanya untuk menunjukkan test case terhapus. 

0
Alexandr