Tạo thể hiện của loại chung trong Java?


576

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 có ai có thể thấy thứ gì đó tôi đang thiếu:

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

EDIT: Hóa ra là Token Super Type 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 xạ, như một số câu trả lời dưới đây đã chỉ ra.

Tôi sẽ để điều này mở ra một chút để xem liệu có ai nghĩ ra điều gì khác biệt đáng kể so với Điều Artima của Ian Robertson không .


2
Chỉ cần thử nghiệm hiệu năng trên thiết bị Android. 10000 thao tác và: 8-9 ms mất một số NewClass (), 9-11 ms mất Factory <someClass> .createInstance () và 64-71 ms mất phản xạ ngắn nhất: someClass z = someClass. Class.newInstance (). Và tất cả các bài kiểm tra là trong một khối thử bắt đơn. Reflection newInstance () đưa ra 4 ngoại lệ khác nhau, nhớ không? Vì vậy, tôi quyết định sử dụng mô hình nhà máy
Deepscorn


4
Với Java 8, bây giờ bạn có thể chuyển một tham chiếu hàm tạo hoặc lambda, điều này làm cho vấn đề này trở nên khá nhỏ để giải quyết. Xem câu trả lời của tôi dưới đây để biết chi tiết.
Daniel Pryden 30/03/2016

Tôi nghĩ rằng đây là ý tưởng tồi để viết mã như vậy, là những cách thanh lịch và dễ đọc hơn để giải quyết vấn đề bên dưới.
Krzysztof Cichocki

1
@DavidCitron "trong một thời gian ngắn" , ông nói ... Đã mười một năm kể từ đó ...
MC Hoàng đế

Câu trả lời:


332

Bạn nói đúng. Bạn không thể làm được 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.


11
Vâng, tôi đã thấy giải pháp đó, nhưng nó chỉ hoạt động nếu bạn đã có một tham chiếu đến một đối tượng Class thuộc loại mà bạn muốn khởi tạo.
David Citron

11
Vâng, tôi biết. Sẽ thật tuyệt nếu bạn có thể làm E. class nhưng điều đó chỉ đơn giản mang lại cho bạn Object. Class vì bị xóa :)
Justin Rudd

6
Đó là cách tiếp cận đúng cho vấn đề này. Nó thường không phải là những gì bạn muốn, nhưng đó là những gì bạn nhận được.
Joachim Sauer

38
Và làm thế nào để bạn gọi phương thức createContents ()?
Alexis Dufrenoy

8
Đây không còn là cách duy nhất để làm điều này, có một cách tốt hơn bây giờ là không yêu cầu chuyển Class<?>tham chiếu bằng Guava và TypeToken, xem câu trả lời này cho mã và liên kết!

129

Nếu điều này có ích, nhưng khi bạn phân lớp (bao gồm cả ẩ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 nhận được một thể hiện của Bar, vd

// 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ù.


2
Vâng, điều này thật tuyệt vời đặc biệt nếu lớp chung là trừu tượng, bạn có thể thực hiện điều này trong các lớp con cụ thể :)
Pierre Henry

Phương thức này cũng hoạt động nếu lớp Fookhông trừu tượng. Nhưng tại sao nó chỉ hoạt động trên các lớp con ẩn danh của Foo? Giả sử chúng ta làm Foobê tông (chúng ta bỏ đi abstract), tại sao sẽ new Foo<Bar>();dẫn đến một lỗi, trong khi new Foo<Bar>(){};không? (Ngoại lệ: "Lớp không thể được chuyển thành ParameterizedType")
Tim Kuipers

2
@TimKuipers Các <E>trong class Foo<E>không bị ràng buộc với bất kỳ loại cụ thể. Bạn sẽ thấy những hành vi đặc biệt khi Ekhông được tĩnh ràng buộc, như trong: new Foo<Bar>(), new Foo<T>() {...}, hoặc class Fizz <E> extends Foo<E>. Trường hợp đầu tiên không bị ràng buộc tĩnh, nó bị xóa tại thời điểm biên dịch. Trường hợp thứ hai thay thế một loại biến khác (T) thay cho Enhưng vẫn không bị ràng buộc. Và trong trường hợp cuối cùng, rõ ràng Elà vẫn không bị ràng buộc.
William Giá

4
Một ví dụ về liên kết tĩnh tham số loại sẽ là class Fizz extends Foo<Bar>- trong trường hợp này, người dùng Fizznhận được một cái gì đó là Foo<Bar>và không thể là bất cứ thứ gì ngoài a Foo<Bar>. Vì vậy, trong trường hợp này, trình biên dịch rất vui khi mã hóa thông tin đó vào siêu dữ liệu của lớp Fizzvà làm cho nó có sẵn dưới dạng ParameterizedTypemã phản chiếu. Khi bạn tạo một lớp bên trong ẩn danh giống như new Foo<Bar>() {...}nó đang làm điều tương tự, ngoại trừ thay vì Fizztrình biên dịch tạo ra một tên lớp "ẩn danh" mà bạn sẽ không biết cho đến khi lớp bên ngoài được biên dịch.
William Giá

4
Cần lưu ý rằng điều này sẽ không hoạt động nếu các đối số loại cũng là ParameterizedType. Ví dụ , Foo<Bar<Baz>>. Bạn sẽ tạo một thể hiện trong ParameterizedTypeImplđó không thể được tạo rõ ràng. Do đó, nên kiểm tra xem getActualTypeArguments()[0]có trở lại không a ParameterizedType. Nếu có, thì bạn muốn lấy kiểu thô và tạo một thể hiện của điều đó thay vào đó.
nghiền nát

106

Trong Java 8, bạn có thể sử dụng Suppliergiao diện chức năng để đạ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::newtrên dòng đó là một 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));

6
Tốt một. Nó tránh sự phản ánh và phải điều trị ngoại lệ.
Bruno Leite

2
Thật tốt. Thật không may cho người dùng Android, điều này yêu cầu API cấp 24 trở lên.
Michael Updike

1
Theo cách tiếp cận ít ồn ào và chức năng hơn, điều này sẽ được chấp nhận câu trả lời theo ý kiến ​​của tôi
Tomas

2
Câu trả lời và nó không khác với câu trả lời thậm chí cũ hơn này cho thấy mô hình kỹ thuật đằng sau nó thậm chí còn cũ hơn cả sự hỗ trợ của Java cho các biểu thức lambda và các tham chiếu phương thức trong khi bạn thậm chí có thể sử dụng mã cũ hơn với chúng khi bạn nâng cấp trình biên dịch của mình
Holger

Nó sẽ có thể đặt chỉ SomeContainer stringContainer = new SomeContainer(String::new);?
Aaron Franke

87

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

..và Factory.create () trông như thế nào?
OhadR

6
@OhadR Factory<>là một giao diện và vì vậy không có phần thân. Vấn đề là bạn cần một lớp các hướng dẫn để truyền buck cho các phương thức "biết" mã cần thiết để xây dựng một thể hiện. Làm điều này tốt hơn nhiều với mã thông thường thay vì ngôn ngữ kim loại Classhoặc Constructornhư sự phản chiếu mang lại cả một thế giới bị tổn thương.
Tom Hawtin - tackline

2
Ngày nay, bạn có thể tạo một cá thể nhà máy với một biểu thức tham chiếu phương thức như sau: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
Cách tiếp cận tốt bởi mã này có thể gây ra ClassCastException nếu Bạn sử dụng loại chung chung. Sau đó, bạn truy xuất lại đối số factType Bạn nên kiểm tra xem đó cũng là ParamterizedType và nếu vậy hãy trả về RawType của anh ấy (hoặc một cái gì đó tốt hơn cái này). Một vấn đề khác với điều này là khi chúng tôi mở rộng nhiều hơn thì một khi mã này cũng sẽ ném ClassCastExellect.
Damian Leszczyński - Vash

3
Gây ra bởi: java.lang.ClassCastException: sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl không thể được đúc để java.lang.Class
juan

@ DamianLeszczyński-Vash cũng sẽ thất bại với, ví dụclass GenericHome<T> extends Home<T>{}
Holger

22

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 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>bằng chứng. Đố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).

22

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 .


6
Giải pháp này hoạt động cho một tập hợp các trường hợp giới hạn, giống như câu trả lời của @ noah với sự phản ánh. Tôi đã thử tất cả chúng ngày hôm nay ... Và tôi đã kết thúc bằng cách chuyển một thể hiện của lớp tham số sang lớp được tham số hóa (để có thể gọi .newInstance ()). Sự thiếu hụt rất lớn về "thuốc generic" ... Foo <Bar> mới (Bar. Class); ... lớp Foo <T> {Lớp cuối cùng riêng tư <T> mTFactory; Foo (Lớp <T> tClass) {mTFactory = tClass; ...} T dụ = tFactory.newInstance (); }
yvol

điều này hoạt động trong mọi trường hợp, ngay cả các phương thức nhà máy tĩnh có tham số chung

13

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

6
Về mặt kỹ thuật, bạn không truyền một hàm, bạn đang truyền một đối tượng hàm (còn được gọi là functor ).
Lorne Laliberte

3
Hoặc có thể để khắc phục Exceptionsử dụng bắt Supplier<E>thay thế.
Martin D

10

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

Không thể tạo trường hợp 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);

6

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

Vâng, điều này hoạt động như nhau ngay cả khi không có thuốc generic - với thuốc generic, việc khởi tạo của thùng chứa này trở nên hơi dư thừa (bạn phải xác định "E" là gì hai lần).
David Citron

tốt, đó là những gì xảy ra khi bạn sử dụng Java và generic ... chúng không đẹp và có những hạn chế nghiêm trọng ...
Mike Stone

6

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

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

Bạn có thể vui lòng cho tôi biết lý do tại sao bạn xuống cấp khi bạn làm như vậy? Tôi không thấy lý do tại sao cách giải quyết chính thức là một giải pháp tồi. Cảm ơn.
Neepsnikeep

Tôi đoán bạn sẽ bỏ phiếu, vì câu trả lời của bạn về cơ bản giống như của Justin Rudd: stackoverflow.com/a/75254/103412
Torsten

5

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 phản xạ 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

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.


15
Và làm thế nào để bạn có được tên lớp chính xác của loại chung trong thời gian chạy?
David Citron

Bạn sẽ phải lưu nó bằng cách sử dụng một thể hiện của lớp đó. Có thể làm được, mặc dù hầu như không thuận tiện. Nếu chung của bạn có một thành viên loại E (hoặc T hoặc bất cứ điều gì), nhận được tên nhị phân của nó chỉ là foo.getClass().getName(). Trường hợp THAT đến từ đâu? Tôi hiện đang chuyển một cho một nhà xây dựng trong dự án mà tôi hiện đang làm việc.
Mark Storer

3

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 createContentsphương thức. 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.


3

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

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

Nếu bạn sử dụng Guava thay vì GSON, thì sẽ hơi khác một chút:(T) new TypeToken<T>(getClass()){}.getRawType().newInstance();
Jacob van Lingen

2

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

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

Lý do cho sự thay đổi

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

b] Một chữ ký loại chung chung 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

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

Như bạn đã nói, bạn không thể thực sự làm điều đó vì xóa kiểu. 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.


Làm thế nào bạn thậm chí sẽ làm điều đó bằng cách sử dụng sự phản chiếu? Phương thức duy nhất tôi thấy là Class.getTypeParameter (), nhưng chỉ trả về các kiểu khai báo, không phải các kiểu thời gian chạy.
David Citron

Bạn đang nói về điều này? artima.com/weblogs/viewpost.jsp?thread=208860
David Citron

0

Nếu bạn có ý 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ô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 thể hiện - nó có thể 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
return   (E)((Class)((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();

1
Điều này không hoạt động trong ví dụ của tôi trong câu hỏi ban đầu. Các siêu lớp cho SomeContainerđơn giản Object. Do đó, this.getClass().getGenericSuperclass()trả về một Class(lớp java.lang.Object), không phải a ParameterizedType. Điều này thực sự đã được chỉ ra bởi stackoverflow.com/questions/75175/ trên .
David Citron

1
Hoàn toàn sai: Ngoại lệ trong luồng "chính" java.lang.ClassCastException: java.lang.Class không thể được chuyển sang java.lang.reflect.ParameterizedType
Aubin

0

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

4
Hoàn toàn sai: Ngoại lệ trong luồng "chính" java.lang.ClassCastException: java.lang.Class không thể được chuyển sang java.lang.reflect.ParameterizedType
Aubin

0

Có nhiều thư viện khác nhau có thể giải quyết Echo 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 triển khai createContentssử 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 một lớp con.


0

Đây là một triển khai createContentssử 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 kiểu:

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.


-1

Bạn có thể với một trình nạp lớp và tên lớp, cuối cùng là một số tham số.

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

điều này còn tệ hơn cả việc vượt qua đối tượng lớp
newacct

Bạn hoàn toàn không cần tham chiếu trình nạp lớp.
Stefan Reich

-1

Đâ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 khi bạn trả lại cá thể 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 thể hiện 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 TypeReferenceluô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.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.