Java: làm thế nào để tôi có được một lớp chữ từ một loại chung?


193

Thông thường, tôi đã thấy mọi người sử dụng lớp theo nghĩa đen như thế này:

Class<Foo> cls = Foo.class;

Nhưng nếu loại này là chung chung, ví dụ như Danh sách? Điều này hoạt động tốt, nhưng có một cảnh báo vì Danh sách nên được tham số hóa:

Class<List> cls = List.class

Vậy tại sao không thêm a <?>? Chà, điều này gây ra lỗi không khớp loại:

Class<List<?>> cls = List.class

Tôi đã tìm ra một cái gì đó như thế này sẽ hoạt động, nhưng đây chỉ là một lỗi cú pháp đơn giản:

Class<List<Foo>> cls = List<Foo>.class

Làm thế nào tôi có thể có được một Class<List<Foo>>cách tĩnh, ví dụ như sử dụng lớp chữ?

Tôi có thể sử dụng @SuppressWarnings("unchecked")để loại bỏ các cảnh báo gây ra bởi việc sử dụng Danh sách không được tham số hóa trong ví dụ đầu tiên Class<List> cls = List.class, nhưng tôi thì không.

Bất kỳ đề xuất?

Câu trả lời:


160

Bạn không thể do loại tẩy xóa .

Các khái quát về Java ít hơn một chút so với đường cú pháp cho các đối tượng. Để lam sang tỏ:

List<Integer> list1 = new ArrayList<Integer>();
List<String> list2 = (List<String>)list1;
list2.add("foo"); // perfectly legal

Trường hợp duy nhất mà thông tin loại chung được giữ lại trong thời gian chạy là Field.getGenericType()nếu thẩm vấn các thành viên của lớp thông qua sự phản chiếu.

Tất cả điều này là lý do tại sao Object.getClass()có chữ ký này:

public final native Class<?> getClass();

Phần quan trọng là Class<?>.

Nói cách khác, từ Câu hỏi thường gặp về Java Generics :

Tại sao không có lớp chữ cho các loại tham số cụ thể?

Bởi vì kiểu tham số không có biểu diễn kiểu thời gian chạy chính xác.

Một lớp nghĩa đen biểu thị một Class đối tượng đại diện cho một loại nhất định. Ví dụ, lớp chữ String.classbiểu thị Class đối tượng đại diện cho loại Stringvà giống hệt với Classđối tượng được trả về khi phương thức getClassđược gọi trên một Stringđối tượng. Một lớp chữ có thể được sử dụng để kiểm tra loại thời gian chạy và để phản ánh.

Các kiểu tham số hóa làm mất các đối số kiểu của chúng khi chúng được dịch sang mã byte trong quá trình biên dịch trong một quá trình gọi là xóa kiểu. Là một tác dụng phụ của việc xóa kiểu, tất cả các cảnh báo của một kiểu chung đều có chung biểu diễn thời gian chạy, cụ thể là kiểu thô tương ứng. Nói cách khác, các kiểu tham số hóa không có biểu diễn kiểu của riêng chúng. Do đó, không có điểm nào trong việc hình thành các chữ nghĩa như List<String>.class, List<Long>.classList<?>.class , vì không có các Classđối tượng như vậy tồn tại. Chỉ loại thô Listcó một Class đối tượng đại diện cho loại thời gian chạy của nó. Nó được gọi là List.class.


12
List<Integer> list1 = new ArrayList<Integer>(); List<String> list2 = (List<String>)list1; list2.add("foo"); // perfectly legal Bạn không thể làm điều đó trong Java, bạn gặp lỗi biên dịch kiểu không khớp!
DhafirNz

4
vậy ... tôi phải làm gì nếu tôi cần?
Christopher Francisco

2
Bạn luôn có thể đánh lừa trình biên dịch bằng cáchList<String> list2 = (List<String>) (Object) list1;
kftse

17
Còn một "Nó chỉ hoạt động trong C #, nhưng không phải trong Java" đối với tôi. Tôi đang giải tuần tự hóa một đối tượng JSON và typeof (Danh sách <MyClass>) hoạt động hoàn toàn tốt trong C #, nhưng Danh sách <MyClass>. Class là một lỗi cú pháp trong Java. Vâng, có một lời giải thích hợp lý cho điều đó như thường lệ như Cletus đã viết, nhưng tôi luôn tự hỏi tại sao tất cả những thứ đó chỉ hoạt động trong C #.
Rau chết tiệt

2
bạn có ý nghĩa gì hoàn toàn hợp pháp? Đó là một phần của mã không biên dịch?
Eduardo Dennis

63

Không có chữ Class cho các loại tham số, tuy nhiên có các đối tượng Loại xác định chính xác các loại này.

Xem java.lang.reflect.ParameterizedType - http://java.sun.com/j2se/1.5.0/docs/api/java/lang/reflect/ParameterizedType.html

Thư viện Gson của Google định nghĩa một lớp TypeToken cho phép chỉ cần tạo các kiểu được tham số hóa và sử dụng nó để chỉ định các đối tượng json với các kiểu tham số phức tạp theo cách thân thiện chung. Trong ví dụ của bạn, bạn sẽ sử dụng:

Type typeOfListOfFoo = new TypeToken<List<Foo>>(){}.getType()

Tôi dự định đăng liên kết đến các lớp TypeToken và Gson javadoc nhưng Stack Overflow sẽ không cho phép tôi đăng nhiều hơn một liên kết vì tôi là người dùng mới, bạn có thể dễ dàng tìm thấy chúng bằng tìm kiếm của Google


1
Với điều này, tôi đã có thể tạo một lớp với E chung và sau đó sử dụng clzz = new TypeToken<E>(){}.getRawType();để lặp lại các enum có cấu trúc tương tự clzz.getEnumConstants()và sau đó sử dụng sự chỉnh sửa để gọi các phương thức thành viên là Method method = clzz.getDeclaredMethod("getSomeFoo");rất nhiều chiến thắng! Cảm ơn bạn!
Naruto Sempai

57

Bạn có thể quản lý nó với một dàn nhân đôi:

@SuppressWarnings("unchecked") Class<List<Foo>> cls = (Class<List<Foo>>)(Object)List.class


2
Bằng cách thay đổi lần truyền thứ hai từ Objectthành Classbạn có thể có thể tiết kiệm được chi phí hoạt động của thời gian chạy được kiểm tra (vô nghĩa).
Clashsoft

2
@Clashsoft Sử dụng Classthay vì Object, như bạn đề xuất, có vẻ có ý nghĩa hơn nhưng nó không loại bỏ sự cần thiết của @SuppressWarnings("unchecked")chú thích, nó thậm chí còn thêm một cảnh báo mới:Class is a raw type. References to generic type Class<T> should be parameterized
Ortomala Lokni

10
Bạn có thể sử dụng Class<?>:(Class<List<Foo>>)(Class<?>)List.class
Devstr

@Devstr Tôi thấy bạn đúng khi tôi thử ... Các đối số để sử dụng (Object) hoặc (Class <?>) Là gì?
cellepo

2
Câu trả lời này là hoàn toàn vô nghĩa. Lý do OP muốn tham số hóa đường dẫn lớp là vì anh ta đã nhận được uncheckedcảnh báo. Câu trả lời này không thay đổi / cải thiện bất kỳ điều đó. OP thậm chí còn tuyên bố rằng anh ta không muốn sử dụng SuppressWarnings...
Spenhouet

6

Để giải thích câu trả lời của cletus, trong thời gian chạy, tất cả các bản ghi của các loại chung được loại bỏ. Generics chỉ được xử lý trong trình biên dịch và được sử dụng để cung cấp loại an toàn bổ sung. Chúng thực sự chỉ là tốc ký cho phép trình biên dịch chèn các kiểu chữ vào những nơi thích hợp. Ví dụ: trước đây bạn phải làm như sau:

List x = new ArrayList();
x.add(new SomeClass());
Iterator i = x.iterator();
SomeClass z = (SomeClass) i.next();

trở thành

List<SomeClass> x = new ArrayList<SomeClass>();
x.add(new SomeClass());
Iterator<SomeClass> i = x.iterator();
SomeClass z = i.next();

Điều này cho phép trình biên dịch kiểm tra mã của bạn tại thời gian biên dịch, nhưng trong thời gian chạy, nó vẫn giống như ví dụ đầu tiên.


Cảm ơn đã giải thích thêm - sự hiểu biết của tôi về thuốc generic đã rõ ràng hơn rất nhiều đến nỗi tôi nhận ra chúng không phải là một cơ chế thời gian chạy. :)
Tom

2
Theo tôi, điều này chỉ có nghĩa là chung chung đã được Sun triển khai theo cách tầm thường, hy vọng rằng Oracle sẽ khắc phục điều này một ngày nào đó. Việc triển khai chung của C # tốt hơn nhiều (Anders giống như thần)
Marcel Valdez Orozco

1
@MarcelValdezOrozco AFAIK, trong Java, họ đã triển khai theo cách đó vì họ muốn mã cũ (trước 1.5) hoạt động trên các JVM mới mà không gặp vấn đề gì. Có vẻ như đó là một quyết định thiết kế rất thông minh, quan tâm đến khả năng tương thích. Tôi không nghĩ có gì tầm thường trong đó.
peter.petrov

3

Câu hỏi thường gặp về Java Generics và do đó cũng là câu trả lời của cletus có vẻ như không có điểm nào để có Class<List<T>>, tuy nhiên vấn đề thực sự là điều này cực kỳ nguy hiểm:

@SuppressWarnings("unchecked")
Class<List<String>> stringListClass = (Class<List<String>>) (Class<?>) List.class;

List<Integer> intList = new ArrayList<>();
intList.add(1);
List<String> stringList = stringListClass.cast(intList);
// Surprise!
String firstElement = stringList.get(0);

Điều này cast()làm cho nó trông như thể nó an toàn, nhưng trong thực tế, nó không an toàn chút nào.


Mặc dù tôi không biết nơi nào không thể List<?>.class= Class<List<?>>vì điều này sẽ khá hữu ích khi bạn có một phương thức xác định loại dựa trên loại Classđối số chung.

getClass()JDK-6184881 yêu cầu chuyển sang sử dụng ký tự đại diện, tuy nhiên có vẻ như thay đổi này sẽ không được thực hiện (rất sớm) vì nó không tương thích với mã trước đó (xem nhận xét này ).


2

Như tất cả chúng ta đều biết rằng nó bị xóa. Nhưng nó có thể được biết trong một số trường hợp trong đó loại được đề cập rõ ràng trong hệ thống phân cấp lớp:

import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.stream.Collectors;

public abstract class CaptureType<T> {
    /**
     * {@link java.lang.reflect.Type} object of the corresponding generic type. This method is useful to obtain every kind of information (including annotations) of the generic type.
     *
     * @return Type object. null if type could not be obtained (This happens in case of generic type whose information cant be obtained using Reflection). Please refer documentation of {@link com.types.CaptureType}
     */
    public Type getTypeParam() {
        Class<?> bottom = getClass();
        Map<TypeVariable<?>, Type> reifyMap = new LinkedHashMap<>();

        for (; ; ) {
            Type genericSuper = bottom.getGenericSuperclass();
            if (!(genericSuper instanceof Class)) {
                ParameterizedType generic = (ParameterizedType) genericSuper;
                Class<?> actualClaz = (Class<?>) generic.getRawType();
                TypeVariable<? extends Class<?>>[] typeParameters = actualClaz.getTypeParameters();
                Type[] reified = generic.getActualTypeArguments();
                assert (typeParameters.length != 0);
                for (int i = 0; i < typeParameters.length; i++) {
                    reifyMap.put(typeParameters[i], reified[i]);
                }
            }

            if (bottom.getSuperclass().equals(CaptureType.class)) {
                bottom = bottom.getSuperclass();
                break;
            }
            bottom = bottom.getSuperclass();
        }

        TypeVariable<?> var = bottom.getTypeParameters()[0];
        while (true) {
            Type type = reifyMap.get(var);
            if (type instanceof TypeVariable) {
                var = (TypeVariable<?>) type;
            } else {
                return type;
            }
        }
    }

    /**
     * Returns the raw type of the generic type.
     * <p>For example in case of {@code CaptureType<String>}, it would return {@code Class<String>}</p>
     * For more comprehensive examples, go through javadocs of {@link com.types.CaptureType}
     *
     * @return Class object
     * @throws java.lang.RuntimeException If the type information cant be obtained. Refer documentation of {@link com.types.CaptureType}
     * @see com.types.CaptureType
     */
    public Class<T> getRawType() {
        Type typeParam = getTypeParam();
        if (typeParam != null)
            return getClass(typeParam);
        else throw new RuntimeException("Could not obtain type information");
    }


    /**
     * Gets the {@link java.lang.Class} object of the argument type.
     * <p>If the type is an {@link java.lang.reflect.ParameterizedType}, then it returns its {@link java.lang.reflect.ParameterizedType#getRawType()}</p>
     *
     * @param type The type
     * @param <A>  type of class object expected
     * @return The Class<A> object of the type
     * @throws java.lang.RuntimeException If the type is a {@link java.lang.reflect.TypeVariable}. In such cases, it is impossible to obtain the Class object
     */
    public static <A> Class<A> getClass(Type type) {
        if (type instanceof GenericArrayType) {
            Type componentType = ((GenericArrayType) type).getGenericComponentType();
            Class<?> componentClass = getClass(componentType);
            if (componentClass != null) {
                return (Class<A>) Array.newInstance(componentClass, 0).getClass();
            } else throw new UnsupportedOperationException("Unknown class: " + type.getClass());
        } else if (type instanceof Class) {
            Class claz = (Class) type;
            return claz;
        } else if (type instanceof ParameterizedType) {
            return getClass(((ParameterizedType) type).getRawType());
        } else if (type instanceof TypeVariable) {
            throw new RuntimeException("The type signature is erased. The type class cant be known by using reflection");
        } else throw new UnsupportedOperationException("Unknown class: " + type.getClass());
    }

    /**
     * This method is the preferred method of usage in case of complex generic types.
     * <p>It returns {@link com.types.TypeADT} object which contains nested information of the type parameters</p>
     *
     * @return TypeADT object
     * @throws java.lang.RuntimeException If the type information cant be obtained. Refer documentation of {@link com.types.CaptureType}
     */
    public TypeADT getParamADT() {
        return recursiveADT(getTypeParam());
    }

    private TypeADT recursiveADT(Type type) {
        if (type instanceof Class) {
            return new TypeADT((Class<?>) type, null);
        } else if (type instanceof ParameterizedType) {
            ArrayList<TypeADT> generic = new ArrayList<>();
            ParameterizedType type1 = (ParameterizedType) type;
            return new TypeADT((Class<?>) type1.getRawType(),
                    Arrays.stream(type1.getActualTypeArguments()).map(x -> recursiveADT(x)).collect(Collectors.toList()));
        } else throw new UnsupportedOperationException();
    }

}

public class TypeADT {
    private final Class<?> reify;
    private final List<TypeADT> parametrized;

    TypeADT(Class<?> reify, List<TypeADT> parametrized) {
        this.reify = reify;
        this.parametrized = parametrized;
    }

    public Class<?> getRawType() {
        return reify;
    }

    public List<TypeADT> getParameters() {
        return parametrized;
    }
}

Và bây giờ bạn có thể làm những việc như:

static void test1() {
        CaptureType<String> t1 = new CaptureType<String>() {
        };
        equals(t1.getRawType(), String.class);
    }

    static void test2() {
        CaptureType<List<String>> t1 = new CaptureType<List<String>>() {
        };
        equals(t1.getRawType(), List.class);
        equals(t1.getParamADT().getParameters().get(0).getRawType(), String.class);
    }


    private static void test3() {
            CaptureType<List<List<String>>> t1 = new CaptureType<List<List<String>>>() {
            };
            equals(t1.getParamADT().getRawType(), List.class);
        equals(t1.getParamADT().getParameters().get(0).getRawType(), List.class);
    }

    static class Test4 extends CaptureType<List<String>> {
    }

    static void test4() {
        Test4 test4 = new Test4();
        equals(test4.getParamADT().getRawType(), List.class);
    }

    static class PreTest5<S> extends CaptureType<Integer> {
    }

    static class Test5 extends PreTest5<Integer> {
    }

    static void test5() {
        Test5 test5 = new Test5();
        equals(test5.getTypeParam(), Integer.class);
    }

    static class PreTest6<S> extends CaptureType<S> {
    }

    static class Test6 extends PreTest6<Integer> {
    }

    static void test6() {
        Test6 test6 = new Test6();
        equals(test6.getTypeParam(), Integer.class);
    }



    class X<T> extends CaptureType<T> {
    }

    class Y<A, B> extends X<B> {
    }

    class Z<Q> extends Y<Q, Map<Integer, List<List<List<Integer>>>>> {
    }

    void test7(){
        Z<String> z = new Z<>();
        TypeADT param = z.getParamADT();
        equals(param.getRawType(), Map.class);
        List<TypeADT> parameters = param.getParameters();
        equals(parameters.get(0).getRawType(), Integer.class);
        equals(parameters.get(1).getRawType(), List.class);
        equals(parameters.get(1).getParameters().get(0).getRawType(), List.class);
        equals(parameters.get(1).getParameters().get(0).getParameters().get(0).getRawType(), List.class);
        equals(parameters.get(1).getParameters().get(0).getParameters().get(0).getParameters().get(0).getRawType(), Integer.class);
    }




    static void test8() throws IllegalAccessException, InstantiationException {
        CaptureType<int[]> type = new CaptureType<int[]>() {
        };
        equals(type.getRawType(), int[].class);
    }

    static void test9(){
        CaptureType<String[]> type = new CaptureType<String[]>() {
        };
        equals(type.getRawType(), String[].class);
    }

    static class SomeClass<T> extends CaptureType<T>{}
    static void test10(){
        SomeClass<String> claz = new SomeClass<>();
        try{
            claz.getRawType();
            throw new RuntimeException("Shouldnt come here");
        }catch (RuntimeException ex){

        }
    }

    static void equals(Object a, Object b) {
        if (!a.equals(b)) {
            throw new RuntimeException("Test failed. " + a + " != " + b);
        }
    }

Thêm thông tin ở đây . Nhưng một lần nữa, gần như không thể truy xuất:

class SomeClass<T> extends CaptureType<T>{}
SomeClass<String> claz = new SomeClass<>();

nơi nó bị xóa.


Đây cũng là cách giải quyết được sử dụng bởi JAX-RS, xem GenericEntityGenericType.
Hein Blöd

1

Do thực tế cho thấy rằng những người theo lớp không có thông tin chung chung, tôi nghĩ bạn nên cho rằng sẽ không thể thoát khỏi tất cả các cảnh báo. Theo một cách nào đó, việc sử dụng Class<Something>cũng giống như sử dụng một bộ sưu tập mà không chỉ định loại chung. Điều tốt nhất tôi có thể đi ra là:

private <C extends A<C>> List<C> getList(Class<C> cls) {
    List<C> res = new ArrayList<C>();
    // "snip"... some stuff happening in here, using cls
    return res;
}

public <C extends A<C>> List<A<C>> getList() {
    return getList(A.class);
}

1

Bạn có thể sử dụng một phương thức trợ giúp để loại bỏ @SuppressWarnings("unchecked")tất cả các lớp.

@SuppressWarnings("unchecked")
private static <T> Class<T> generify(Class<?> cls) {
    return (Class<T>)cls;
}

Sau đó bạn có thể viết

Class<List<Foo>> cls = generify(List.class);

Các ví dụ sử dụng khác là

  Class<Map<String, Integer>> cls;

  cls = generify(Map.class);

  cls = TheClass.<Map<String, Integer>>generify(Map.class);

  funWithTypeParam(generify(Map.class));

public void funWithTypeParam(Class<Map<String, Integer>> cls) {
}

Tuy nhiên, vì nó hiếm khi thực sự hữu ích và việc sử dụng phương thức đánh bại việc kiểm tra loại trình biên dịch, tôi không khuyên bạn nên triển khai nó ở nơi có thể truy cập công khai.

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.