Làm thế nào để tôi có được một thể hiện của lớp T chung?


700

Tôi có một lớp học chung chung Foo<T>. Trong một phương thức Foo, tôi muốn lấy thể hiện của lớp T, nhưng tôi không thể gọi T.class.

Cách ưa thích để có được xung quanh nó bằng cách sử dụng là T.classgì?


2
Hãy thử câu trả lời trong câu hỏi này. Tôi nghĩ nó tương tự. stackoverflow.com/questions/1942644/ trộm
Emil

3
có thể trùng lặp với loại lớp chung trong thời gian chạy
7hi4g0

1
có thể trùng lặp với việc tạo một lớp chung trong Java
matiasg

1
import com.fasterxml.jackson.core.type.TypeReference; new TypeReference<T>(){}
Bogdan Shulga

Câu trả lời:


567

Câu trả lời ngắn gọn là, không có cách nào để tìm ra kiểu thời gian chạy của các tham số kiểu chung trong Java. Tôi đề nghị đọc chương về cách xóa kiểu trong Hướng dẫn Java để biết thêm chi tiết.

Một giải pháp phổ biến cho vấn đề này là chuyển Classtham số loại vào hàm tạo của kiểu chung, vd

class Foo<T> {
    final Class<T> typeParameterClass;

    public Foo(Class<T> typeParameterClass) {
        this.typeParameterClass = typeParameterClass;
    }

    public void bar() {
        // you can access the typeParameterClass here and do whatever you like
    }
}

73
Câu trả lời này không cung cấp một giải pháp hợp lệ nhưng không chính xác khi nói rằng không có cách nào để tìm loại chung trong thời gian chạy. Hóa ra loại tẩy xóa phức tạp hơn nhiều so với tẩy xóa chăn. Câu trả lời của tôi cho bạn thấy làm thế nào để có được các loại chung chung.
Ben Thurley

3
@BenThurley Thủ thuật gọn gàng, nhưng theo tôi có thể thấy nó chỉ hoạt động nếu có một siêu kiểu chung cho nó sử dụng. Trong ví dụ của tôi, bạn không thể truy xuất loại T trong Foo <T>.
Zsolt Török

@webj Racer Không, bạn không nên. Việc chỉ định typeParameterClassmà không có gán mặc định trong hàm tạo là hoàn toàn tốt. Không cần thiết phải đặt lại lần thứ hai.
Adowrath

Đây là giải pháp đầu tiên xuất hiện trong đầu, nhưng đôi khi không phải bạn sẽ là người tạo / khởi tạo các đối tượng. Vì vậy, bạn sẽ không thể sử dụng hàm tạo. Ví dụ như trong khi truy xuất các thực thể JPA từ cơ sở dữ liệu.
Paramvir Singh Karwal

233

Tôi đang tìm cách để tự làm việc này mà không cần thêm phụ thuộc vào đường dẫn lớp. Sau một cuộc điều tra tôi thấy rằng nó tốt miễn là bạn có một siêu kiểu generic. Điều này là ổn đối với tôi khi tôi đang làm việc với một lớp DAO với một siêu kiểu lớp chung. Nếu điều này phù hợp với kịch bản của bạn thì đó là cách tiếp cận gọn gàng nhất IMHO.

Hầu hết các trường hợp sử dụng thuốc generic mà tôi gặp đều có một số loại siêu kiểu chung chung, ví dụ như List<T>cho ArrayList<T>hoặc GenericDAO<T>cho DAO<T>, v.v.

Giải pháp Java thuần túy

Bài viết Truy cập các loại chung trong thời gian chạy trong Java giải thích cách bạn có thể làm điều đó bằng cách sử dụng Java thuần túy.

@SuppressWarnings("unchecked")
public GenericJpaDao() {
  this.entityBeanType = ((Class) ((ParameterizedType) getClass()
      .getGenericSuperclass()).getActualTypeArguments()[0]);
}

Giải pháp mùa xuân

Dự án của tôi đã sử dụng Spring , điều này thậm chí còn tốt hơn vì Spring có một phương thức tiện ích tiện dụng để tìm loại. Đây là cách tiếp cận tốt nhất đối với tôi vì nó trông gọn gàng nhất. Tôi đoán nếu bạn không sử dụng Spring, bạn có thể viết phương thức tiện ích của riêng mình.

import org.springframework.core.GenericTypeResolver;

public abstract class AbstractHibernateDao<T extends DomainObject> implements DataAccessObject<T>
{

    @Autowired
    private SessionFactory sessionFactory;

    private final Class<T> genericType;

    private final String RECORD_COUNT_HQL;
    private final String FIND_ALL_HQL;

    @SuppressWarnings("unchecked")
    public AbstractHibernateDao()
    {
        this.genericType = (Class<T>) GenericTypeResolver.resolveTypeArgument(getClass(), AbstractHibernateDao.class);
        this.RECORD_COUNT_HQL = "select count(*) from " + this.genericType.getName();
        this.FIND_ALL_HQL = "from " + this.genericType.getName() + " t ";
    }

1
hãy làm rõ ý nghĩa của resolveTypeArgument luận
gstackoverflow

getClass () là một phương thức của java.lang.Object sẽ trả về lớp của đối tượng cụ thể trong thời gian chạy, đây là đối tượng bạn muốn giải quyết loại cho. AbstractHibernateDao. Class chỉ là tên của lớp cơ sở hoặc siêu lớp của hệ thống phân cấp lớp chung chung. Báo cáo nhập khẩu được bao gồm để bạn có thể dễ dàng tìm thấy các tài liệu và tự kiểm tra. Đây là trang docs.spring.io/spring/docs/civerse/javadoc-api/org/ mẹo
Ben Thurley

Giải pháp lò xo với phiên bản 4.3.6 trở lên là gì. Nó không hoạt động với mùa xuân 4.3.6.
Erlan

1
Liên kết trong "Giải pháp Java thuần túy" đã bị hỏng, giờ đây là blog.xebia.com/acessing-generic-types-at-r nb
Nick Breen

1
@ AlikElzin-kilaka, nó được khởi tạo trong hàm tạo bằng lớp Spring GenericTypeResolver.
Ben Thurley

103

Tuy nhiên, có một lỗ hổng nhỏ: nếu bạn định nghĩa Foolớp của mình là trừu tượng. Điều đó có nghĩa là bạn phải khởi tạo lớp của bạn như sau:

Foo<MyType> myFoo = new Foo<MyType>(){};

(Lưu ý niềng răng đôi ở cuối.)

Bây giờ bạn có thể truy xuất loại trong Tthời gian chạy:

Type mySuperclass = myFoo.getClass().getGenericSuperclass();
Type tType = ((ParameterizedType)mySuperclass).getActualTypeArguments()[0];

Tuy nhiên, lưu ý rằng đó mySuperclassphải là siêu lớp của định nghĩa lớp thực sự xác định loại cuối cùng cho T.

Nó cũng không phải là rất thanh lịch, nhưng bạn phải quyết định xem bạn thích new Foo<MyType>(){}hay new Foo<MyType>(MyType.class);trong mã của bạn.


Ví dụ:

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

import java.util.ArrayDeque;
import java.util.Deque;
import java.util.NoSuchElementException;

/**
 * Captures and silently ignores stack exceptions upon popping.
 */
public abstract class SilentStack<E> extends ArrayDeque<E> {
  public E pop() {
    try {
      return super.pop();
    }
    catch( NoSuchElementException nsee ) {
      return create();
    }
  }

  public E create() {
    try {
      Type sooper = getClass().getGenericSuperclass();
      Type t = ((ParameterizedType)sooper).getActualTypeArguments()[ 0 ];

      return (E)(Class.forName( t.toString() ).newInstance());
    }
    catch( Exception e ) {
      return null;
    }
  }
}

Sau đó:

public class Main {
    // Note the braces...
    private Deque<String> stack = new SilentStack<String>(){};

    public static void main( String args[] ) {
      // Returns a new instance of String.
      String s = stack.pop();
      System.out.printf( "s = '%s'\n", s );
    }
}

5
Đây dễ dàng là câu trả lời tốt nhất ở đây! Ngoài ra, với giá trị của nó, đây là chiến lược mà Google Guice sử dụng cho các lớp ràng buộc vớiTypeLiteral
ATG

14
Lưu ý rằng mỗi khi phương thức xây dựng đối tượng này được sử dụng, một lớp ẩn danh mới được tạo. Nói cách khác, hai đối tượng abđược tạo theo cách này sẽ mở rộng cùng một lớp nhưng không có các lớp đối tượng giống hệt nhau. a.getClass() != b.getClass()
Martin Serrano

3
Có một kịch bản trong đó điều này không hoạt động. Nếu Foo nên triển khai một giao diện, chẳng hạn như Nối tiếp, thì lớp ẩn danh sẽ không được Nối tiếp trừ khi lớp khởi tạo là. Tôi đã cố gắng khắc phục nó bằng cách tạo một lớp nhà máy tuần tự hóa tạo ra lớp ẩn danh có nguồn gốc từ Foo, nhưng sau đó, vì một số lý do, getActualTypeArgument trả về loại chung thay vì lớp thực tế. Ví dụ: (FooFactory mới <MyType> ()). CreatedFoo ()
Lior Chaga

38

Một cách tiếp cận / giải pháp / giải pháp chuẩn là thêm một classđối tượng vào (các) hàm tạo, như:

 public class Foo<T> {

    private Class<T> type;
    public Foo(Class<T> type) {
      this.type = type;
    }

    public Class<T> getType() {
      return type;
    }

    public T newInstance() {
      return type.newInstance();
    }
 }

1
Nhưng dường như không thể @autowired trong sử dụng thực tế, có cách nào để khắc phục không?
Alfred Huang

@AlfredHuang Công việc xung quanh sẽ là tạo ra một bean cho lớp thực hiện điều này và không dựa vào tự động.
Calebj

20

Hãy tưởng tượng bạn có một siêu lớp trừu tượng chung chung:

public abstract class Foo<? extends T> {}

Và sau đó, bạn có một lớp thứ hai mở rộng Foo với một Bar chung kéo dài T:

public class Second extends Foo<Bar> {}

Bạn có thể nhận được lớp Bar.classtrong lớp Foo bằng cách chọn Type(từ câu trả lời bert bruynooghe) và suy ra nó bằng cách sử dụng Classví dụ:

Type mySuperclass = myFoo.getClass().getGenericSuperclass();
Type tType = ((ParameterizedType)mySuperclass).getActualTypeArguments()[0];
//Parse it as String
String className = tType.toString().split(" ")[1];
Class clazz = Class.forName(className);

Bạn phải lưu ý thao tác này không lý tưởng, vì vậy nên lưu trữ giá trị được tính toán để tránh nhiều phép tính trên này. Một trong những cách sử dụng điển hình là trong triển khai DAO chung.

Việc thực hiện cuối cùng:

public abstract class Foo<T> {

    private Class<T> inferedClass;

    public Class<T> getGenericClass(){
        if(inferedClass == null){
            Type mySuperclass = getClass().getGenericSuperclass();
            Type tType = ((ParameterizedType)mySuperclass).getActualTypeArguments()[0];
            String className = tType.toString().split(" ")[1];
            inferedClass = Class.forName(className);
        }
        return inferedClass;
    }
}

Giá trị được trả về là Bar. Class khi được gọi từ lớp Foo trong hàm khác hoặc từ lớp Bar.


1
toString().split(" ")[1]đó là vấn đề, hãy tránh"class "
IgniteCoders

16

Đây là một giải pháp làm việc:

@SuppressWarnings("unchecked")
private Class<T> getGenericTypeClass() {
    try {
        String className = ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0].getTypeName();
        Class<?> clazz = Class.forName(className);
        return (Class<T>) clazz;
    } catch (Exception e) {
        throw new IllegalStateException("Class is not parametrized with generic type!!! Please use extends <> ");
    }
} 

GHI CHÚ: Chỉ có thể được sử dụng như siêu lớp

  1. Phải được mở rộng với lớp đánh máy ( Child extends Generic<Integer>)

HOẶC LÀ

  1. Phải được tạo dưới dạng triển khai ẩn danh ( new Generic<Integer>() {};)

3
getTypeName gọi toString, do đó có thể được thay thế bằng .getActualTypeArgument () [0] .toString ();
Yaroslav Kovbas


9

Một tuyến đường tốt hơn Lớp mà những người khác đề xuất là truyền vào một đối tượng có thể làm những gì bạn đã làm với Lớp, ví dụ: tạo một thể hiện mới.

interface Factory<T> {
  T apply();
}

<T> void List<T> make10(Factory<T> factory) {
  List<T> result = new ArrayList<T>();
  for (int a = 0; a < 10; a++)
    result.add(factory.apply());
  return result;
}

class FooFactory<T> implements Factory<Foo<T>> {
  public Foo<T> apply() {
    return new Foo<T>();
  }
}

List<Foo<Integer>> foos = make10(new FooFactory<Integer>());

@ Ricky Clarkson: Tôi không thấy nhà máy này sẽ trả lại các foos tham số như thế nào. Bạn có thể giải thích làm thế nào bạn đưa Foo <T> ra khỏi đây không? Dường như với tôi điều này chỉ mang lại cho Foo không bị khuất phục. Không phải T trong make10 chỉ đơn giản là Foo ở đây sao?
ib84

@ ib84 Tôi đã sửa mã; Tôi dường như đã bỏ lỡ rằng Foo đã được tham số hóa khi tôi viết câu trả lời ban đầu.
Ricky Clarkson

9

Tôi đã có vấn đề này trong một lớp chung chung trừu tượng. Trong trường hợp cụ thể này, giải pháp đơn giản hơn:

abstract class Foo<T> {
    abstract Class<T> getTClass();
    //...
}

và sau này trên lớp dẫn xuất:

class Bar extends Foo<Whatever> {
    @Override
    Class<T> getTClass() {
        return Whatever.class;
    }
}

Đúng vậy, nhưng tôi muốn để lại mức tối thiểu cần phải hoàn thành trong khi mở rộng lớp này. Kiểm tra câu trả lời của droidpl
Paramvir Singh Karwal

5

Tôi có một giải pháp (xấu nhưng hiệu quả) cho vấn đề này, mà tôi đã sử dụng gần đây:

import java.lang.reflect.TypeVariable;


public static <T> Class<T> getGenericClass()
{
    __<T> ins = new __<T>();
    TypeVariable<?>[] cls = ins.getClass().getTypeParameters(); 

    return (Class<T>)cls[0].getClass();
}

private final class __<T> // generic helper class which does only provide type information
{
    private __()
    {
    }
}


3

Tôi tìm thấy một cách chung chung và đơn giản để làm điều đó. Trong lớp của tôi, tôi đã tạo ra một phương thức trả về kiểu chung theo vị trí của nó trong định nghĩa lớp. Hãy giả sử một định nghĩa lớp như thế này:

public class MyClass<A, B, C> {

}

Bây giờ hãy tạo một số thuộc tính để duy trì các loại:

public class MyClass<A, B, C> {

    private Class<A> aType;

    private Class<B> bType;

    private Class<C> cType;

// Getters and setters (not necessary if you are going to use them internally)

    } 

Sau đó, bạn có thể tạo một phương thức chung trả về kiểu dựa trên chỉ mục của định nghĩa chung:

   /**
     * Returns a {@link Type} object to identify generic types
     * @return type
     */
    private Type getGenericClassType(int index) {
        // To make it use generics without supplying the class type
        Type type = getClass().getGenericSuperclass();

        while (!(type instanceof ParameterizedType)) {
            if (type instanceof ParameterizedType) {
                type = ((Class<?>) ((ParameterizedType) type).getRawType()).getGenericSuperclass();
            } else {
                type = ((Class<?>) type).getGenericSuperclass();
            }
        }

        return ((ParameterizedType) type).getActualTypeArguments()[index];
    }

Cuối cùng, trong hàm tạo, chỉ cần gọi phương thức và gửi chỉ mục cho từng loại. Mã hoàn chỉnh sẽ trông như sau:

public class MyClass<A, B, C> {

    private Class<A> aType;

    private Class<B> bType;

    private Class<C> cType;


    public MyClass() {
      this.aType = (Class<A>) getGenericClassType(0);
      this.bType = (Class<B>) getGenericClassType(1);
      this.cType = (Class<C>) getGenericClassType(2);
    }

   /**
     * Returns a {@link Type} object to identify generic types
     * @return type
     */
    private Type getGenericClassType(int index) {

        Type type = getClass().getGenericSuperclass();

        while (!(type instanceof ParameterizedType)) {
            if (type instanceof ParameterizedType) {
                type = ((Class<?>) ((ParameterizedType) type).getRawType()).getGenericSuperclass();
            } else {
                type = ((Class<?>) type).getGenericSuperclass();
            }
        }

        return ((ParameterizedType) type).getActualTypeArguments()[index];
    }
}

2

Như đã giải thích trong các câu trả lời khác, để sử dụng ParameterizedTypephương pháp này , bạn cần mở rộng lớp, nhưng dường như đó là việc làm thêm để tạo ra một lớp hoàn toàn mới mở rộng nó ...

Vì vậy, làm cho lớp trừu tượng nó buộc bạn phải mở rộng nó, do đó đáp ứng yêu cầu phân lớp. (sử dụng @Getter của lombok).

@Getter
public abstract class ConfigurationDefinition<T> {

    private Class<T> type;
    ...

    public ConfigurationDefinition(...) {
        this.type = (Class<T>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
        ...
    }
}

Bây giờ để mở rộng nó mà không cần xác định một lớp mới. (Lưu ý {} ở cuối ... được mở rộng, nhưng không ghi đè lên bất cứ điều gì - trừ khi bạn muốn).

private ConfigurationDefinition<String> myConfigA = new ConfigurationDefinition<String>(...){};
private ConfigurationDefinition<File> myConfigB = new ConfigurationDefinition<File>(...){};
...
Class stringType = myConfigA.getType();
Class fileType = myConfigB.getType();

2

Tôi giả sử rằng, vì bạn có một lớp chung, bạn sẽ có một biến như thế:

private T t;

(biến này cần lấy một giá trị tại hàm tạo)

Trong trường hợp đó, bạn có thể chỉ cần tạo phương thức sau:

Class<T> getClassOfInstance()
{
    return (Class<T>) t.getClass();
}

Hy vọng nó giúp!


1
   public <T> T yourMethodSignature(Class<T> type) {

        // get some object and check the type match the given type
        Object result = ...            

        if (type.isAssignableFrom(result.getClass())) {
            return (T)result;
        } else {
            // handle the error
        }
   }

1

Nếu bạn đang mở rộng hoặc triển khai bất kỳ lớp / giao diện nào đang sử dụng generic, bạn có thể nhận Loại chung của lớp / giao diện cha mẹ, mà không sửa đổi bất kỳ lớp / giao diện hiện có nào.

Có thể có ba khả năng,

Trường hợp 1 Khi lớp của bạn đang mở rộng một lớp đang sử dụng Generics

public class TestGenerics {
    public static void main(String[] args) {
        Type type = TestMySuperGenericType.class.getGenericSuperclass();
        Type[] gTypes = ((ParameterizedType)type).getActualTypeArguments();
        for(Type gType : gTypes){
            System.out.println("Generic type:"+gType.toString());
        }
    }
}

class GenericClass<T> {
    public void print(T obj){};
}

class TestMySuperGenericType extends GenericClass<Integer> {
}

Trường hợp 2 Khi lớp của bạn đang triển khai một giao diện đang sử dụng Generics

public class TestGenerics {
    public static void main(String[] args) {
        Type[] interfaces = TestMySuperGenericType.class.getGenericInterfaces();
        for(Type type : interfaces){
            Type[] gTypes = ((ParameterizedType)type).getActualTypeArguments();
            for(Type gType : gTypes){
                System.out.println("Generic type:"+gType.toString());
            }
        }
    }
}

interface GenericClass<T> {
    public void print(T obj);
}

class TestMySuperGenericType implements GenericClass<Integer> {
    public void print(Integer obj){}
}

Trường hợp 3 Khi giao diện của bạn đang mở rộng giao diện đang sử dụng Generics

public class TestGenerics {
    public static void main(String[] args) {
        Type[] interfaces = TestMySuperGenericType.class.getGenericInterfaces();
        for(Type type : interfaces){
            Type[] gTypes = ((ParameterizedType)type).getActualTypeArguments();
            for(Type gType : gTypes){
                System.out.println("Generic type:"+gType.toString());
            }
        }
    }
}

interface GenericClass<T> {
    public void print(T obj);
}

interface TestMySuperGenericType extends GenericClass<Integer> {
}

1

Đó là khá thẳng về phía trước. Nếu bạn cần từ trong cùng một lớp:

Class clazz = this.getClass();
ParameterizedType parameterizedType = (ParameterizedType) clazz.getGenericSuperclass();
try {
        Class typeClass = Class.forName( parameterizedType.getActualTypeArguments()[0].getTypeName() );
        // You have the instance of type 'T' in typeClass variable

        System.out.println( "Class instance name: "+  typeClass.getName() );
    } catch (ClassNotFoundException e) {
        System.out.println( "ClassNotFound!! Something wrong! "+ e.getMessage() );
    }

0

Trên thực tế, tôi cho rằng bạn có một trường trong lớp loại T. Nếu không có trường loại T, thì điểm nào có Loại chung? Vì vậy, bạn có thể chỉ cần làm một thể hiện trên trường đó.

Trong trường hợp của tôi, tôi có một

Liệt kê các mục <T>;
trong lớp của tôi và tôi kiểm tra xem loại lớp có phải là "Địa phương" không

if (items.get (0) instanceof Locality) ...

Tất nhiên, điều này chỉ hoạt động nếu tổng số lớp có thể bị hạn chế.


4
Tôi phải làm gì nếu items.isEmpty () là đúng?
hỗn loạn3quilibrium

0

Câu hỏi này đã cũ, nhưng bây giờ tốt nhất là sử dụng google Gson .

Một ví dụ để có được tùy chỉnh viewModel.

Class<CustomViewModel<String>> clazz = new GenericClass<CustomViewModel<String>>().getRawType();
CustomViewModel<String> viewModel = viewModelProvider.get(clazz);

Lớp loại chung

class GenericClass<T>(private val rawType: Class<*>) {

    constructor():this(`$Gson$Types`.getRawType(object : TypeToken<T>() {}.getType()))

    fun getRawType(): Class<T> {
        return rawType as Class<T>
    }
}

0

Tôi muốn truyền T. class cho một phương thức sử dụng Generics

Phương thức readFile đọc tệp .csv được chỉ định bởi fileName với fullpath. Có thể có các tệp csv với các nội dung khác nhau do đó tôi cần phải vượt qua lớp tệp mô hình để tôi có thể có được các đối tượng thích hợp. Vì đây là đọc tệp csv nên tôi muốn làm theo cách chung chung. Vì một số lý do hoặc không có giải pháp nào ở trên làm việc cho tôi. Tôi cần sử dụng Class<? extends T> typeđể làm cho nó hoạt động. Tôi sử dụng thư viện opencsv để phân tích tệp CSV.

private <T>List<T> readFile(String fileName, Class<? extends T> type) {

    List<T> dataList = new ArrayList<T>();
    try {
        File file = new File(fileName);

        Reader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
        Reader headerReader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));

        CSVReader csvReader = new CSVReader(headerReader);
        // create csv bean reader
        CsvToBean<T> csvToBean = new CsvToBeanBuilder(reader)
                .withType(type)
                .withIgnoreLeadingWhiteSpace(true)
                .build();

        dataList = csvToBean.parse();
    }
    catch (Exception ex) {
        logger.error("Error: ", ex);
    }

    return dataList;
}

Đây là cách gọi phương thức readFile

List<RigSurfaceCSV> rigSurfaceCSVDataList = readSurfaceFile(surfaceFileName, RigSurfaceCSV.class);

-4

Tôi đang sử dụng cách giải quyết cho việc này:

class MyClass extends Foo<T> {
....
}

MyClass myClassInstance = MyClass.class.newInstance();
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.