Nhận loại tham số chung trong Java với sự phản chiếu


143

Có thể có được loại tham số chung?

Một ví dụ:

public final class Voodoo {
    public static void chill(List<?> aListWithTypeSpiderMan) {
        // Here I'd like to get the Class-Object 'SpiderMan'
        Class typeOfTheList = ???;
    }

    public static void main(String... args) {
        chill(new ArrayList<SpiderMan>());
    }
}

Câu trả lời:


156

Một lần xây dựng, tôi đã từng vấp ngã trông giống như

Class<T> persistentClass = (Class<T>)
   ((ParameterizedType)getClass().getGenericSuperclass())
      .getActualTypeArguments()[0];

Vì vậy, dường như có một số phép thuật phản chiếu xung quanh mà tôi không may hoàn toàn không hiểu ... Xin lỗi.


2
Nhưng tuyên bố nó là trừu tượng rồi. khi bạn muốn sử dụng nó, sau đó phân lớp nó nội tuyến.
Snicolas

42
Mặc dù điều này rất cũ và được chấp nhận vì một số lý do, tôi đã từ chối vì đơn giản là nó không trả lời câu hỏi. Nó sẽ không đưa ra "Người nhện" trong ví dụ đưa ra trong câu hỏi. Nó chắc chắn hữu ích trong một số tình huống, nhưng nó không hoạt động cho câu hỏi được hỏi.
Jon Skeet

15
Không ai đến đây sắp chấp nhận "Không thể thực hiện được" cho câu trả lời. Chúng tôi ở đây vì chúng tôi tuyệt vọng.
Addison

14
Tôi nhận được ngoại lệ này: Exception in thread "main" java.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType không chắc ràng ràng buộc là gì.
Món ăn

4
Giống như @Dish, tôi chỉ nhận được mộtjava.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType
Kenny Wyland

133

Tôi muốn thử chia nhỏ câu trả lời từ @DerMike để giải thích:

Đầu tiên, xóa kiểu không có nghĩa là JDK loại bỏ thông tin kiểu khi chạy. Đây là một phương pháp để cho phép kiểm tra kiểu thời gian biên dịch và khả năng tương thích kiểu thời gian chạy cùng tồn tại trong cùng một ngôn ngữ. Như khối mã này ngụ ý, JDK giữ lại thông tin loại đã xóa - nó chỉ không liên quan đến các phôi và nội dung được kiểm tra.

Thứ hai, điều này cung cấp thông tin loại chung cho một lớp chung chính xác một cấp độ gia truyền từ loại cụ thể được kiểm tra - tức là một lớp cha trừu tượng với các tham số loại chung có thể tìm thấy các loại cụ thể tương ứng với các tham số loại của nó để thực hiện cụ thể mà trực tiếp thừa hưởng từ nó. Nếu lớp này không trừu tượng và khởi tạo, hoặc việc triển khai cụ thể giảm hai cấp, thì điều này sẽ không hiệu quả (mặc dù một chút mánh lới có thể khiến nó áp dụng cho bất kỳ cấp độ nào được xác định trước vượt quá một hoặc đến cấp thấp nhất với các tham số loại X chung, et cetera).

Dù sao, để giải thích. Đây là mã một lần nữa, được tách thành các dòng để dễ tham khảo:

1 # Class genericParameter0Of ThisClass = 
2 # (Lớp)
3 # ((ParameterizedType)
4 # getClass ()
5 # .getGenericSuperclass ())
6 # .getActualTypeArgument () [0];

Hãy 'chúng tôi' là lớp trừu tượng với các loại chung có chứa mã này. Đọc này đại khái từ trong ra ngoài:

  • Dòng 4 lấy thể hiện Class của lớp cụ thể hiện tại. Điều này xác định loại bê tông con cháu ngay lập tức của chúng tôi.
  • Dòng 5 lấy siêu kiểu của lớp đó là Loại; đây là chúng tôi. Vì chúng tôi là loại tham số, chúng tôi có thể tự chuyển mình thành ParameterizedType (dòng 3) một cách an toàn. Điều quan trọng là khi Java xác định đối tượng Loại này, nó sử dụng thông tin loại có trong phần tử con để liên kết thông tin loại với các tham số loại của chúng ta trong thể hiện ParameterizedType mới. Vì vậy, bây giờ chúng ta có thể truy cập các loại cụ thể cho thuốc generic của chúng tôi.
  • Dòng 6 lấy mảng các kiểu được ánh xạ vào tổng quát của chúng tôi, theo thứ tự như được khai báo trong mã lớp. Đối với ví dụ này, chúng tôi rút ra tham số đầu tiên. Điều này trở lại như là một loại.
  • Dòng 2 phôi Loại cuối cùng được trả về Lớp. Điều này an toàn vì chúng tôi biết loại tham số loại chung nào của chúng tôi có thể lấy và có thể xác nhận rằng tất cả chúng sẽ là các lớp (Tôi không chắc người Java sẽ đi bằng cách lấy tham số chung không có cá thể Class như thế nào liên kết với nó, thực sự).

... và đó là khá nhiều đó. Vì vậy, chúng tôi đẩy thông tin loại từ việc triển khai cụ thể của chúng tôi trở lại vào chính chúng tôi và sử dụng thông tin đó để truy cập vào một điều khiển lớp. chúng ta có thể tăng gấp đôi getGenericSuperclass () và đi hai cấp độ hoặc loại bỏ getGenericSuperclass () và nhận các giá trị cho bản thân dưới dạng cụ thể (báo trước: Tôi chưa thử các kịch bản này, chúng chưa đưa ra cho tôi).

Sẽ rất khó khăn nếu những đứa trẻ cụ thể của bạn là một số bước nhảy tùy ý, hoặc nếu bạn cụ thể và không phải là cuối cùng, và đặc biệt là khó khăn nếu bạn mong muốn bất kỳ đứa trẻ nào của bạn (sâu sắc) có thể có chung chung. Nhưng bạn thường có thể thiết kế xung quanh những cân nhắc đó, vì vậy điều này giúp bạn có được hầu hết các cách.

Hy vọng điều này đã giúp ai đó! Tôi nhận ra bài này là cổ xưa. Có lẽ tôi sẽ cắt lời giải thích này và giữ nó cho các câu hỏi khác.


3
Chỉ cần trả lời "Tôi không chắc bằng cách nào Java sẽ nhận được một tham số chung không có một cá thể Class được liên kết với nó,") Điều này là có thể nếu tham số của lớp chung là một " biểu thức ký tự đại diện "(giả sử :? extends someClass) hoặc" biến kiểu "(giả sử: <T> trong đó T xuất phát từ một lớp con chung). trong cả hai trường hợp, các thể hiện "Loại" được trả về không thể được chuyển thành "Lớp" vì mỗi VM có thể có cách triển khai khác nhau cho cả hai thực hiện giao diện Loại nhưng không xuất phát trực tiếp từ java.lang.Class.
Roberto Andrade

19

Thật ra tôi đã làm việc này. Hãy xem xét đoạn trích sau:

Method m;
Type[] genericParameterTypes = m.getGenericParameterTypes();
for (int i = 0; i < genericParameterTypes.length; i++) {
     if( genericParameterTypes[i] instanceof ParameterizedType ) {
                Type[] parameters = ((ParameterizedType)genericParameterTypes[i]).getActualTypeArguments();
//parameters[0] contains java.lang.String for method like "method(List<String> value)"

     }
 }

Tôi đang sử dụng jdk 1.6


12
-1: điều này không giải quyết được vấn đề trong câu hỏi; nó chỉ cung cấp cho bạn kiểu khai báo trong chữ ký phương thức, không phải kiểu thời gian chạy thực tế.
Michael Borgwardt

5
Có thể không trả lời câu hỏi của OP, nhưng đó là một kỹ thuật hữu ích.
dnault

3
Đây là câu trả lời cho một câu hỏi hoàn toàn khác.
Dawood ibn Kareem

Bạn có thể gọi m.getParameterTypes () để nhận tất cả các loại thực tế. Nó sẽ trả về "Danh sách" cho ví dụ.
Lee Meador

Nếu genericParameterTypes [i] không phải là một thể hiện của ParameterizedType, thì đó có thể là một Class. Điều đó có nghĩa là một đối số cụ thể cho phương thức hoàn toàn không được tham số hóa.
Lee Meador

18

Thực tế, có một giải pháp, bằng cách áp dụng thủ thuật "lớp ẩn danh" và các ý tưởng từ Mã thông báo Super Type :

public final class Voodoo {
    public static void chill(final List<?> aListWithSomeType) {
        // Here I'd like to get the Class-Object 'SpiderMan'
        System.out.println(aListWithSomeType.getClass().getGenericSuperclass());
        System.out.println(((ParameterizedType) aListWithSomeType
            .getClass()
            .getGenericSuperclass()).getActualTypeArguments()[0]);
    }
    public static void main(String... args) {
        chill(new ArrayList<SpiderMan>() {});
    }
}
class SpiderMan {
}

Bí quyết nằm trong việc tạo ra một lớp vô danh , new ArrayList<SpiderMan>() {}, tại địa điểm của bản gốc (đơn giản) new ArrayList<SpiderMan>(). Việc sử dụng một lớp anoymous (nếu có thể) đảm bảo rằng trình biên dịch giữ lại thông tin về đối số kiểu SpiderManđược cung cấp cho tham số kiểu List<?>. Võngà!


Điều này gián tiếp trả lời câu hỏi: Vấn đề ban đầu không có giải pháp. Để loại giữ lại các tham số chung của nó, nó cần được nhúng vào một loại khác, chẳng hạn như một lớp con. Ví dụ này cho thấy cách bạn cần thay đổi mã gọi để có thể khôi phục tham số loại chung trong chill ().
Florian F

FYI, liên kết đầu tiên đã chết
Ashvin Sharma

@AshvinSharma Tôi tin rằng cùng một tài liệu có sẵn ở đây: rgomes.info/USE-typetokens-to-retrieve-generic-parameter
Yann-Gaël Guéhéneuc

7

Do loại xóa, cách duy nhất để biết loại danh sách sẽ là truyền vào loại dưới dạng tham số cho phương thức:

public class Main {

    public static void main(String[] args) {
        doStuff(new LinkedList<String>(), String.class);

    }

    public static <E> void doStuff(List<E> list, Class<E> clazz) {

    }

}

7

Phụ lục cho câu trả lời của @ DerMike để lấy tham số chung của giao diện được tham số hóa (sử dụng phương thức #getGenericInterfaces () bên trong phương thức mặc định Java-8 để tránh trùng lặp):

import java.lang.reflect.ParameterizedType; 

public class ParametrizedStuff {

@SuppressWarnings("unchecked")
interface Awesomable<T> {
    default Class<T> parameterizedType() {
        return (Class<T>) ((ParameterizedType)
        this.getClass().getGenericInterfaces()[0])
            .getActualTypeArguments()[0];
    }
}

static class Beer {};
static class EstrellaGalicia implements Awesomable<Beer> {};

public static void main(String[] args) {
    System.out.println("Type is: " + new EstrellaGalicia().parameterizedType());
    // --> Type is: ParameterizedStuff$Beer
}

Đây không phải là những gì bài viết gốc đã yêu cầu. Ở đây, bạn đã tạo một lớp con của Awesomeable<Beer>. Trong trường hợp đó, thông tin loại được bảo tồn. Nếu bạn chuyển new Awesomable<Beer> ()sang phương thức wt sẽ không hoạt động.
Florian F

@FlorianF Nếu bạn đang di chuyển Awesomable<Beer>mà không có định nghĩa rõ ràng về một lớp con cụ thể như EstrellaGaliciatrong trường hợp này, thì nó vẫn nhận được loại tham số: Tôi đã chạy nó ngay bây giờ: System.out.println("Type is: " + new Awesomable<Beer>() {}.parameterizedType());---> Loại là: ParameterizedStuff $ Bia
Campa

6

Không, điều đó là không thể. Do các vấn đề tương thích hướng xuống, các tổng quát của Java dựa trên kiểu xóa , ia trong thời gian chạy, tất cả những gì bạn có là một Listđối tượng không chung chung . Có một số thông tin về các tham số loại trong thời gian chạy, nhưng nó nằm trong các định nghĩa lớp (tức là bạn có thể hỏi " định nghĩa của trường này sử dụng loại chung nào? "), Không phải trong các trường hợp đối tượng.


Mặc dù java có thể lưu trữ dữ liệu này dưới dạng dữ liệu meta trong thời gian chạy, nhưng không thể? Tôi hy vọng nó sẽ. Xui xẻo.
cimnine

1
@ user99485: Không. Tôi đúng và Andrey sai. Anh ấy đang đề cập đến thông tin loại tôi đề cập trong phần thứ hai của câu trả lời của tôi, nhưng đó không phải là những gì câu hỏi yêu cầu.
Michael Borgwardt

5

Như @bertolami đã chỉ ra, chúng tôi không thể sử dụng loại biến và nhận giá trị tương lai của nó (nội dung của biến typeOfList).

Tuy nhiên, bạn có thể chuyển lớp dưới dạng tham số trên nó như thế này:

public final class voodoo {
    public static void chill(List<T> aListWithTypeSpiderMan, Class<T> clazz) {
        // Here I'd like to get the Class-Object 'SpiderMan'
        Class typeOfTheList = clazz;
    }

    public static void main(String... args) {
        chill(new List<SpiderMan>(), Spiderman.class );
    }
}

Đó là ít nhiều những gì Google làm khi bạn phải chuyển một biến lớp cho hàm tạo của ActivityInstrumentationTestCase2 .


3

Không, điều đó là không thể.

Bạn có thể nhận được một loại chung của một trường được cung cấp một lớp là ngoại lệ duy nhất cho quy tắc đó và thậm chí đó là một chút hack.

Xem Biết loại chung trong Java để biết ví dụ về điều đó.


1

Bạn có thể lấy loại tham số chung có sự phản chiếu như trong ví dụ này mà tôi tìm thấy ở đây :

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

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

Làm thế nào bạn có thể làm điều tương tự để có V của Home <K, V>?
Rafael Sanches

1

Câu trả lời nhanh cho Câu hỏi là không bạn có thể, vì xóa kiểu chung Java.

Câu trả lời dài hơn sẽ là nếu bạn đã tạo danh sách của mình như thế này:

new ArrayList<SpideMan>(){}

Sau đó, trong trường hợp này, loại chung được bảo toàn trong siêu lớp chung của lớp ẩn danh mới ở trên.

Không phải tôi khuyên bạn nên làm điều này với các danh sách, nhưng đó là một triển khai người nghe:

new Listener<Type>() { public void doSomething(Type t){...}}

Và kể từ khi ngoại suy các loại siêu lớp chung và siêu giao diện thay đổi giữa các JVM, giải pháp chung không đơn giản như một số câu trả lời có thể đề xuất.

Bây giờ tôi đã làm nó.


0

Điều này là không thể bởi vì generic trong Java chỉ được xem xét tại thời điểm biên dịch. Do đó, các tổng quát Java chỉ là một số loại tiền xử lý. Tuy nhiên, bạn có thể nhận được lớp thực tế của các thành viên trong danh sách.


Vâng, đó là những gì tôi đang làm bây giờ. Nhưng bây giờ tôi muốn biết loại ngay cả khi danh sách trống. Nhưng bốn người không thể sai được. Cảm ơn tất cả)!
cimnine

19
Đây không phải là sự thật. Mặc dù rất khó để bạn có thể thấy trong bài tiếp theo mà ParameterizedType cho phép làm điều đó.
Edmondo1984

1
Đồng ý, nó có thể được thực hiện. Ví dụ từ DerMike phác thảo nó (làm việc cho tôi).
mac

LOL tại "bốn người không thể sai". Câu trả lời này là chính xác.
Dawood ibn Kareem

0

Tôi đã mã hóa điều này cho các phương thức sẽ chấp nhận hoặc trả lại Iterable<?...>. Đây là mã:

/**
 * Assuming the given method returns or takes an Iterable<T>, this determines the type T.
 * T may or may not extend WindupVertexFrame.
 */
private static Class typeOfIterable(Method method, boolean setter)
{
    Type type;
    if (setter) {
        Type[] types = method.getGenericParameterTypes();
        // The first parameter to the method expected to be Iterable<...> .
        if (types.length == 0)
            throw new IllegalArgumentException("Given method has 0 params: " + method);
        type = types[0];
    }
    else {
        type = method.getGenericReturnType();
    }

    // Now get the parametrized type of the generic.
    if (!(type instanceof ParameterizedType))
        throw new IllegalArgumentException("Given method's 1st param type is not parametrized generic: " + method);
    ParameterizedType pType = (ParameterizedType) type;
    final Type[] actualArgs = pType.getActualTypeArguments();
    if (actualArgs.length == 0)
        throw new IllegalArgumentException("Given method's 1st param type is not parametrized generic: " + method);

    Type t = actualArgs[0];
    if (t instanceof Class)
        return (Class<?>) t;

    if (t instanceof TypeVariable){
        TypeVariable tv =  (TypeVariable) actualArgs[0];
        AnnotatedType[] annotatedBounds = tv.getAnnotatedBounds();///
        GenericDeclaration genericDeclaration = tv.getGenericDeclaration();///
        return (Class) tv.getAnnotatedBounds()[0].getType();
    }

    throw new IllegalArgumentException("Unknown kind of type: " + t.getTypeName());
}

0

Bạn không thể có được một tham số chung từ một biến. Nhưng bạn có thể từ một phương thức hoặc khai báo trường:

Method method = getClass().getDeclaredMethod("chill", List.class);
Type[] params = method.getGenericParameterTypes();
ParameterizedType firstParam = (ParameterizedType) params[0];
Type[] paramsOfFirstGeneric = firstParam.getActualTypeArguments();

Điều này mang lại cho tôi một Ngoại lệ trong luồng "chính" java.lang.ClassCastException: sun.reflect.generics.reflectiveObjects.TypeVariableImpl
Lluis Martinez

'params' là 'Type' có một vài giao diện con. TypeVariableImpl là một cái được sử dụng cho các đối số phương thức và nó cho biết tên của cái chung bên trong <> s, chứ không phải Class mà nó đại diện.
Lee Meador

0

Chỉ cần tôi đọc đoạn mã này là khó, tôi chỉ chia nó thành 2 dòng có thể đọc được:

// assuming that the Generic Type parameter is of type "T"
ParameterizedType p = (ParameterizedType) getClass().getGenericSuperclass();
Class<T> c =(Class<T>)p.getActualTypeArguments()[0];

Tôi muốn tạo một thể hiện của tham số Type mà không có bất kỳ tham số nào cho phương thức của tôi:

publc T getNewTypeInstance(){
    ParameterizedType p = (ParameterizedType) getClass().getGenericSuperclass();
    Class<T> c =(Class<T>)p.getActualTypeArguments()[0];

    // for me i wanted to get the type to create an instance
    // from the no-args default constructor
    T t = null;
    try{
        t = c.newInstance();
    }catch(Exception e){
        // no default constructor available
    }
    return t;
}

0

Đây là một mẹo khác. Sử dụng một mảng vararg chung

import java.util.ArrayList;

class TypedArrayList<E> extends ArrayList<E>
{
    @SafeVarargs
    public TypedArrayList (E... typeInfo)
    {
        // Get generic type at runtime ...
        System.out.println (typeInfo.getClass().getComponentType().getTypeName());
    }
}

public class GenericTest
{
    public static void main (String[] args)
    {
        // No need to supply the dummy argument
        ArrayList<Integer> ar1 = new TypedArrayList<> ();
        ArrayList<String> ar2 = new TypedArrayList<> ();
        ArrayList<?> ar3 = new TypedArrayList<> ();
    }
}

0

Tôi nhận thấy rằng nhiều người nghiêng về getGenericSuperclass()giải pháp:

class RootGeneric<T> {
  public Class<T> persistentClass = (Class<T>)
    ((ParameterizedType)getClass().getGenericSuperclass())
      .getActualTypeArguments()[0];
}

Tuy nhiên, giải pháp này dễ bị lỗi. Nó sẽ không hoạt động đúng nếu có con cháu trong thế hệ con cháu. Xem xét điều này:

class Foo<S> extends RootGeneric<Integer> {}

class Bar extends Foo<Double> {}

Loại nào sẽ Bar.persistentClasscó? Class<Integer>? Không, nó sẽ được Class<Double>. Điều này sẽ xảy ra do getClass()luôn trả về lớp cao nhất, Bartrong trường hợp này và siêu hạng chung của nó là Foo<Double>. Do đó, loại đối số sẽ là Double.

Nếu bạn cần một giải pháp đáng tin cậy mà không thất bại, tôi có thể đề xuất hai.

  1. Sử dụng Guava. Nó có một lớp được tạo ra chính xác cho mục đích này : com.google.common.reflect.TypeToken. Nó xử lý tất cả các trường hợp góc tốt và cung cấp một số chức năng tốt hơn. Nhược điểm là một phụ thuộc thêm. Nếu bạn đã sử dụng lớp này, mã của bạn sẽ trông đơn giản và rõ ràng, như thế này:
class RootGeneric<T> {
  @SuppressWarnings("unchecked")
  public final Class<T> persistentClass = (Class<T>) (new TypeToken<T>(getClass()) {}.getType());
}
  1. Sử dụng phương pháp tùy chỉnh dưới đây. Nó thực hiện một logic đơn giản hóa đáng kể tương tự như lớp ổi, được đề cập ở trên. Tuy nhiên, tôi không đảm bảo nó dễ bị lỗi. Nó không giải quyết vấn đề với con cháu chung.
abstract class RootGeneric<T> {
  @SuppressWarnings("unchecked")
  private Class<T> getTypeOfT() {
    Class<T> type = null;
    Class<?> iter = getClass();
    while (iter.getSuperclass() != null) {
      Class<?> next = iter.getSuperclass();
      if (next != null && next.isAssignableFrom(RootGeneric.class)) {
        type =
            (Class<T>)
                ((ParameterizedType) iter.getGenericSuperclass()).getActualTypeArguments()[0];
        break;
      }
      iter = next;
    }
    if (type == null) {
      throw new ClassCastException("Cannot determine type of T");
    }
    return type;
  }
}

-1

Sử dụng:

Class<?> typeOfTheList = aListWithTypeSpiderMan.toArray().getClass().getComponentType();

Cái này sai. toArray()trả lại Object[]. Bạn không thể và không nên phụ thuộc vào nó là một số kiểu con cụ thể.
Radiodef
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.