it-swarm-vi.tech

Tạo thể hiện của kiểu chung trong Java?

Có thể tạo một thể hiện của một loại chung trong Java không? Tôi đang suy nghĩ dựa trên những gì tôi đã thấy rằng câu trả lời là no (do loại xóa), nhưng tôi sẽ quan tâm nếu ai đó có thể thấy thứ gì đó tôi đang thiếu:

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

EDIT: Hóa ra Super Type Tokens có thể được sử dụng để giải quyết vấn đề của tôi, nhưng nó đòi hỏi rất nhiều mã dựa trên phản ánh, như một số câu trả lời dưới đây đã chỉ ra.

Tôi sẽ để nó mở trong một thời gian ngắn để xem liệu có ai nghĩ ra bất cứ điều gì khác biệt đáng kể so với bài viết Artima của Ian Robertson .

525
David Citron

Bạn nói đúng. Bạn không thể làm new E(). Nhưng bạn có thể thay đổi nó thành

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

Đó là một nỗi đau. Nhưng nó đã có tác dụng. Gói nó trong mô hình nhà máy làm cho nó dễ chịu hơn một chút.

305
Justin Rudd

Nếu điều này có ích, nhưng khi bạn phân lớp (bao gồm ẩn danh) một loại chung, thông tin loại có sẵn thông qua sự phản chiếu. ví dụ.,

public abstract class Foo<E> {

  public E instance;  

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

}

Vì vậy, khi bạn phân lớp Foo, bạn sẽ có một phiên bản của Bar, ví dụ:

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

Nhưng đó là rất nhiều công việc và chỉ hoạt động cho các lớp con. Có thể tiện dụng mặc dù.

121
noah

Bạn sẽ cần một số loại nhà máy trừu tượng loại này hoặc loại khác để vượt qua:

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

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

Trong Java 8, bạn có thể sử dụng giao diện Supplier để đạt được điều này khá dễ dàng:

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

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

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

Bạn sẽ xây dựng lớp này như thế này:

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

Cú pháp String::new trên dòng đó là tham chiếu hàm tạo .

Nếu hàm tạo của bạn nhận các đối số, bạn có thể sử dụng biểu thức lambda thay thế:

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

Nếu bạn cần một thể hiện mới của một đối số kiểu bên trong một lớp chung thì hãy tạo các hàm tạo của bạn yêu cầu lớp của nó ...

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

Sử dụng:

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

Ưu điểm:

  • Đơn giản hơn nhiều (và ít vấn đề hơn) so với cách tiếp cận Super Type Token (STT) của Robertson.
  • Hiệu quả hơn nhiều so với phương pháp STT (sẽ ăn điện thoại di động của bạn cho bữa sáng).

Nhược điểm:

  • Không thể chuyển Class cho một hàm tạo mặc định (đó là lý do tại sao Foo là cuối cùng). Nếu bạn thực sự cần một hàm tạo mặc định, bạn luôn có thể thêm phương thức setter nhưng sau đó bạn phải nhớ gọi cho cô ấy sau.
  • Sự phản đối của Robertson ... Nhiều thanh hơn một con cừu đen (mặc dù chỉ định lớp đối số loại một lần nữa sẽ không chính xác giết bạn). Và ngược lại với tuyên bố của Robertson, điều này không vi phạm hiệu trưởng DRY vì trình biên dịch sẽ đảm bảo tính chính xác của kiểu.
  • Không hoàn toàn Foo<L>proof. Đối với người mới bắt đầu ... newInstance() sẽ ném wobbler nếu lớp đối số loại không có hàm tạo mặc định. Điều này không áp dụng cho tất cả các giải pháp được biết mặc dù.
  • Thiếu tổng số đóng gói của phương pháp STT. Không phải là một vấn đề lớn mặc dù (xem xét chi phí hoạt động thái quá của STT).
20
R2D2M2

Bạn có thể làm điều này ngay bây giờ và nó không yêu cầu một loạt mã phản chiếu.

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

Tất nhiên, nếu bạn cần gọi hàm tạo sẽ yêu cầu một số phản xạ, nhưng đó là tài liệu rất tốt, thủ thuật này không phải là!

Đây là JavaDoc cho TypeToken .

19
user177800

Hãy suy nghĩ về một cách tiếp cận chức năng hơn: thay vì tạo ra một số E từ không có gì (rõ ràng là mùi mã), hãy truyền một hàm biết cách tạo một, tức là.

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

Từ Hướng dẫn Java - Hạn chế về Generics :

Không thể tạo phiên bản của tham số loại

Bạn không thể tạo một thể hiện của một tham số loại. Ví dụ: đoạn mã sau gây ra lỗi thời gian biên dịch:

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

Như một giải pháp thay thế, bạn có thể tạo một đối tượng của tham số loại thông qua sự phản chiếu:

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

Bạn có thể gọi phương thức chắp thêm như sau:

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

Đây là một tùy chọn tôi đã đưa ra, nó có thể giúp:

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: Ngoài ra, bạn có thể sử dụng hàm tạo này (nhưng nó yêu cầu một thể hiện của E):

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

Nếu bạn không muốn gõ tên lớp hai lần trong khi khởi tạo như trong:

new SomeContainer<SomeType>(SomeType.class);

Bạn có thể sử dụng phương pháp nhà máy:

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

Giống như trong:

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.

Đáng tiếc Java không cho phép những gì bạn muốn làm. Xem cách giải quyết chính thức :

Bạn không thể tạo một thể hiện của một tham số loại. Ví dụ: đoạn mã sau gây ra lỗi thời gian biên dịch:

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

Như một giải pháp thay thế, bạn có thể tạo một đối tượng của tham số loại thông qua sự phản chiếu:

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

Bạn có thể gọi phương thức chắp thêm như sau:

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

Khi bạn đang làm việc với E vào thời gian biên dịch, bạn không thực sự quan tâm loại chung chung "E" (bạn sử dụng sự phản chiếu hoặc làm việc với lớp cơ sở của loại chung), vì vậy hãy để lớp con cung cấp thể hiện của 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

Sử dụng lớp TypeToken<T> :

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

Bạn có thể dùng:

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

Nhưng bạn cần cung cấp tên lớp chính xác, bao gồm các gói, vd. Java.io.FileInputStream. Tôi đã sử dụng điều này để tạo một trình phân tích cú pháp biểu thức toán học.

3
Jaroslav Smid

Một sự quan trọng của câu trả lời @ Nô-ê. 

Lý do cho sự thay đổi

a] An toàn hơn nếu sử dụng nhiều hơn 1 loại chung trong trường hợp bạn thay đổi thứ tự.

b] Chữ ký loại chung loại thay đổi theo thời gian để bạn sẽ không ngạc nhiên bởi các ngoại lệ không giải thích được trong thời gian chạy.

Mã mạnh mẽ

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

Hoặc sử dụng một lớp lót

Mã một dòng

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

Tôi nghĩ rằng tôi có thể làm điều đó, nhưng khá thất vọng: nó không hoạt động, nhưng tôi nghĩ nó vẫn đáng để chia sẻ. 

Có lẽ ai đó có thể sửa:

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

    }
}

Nó tạo ra:

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)

Dòng 26 là dòng có [*].

Giải pháp khả thi duy nhất là giải pháp của @JustinRudd

2
Luigi R. Viggiano

Có nhiều thư viện khác nhau có thể giải quyết E cho bạn bằng cách sử dụng các kỹ thuật tương tự như những gì bài báo của Robertson đã thảo luận. Đây là một hàm của createContents sử dụng TypeTools để giải quyết lớp thô được đại diện bởi E:

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

Điều này giả định rằng getClass () phân giải thành một lớp con của SomethingContainer và sẽ thất bại vì giá trị tham số thực tế của E sẽ bị xóa trong thời gian chạy nếu nó không bị bắt trong lớp con.

0
Jonathan

Nếu bạn có nghĩa là new E() Thì không thể. Và tôi sẽ nói thêm rằng điều đó không phải lúc nào cũng đúng - làm sao bạn biết nếu E có hàm tạo không có đối số công khai? Nhưng bạn luôn có thể ủy quyền tạo cho một số lớp khác biết cách tạo một cá thể - đó có thể là Class<E> hoặc mã tùy chỉnh của bạn như thế này

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

Bạn có thể đạt được điều này với đoạn mã sau:

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

Đây là một triển khai của createContents sử dụng TypeTools để giải quyết lớp thô được đại diện bởi E:

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

Cách tiếp cận này chỉ hoạt động nếu SomeContainer được phân lớp để giá trị thực của E được ghi lại trong một định nghĩa loại:

class SomeStringContainer extends SomeContainer<String>

Mặt khác, giá trị của E bị xóa trong thời gian chạy và không thể phục hồi.

0
Jonathan

những gì bạn có thể làm là -

  1. Đầu tiên khai báo biến của lớp chung đó

    2.Sau đó tạo một hàm tạo của nó và khởi tạo đối tượng đó

  2. Sau đó sử dụng nó bất cứ nơi nào bạn muốn sử dụng nó

thí dụ-

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 (thực thể);

0
Sudhanshu Jain

Như bạn đã nói, bạn thực sự không thể làm điều đó vì kiểu xóa. Bạn có thể sắp xếp thực hiện bằng cách sử dụng sự phản chiếu, nhưng nó đòi hỏi rất nhiều mã và xử lý lỗi.

0
Adam Rosenfield

Hy vọng điều này không quá muộn để giúp đỡ !!!

Java là loại an toàn, chỉ có Object là có thể tạo cá thể.

Trong trường hợp của tôi, tôi không thể truyền tham số cho phương thức createContents. Giải pháp của tôi là sử dụng mở rộng thay vì tất cả các câu trả lời dưới đây.

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

Đây là trường hợp ví dụ của tôi mà tôi không thể truyền tham số.

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

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

Sử dụng sự phản chiếu tạo ra lỗi thời gian chạy, nếu bạn mở rộng lớp chung của mình mà không có loại đối tượng nào. Để mở rộng loại chung của bạn thành đối tượng chuyển đổi lỗi này thành lỗi thời gian biên dịch. 

0
Se Song

Đây là một giải pháp cải tiến, dựa trên ParameterizedType.getActualTypeArguments, đã được đề cập bởi @noah, @Lars Bohl và một số người khác. 

Cải tiến nhỏ đầu tiên trong việc thực hiện. Nhà máy không nên trả lại ví dụ, nhưng một loại. Ngay sau khi bạn trả lại ví dụ bằng cách sử dụng Class.newInstance(), bạn sẽ giảm phạm vi sử dụng. Bởi vì chỉ có các hàm tạo không có đối số mới có thể được gọi như thế này. Cách tốt hơn là trả về một kiểu và cho phép khách hàng chọn, hàm tạo mà anh ta muốn gọi:

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

  }
}

Dưới đây là một ví dụ sử dụng. @Lars Bohl chỉ hiển thị một cách đăng nhập để có được sự thống nhất về gen thông qua phần mở rộng. @noah chỉ thông qua việc tạo một cá thể với {}. Dưới đây là các xét nghiệm để chứng minh cả hai trường hợp:

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

Lưu ý: bạn có thể buộc các máy khách của TypeReference luôn sử dụng {} khi cá thể được tạo bằng cách làm cho lớp này trừu tượng: public abstract class TypeReference<T>. Tôi đã không làm điều đó, chỉ để hiển thị trường hợp thử nghiệm bị xóa. 

0
Alexandr