Nhận loại chung của java.util.List


262

Tôi có;

List<String> stringList = new ArrayList<String>();
List<Integer> integerList = new ArrayList<Integer>();

Có cách nào (dễ dàng) để lấy loại chung của danh sách không?


Để có thể lập trình kiểm tra một đối tượng List và xem loại chung của nó. Một phương thức có thể muốn chèn các đối tượng dựa trên loại chung của bộ sưu tập. Điều này có thể có trong các ngôn ngữ triển khai generic trong thời gian chạy thay vì thời gian biên dịch.
Steve Kuo

4
Phải - về cách duy nhất để cho phép phát hiện thời gian chạy là phân lớp phụ: bạn thực sự có thể mở rộng kiểu chung và sau đó sử dụng khai báo kiểu tìm phản xạ mà kiểu con sử dụng. Đây là một chút suy nghĩ, nhưng có thể. Thật không may, không có cách dễ dàng để thực thi rằng người ta phải sử dụng lớp con chung.
StaxMan

1
Chắc chắn StringList chứa chuỗi và số nguyên integList? Tại sao làm cho nó phức tạp hơn?
Ben Thurley

Câu trả lời:


408

Nếu đó thực sự là các trường của một lớp nhất định, thì bạn có thể nhận được chúng với một chút trợ giúp của sự phản ánh:

package test;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.List;

public class Test {

    List<String> stringList = new ArrayList<String>();
    List<Integer> integerList = new ArrayList<Integer>();

    public static void main(String... args) throws Exception {
        Field stringListField = Test.class.getDeclaredField("stringList");
        ParameterizedType stringListType = (ParameterizedType) stringListField.getGenericType();
        Class<?> stringListClass = (Class<?>) stringListType.getActualTypeArguments()[0];
        System.out.println(stringListClass); // class java.lang.String.

        Field integerListField = Test.class.getDeclaredField("integerList");
        ParameterizedType integerListType = (ParameterizedType) integerListField.getGenericType();
        Class<?> integerListClass = (Class<?>) integerListType.getActualTypeArguments()[0];
        System.out.println(integerListClass); // class java.lang.Integer.
    }
}

Bạn cũng có thể làm điều đó cho các kiểu tham số và kiểu trả về của phương thức.

Nhưng nếu chúng nằm trong cùng một phạm vi của lớp / phương thức mà bạn cần biết về chúng, thì không có lý do gì để biết chúng, bởi vì bạn đã tự khai báo chúng.


17
Chắc chắn có những tình huống mà điều này là hữu ích ở trên. Trong ví dụ khung ORM không cấu hình.
BalusC

3
..có thể sử dụng Class # getDeclaredFields () để nhận tất cả các trường mà không cần biết tên trường.
BalusC

1
BalusC: Nghe có vẻ giống như một khung tiêm đối với tôi .. Dù sao đó cũng là cách sử dụng.
falstro

1
@loolooyyyy Thay vì sử dụng TypeLiteral, tôi khuyên bạn nên sử dụng TypeToken từ Guava. github.com/google/guava/wiki/ReflectionExplained
Babyburger

1
@Oleg Biến của bạn đang sử dụng <?>(loại ký tự đại diện) thay vì <Class>(loại lớp). Thay thế của bạn <?>bằng <Class>hoặc bất cứ điều gì <E>.
BalusC

19

Bạn cũng có thể làm tương tự cho các tham số phương thức:

Type[] types = method.getGenericParameterTypes();
//Now assuming that the first parameter to the method is of type List<Integer>
ParameterizedType pType = (ParameterizedType) types[0];
Class<?> clazz = (Class<?>) pType.getActualTypeArguments()[0];
System.out.println(clazz); //prints out java.lang.Integer

Trong phương thức.getGenericParameterTypes (); phương pháp là gì?
Neo Ravi

18

Câu trả lời ngắn gọn: không.

Đây có lẽ là một bản sao, không thể tìm thấy một cái thích hợp ngay bây giờ.

Java sử dụng một cái gì đó gọi là kiểu xóa, có nghĩa là trong thời gian chạy cả hai đối tượng là tương đương. Trình biên dịch biết các danh sách chứa các số nguyên hoặc chuỗi và như vậy có thể duy trì một môi trường an toàn kiểu. Thông tin này bị mất (trên cơ sở đối tượng) trong thời gian chạy và danh sách chỉ chứa 'Đối tượng'.

Bạn CÓ THỂ tìm hiểu một chút về lớp học, loại nào có thể được tham số hóa, nhưng thông thường đây chỉ là bất cứ thứ gì mở rộng "Đối tượng", tức là bất cứ thứ gì. Nếu bạn xác định một loại là

class <A extends MyClass> AClass {....}

AClass. Class sẽ chỉ chứa thực tế là tham số A được giới hạn bởi MyClass, nhưng hơn thế nữa, không có cách nào để nói.


1
Điều đó sẽ đúng nếu lớp cụ thể của chung không được chỉ định; trong ví dụ của mình, anh ta tuyên bố rõ ràng các danh sách như List<Integer>List<String>; trong những trường hợp đó, loại tẩy không được áp dụng.
Haroldo_OK

13

Kiểu chung của một bộ sưu tập chỉ nên quan trọng nếu nó thực sự có các đối tượng trong đó, phải không? Vì vậy, không dễ dàng hơn để làm:

Collection<?> myCollection = getUnknownCollectionFromSomewhere();
Class genericClass = null;
Iterator it = myCollection.iterator();
if (it.hasNext()){
    genericClass = it.next().getClass();
}
if (genericClass != null) { //do whatever we needed to know the type for

Không có thứ gọi là loại chung trong thời gian chạy, nhưng các đối tượng bên trong thời gian chạy được đảm bảo cùng loại với loại chung được khai báo, do đó, chỉ cần kiểm tra lớp của vật phẩm trước khi chúng tôi xử lý nó là đủ dễ dàng.

Một điều khác bạn có thể làm chỉ đơn giản là xử lý danh sách để có được các thành viên đúng loại, bỏ qua những người khác (hoặc xử lý chúng khác nhau).

Map<Class<?>, List<Object>> classObjectMap = myCollection.stream()
    .filter(Objects::nonNull)
    .collect(Collectors.groupingBy(Object::getClass));

// Process the list of the correct class, and/or handle objects of incorrect
// class (throw exceptions, etc). You may need to group subclasses by
// filtering the keys. For instance:

List<Number> numbers = classObjectMap.entrySet().stream()
        .filter(e->Number.class.isAssignableFrom(e.getKey()))
        .flatMap(e->e.getValue().stream())
        .map(Number.class::cast)
        .collect(Collectors.toList());

Điều này sẽ cung cấp cho bạn một danh sách tất cả các mục có các lớp là các lớp con Numbermà sau đó bạn có thể xử lý khi bạn cần. Phần còn lại của các mục đã được lọc ra vào danh sách khác. Vì chúng ở trên bản đồ, bạn có thể xử lý chúng theo ý muốn hoặc bỏ qua chúng.

Nếu bạn muốn bỏ qua các mục của các lớp khác hoàn toàn, nó trở nên đơn giản hơn nhiều:

List<Number> numbers = myCollection.stream()
    .filter(Number.class::isInstance)
    .map(Number.class::cast)
    .collect(Collectors.toList());

Bạn thậm chí có thể tạo một phương thức tiện ích để đảm bảo rằng danh sách CHỈ chứa các mục đó khớp với một lớp cụ thể:

public <V> List<V> getTypeSafeItemList(Collection<Object> input, Class<V> cls) {
    return input.stream()
            .filter(cls::isInstance)
            .map(cls::cast)
            .collect(Collectors.toList());
}

5
Và nếu bạn có một bộ sưu tập trống? Hoặc nếu bạn có Bộ sưu tập <Object> với Số nguyên và Chuỗi?
Falci

Hợp đồng cho lớp chỉ có thể yêu cầu danh sách các đối tượng cuối cùng được thông qua và lệnh gọi phương thức sẽ trả về null nếu danh sách là null, trống hoặc nếu loại danh sách không hợp lệ.
ggb667

1
Trong trường hợp bộ sưu tập trống, việc gán cho bất kỳ loại danh sách nào trong thời gian chạy là an toàn, bởi vì không có gì trong đó và sau khi xóa loại xảy ra, Danh sách là Danh sách là Danh sách. Vì điều này, tôi không thể nghĩ ra bất kỳ trường hợp nào mà loại bộ sưu tập trống sẽ quan trọng.
Steve K

Chà, "có thể có vấn đề" nếu bạn giữ tham chiếu trong một luồng và xử lý nó một khi các đối tượng bắt đầu hiển thị trong một kịch bản tiêu dùng của nhà sản xuất. Nếu danh sách có các loại đối tượng sai, điều xấu có thể xảy ra. Tôi đoán để ngăn chặn rằng bạn sẽ phải dừng xử lý bằng cách ngủ cho đến khi có ít nhất một mục trong danh sách trước khi tiếp tục.
ggb667

Nếu bạn có loại kịch bản nhà sản xuất / người tiêu dùng đó, thì việc lập kế hoạch cho danh sách sẽ có một loại chung chung nhất định mà không chắc chắn những gì có thể được đưa vào danh sách là thiết kế tồi, dù sao đi nữa. Thay vào đó, bạn tuyên bố nó là một bộ sưu tập Objectvà khi bạn rút chúng ra khỏi danh sách, bạn sẽ đưa chúng cho một trình xử lý thích hợp dựa trên loại của chúng.
Steve K

11

Để tìm loại chung của một trường:

((Class)((ParameterizedType)field.getGenericType()).getActualTypeArguments()[0]).getSimpleName()

10

Nếu bạn cần lấy kiểu chung của kiểu trả về, tôi đã sử dụng cách tiếp cận này khi tôi cần tìm các phương thức trong một lớp trả về a Collectionvà sau đó truy cập các kiểu chung của chúng:

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.List;

public class Test {

    public List<String> test() {
        return null;
    }

    public static void main(String[] args) throws Exception {

        for (Method method : Test.class.getMethods()) {
            Class returnClass = method.getReturnType();
            if (Collection.class.isAssignableFrom(returnClass)) {
                Type returnType = method.getGenericReturnType();
                if (returnType instanceof ParameterizedType) {
                    ParameterizedType paramType = (ParameterizedType) returnType;
                    Type[] argTypes = paramType.getActualTypeArguments();
                    if (argTypes.length > 0) {
                        System.out.println("Generic type is " + argTypes[0]);
                    }
                }
            }
        }

    }

}

Kết quả này:

Kiểu chung là lớp java.lang.String


6

Mở rộng câu trả lời của Steve K:

/** 
* Performs a forced cast.  
* Returns null if the collection type does not match the items in the list.
* @param data The list to cast.
* @param listType The type of list to cast to.
*/
static <T> List<? super T> castListSafe(List<?> data, Class<T> listType){
    List<T> retval = null;
    //This test could be skipped if you trust the callers, but it wouldn't be safe then.
    if(data!=null && !data.isEmpty() && listType.isInstance(data.iterator().next().getClass())) {
        @SuppressWarnings("unchecked")//It's OK, we know List<T> contains the expected type.
        List<T> foo = (List<T>)data;
        return retval;
    }
    return retval;
}
Usage:

protected WhateverClass add(List<?> data) {//For fluant useage
    if(data==null) || data.isEmpty(){
       throw new IllegalArgumentException("add() " + data==null?"null":"empty" 
       + " collection");
    }
    Class<?> colType = data.iterator().next().getClass();//Something
    aMethod(castListSafe(data, colType));
}

aMethod(List<Foo> foo){
   for(Foo foo: List){
      System.out.println(Foo);
   }
}

aMethod(List<Bar> bar){
   for(Bar bar: List){
      System.out.println(Bar);
   }
}

Những vấn đề tương tự như với câu trả lời của Steve K: Nếu danh sách trống thì sao? Điều gì xảy ra nếu danh sách thuộc loại <Object> có chứa Chuỗi và Số nguyên? Mã của bạn có nghĩa là bạn chỉ có thể thêm nhiều Chuỗi nếu mục đầu tiên trong danh sách là một chuỗi và hoàn toàn không có số nguyên nào ...
subslner

Nếu danh sách trống, bạn hết may mắn. Nếu danh sách là Object và nó thực sự chứa Object thì bạn vẫn ổn, nhưng nếu nó có sự pha trộn của những thứ bạn không gặp may. Xóa có nghĩa là điều này là tốt như bạn có thể làm. Erasure là thiếu trong quan điểm của tôi. Sự phân nhánh của nó ngay lập tức được hiểu kém và không đủ để giải quyết các trường hợp sử dụng thú vị và mong muốn của mối quan tâm điển hình. Không có gì ngăn cản việc tạo một lớp có thể được hỏi nó thuộc loại nào, nhưng đó đơn giản không phải là cách các lớp tích hợp hoạt động. Bộ sưu tập nên biết những gì chúng chứa, nhưng than ôi ...
ggb667

4

Trong thời gian chạy, không, bạn không thể.

Tuy nhiên thông qua sự phản ánh các tham số loại thể truy cập. Thử

for(Field field : this.getDeclaredFields()) {
    System.out.println(field.getGenericType())
}

Phương thức getGenericType()trả về một đối tượng Type. Trong trường hợp này, nó sẽ là một thể hiện của ParametrizedType, trong đó lần lượt có các phương thức getRawType()(sẽ chứa List.class, trong trường hợp này) và getActualTypeArguments()sẽ trả về một mảng (trong trường hợp này, có độ dài một, chứa một String.classhoặc Integer.class).


2
và nó có hoạt động cho các tham số nhận được bởi một phương thức thay vì các trường của một lớp không ???
khai mạc

@opensas Bạn có thể sử dụng method.getGenericParameterTypes()để lấy các kiểu khai báo của tham số của phương thức.
Radiodef

4

Có cùng một vấn đề, nhưng tôi đã sử dụng instanceof thay thế. Đã làm theo cách này:

List<Object> listCheck = (List<Object>)(Object) stringList;
    if (!listCheck.isEmpty()) {
       if (listCheck.get(0) instanceof String) {
           System.out.println("List type is String");
       }
       if (listCheck.get(0) instanceof Integer) {
           System.out.println("List type is Integer");
       }
    }
}

Điều này liên quan đến việc sử dụng các phôi không được kiểm tra vì vậy chỉ làm điều này khi bạn biết nó là một danh sách, và nó có thể là loại gì.


3

Nói chung là không thể, bởi vì List<String>List<Integer>chia sẻ cùng một lớp thời gian chạy.

Tuy nhiên, bạn có thể phản ánh về loại khai báo của trường đang giữ danh sách (nếu loại khai báo không tự tham chiếu đến một tham số loại có giá trị mà bạn không biết).


2

Như những người khác đã nói, câu trả lời đúng duy nhất là không, loại đã bị xóa.

Nếu danh sách có số phần tử khác không, bạn có thể điều tra loại phần tử đầu tiên (ví dụ: sử dụng phương thức getClass). Điều đó sẽ không cho bạn biết loại chung của danh sách, nhưng sẽ hợp lý khi giả định rằng loại chung là một số siêu lớp của các loại trong danh sách.

Tôi sẽ không ủng hộ cách tiếp cận, nhưng trong một ràng buộc, nó có thể hữu ích.


Điều đó sẽ đúng nếu lớp cụ thể của chung không được chỉ định; trong ví dụ của mình, anh ta tuyên bố rõ ràng các danh sách như List<Integer>List<String>; trong những trường hợp đó, loại tẩy không được áp dụng.
Haroldo_OK

2
import org.junit.Assert;
import org.junit.Test;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

public class GenericTypeOfCollectionTest {
    public class FormBean {
    }

    public class MyClazz {
        private List<FormBean> list = new ArrayList<FormBean>();
    }

    @Test
    public void testName() throws Exception {
        Field[] fields = MyClazz.class.getFields();
        for (Field field : fields) {
            //1. Check if field is of Collection Type
            if (Collection.class.isAssignableFrom(field.getType())) {
                //2. Get Generic type of your field
                Class fieldGenericType = getFieldGenericType(field);
                //3. Compare with <FromBean>
                Assert.assertTrue("List<FormBean>",
                  FormBean.class.isAssignableFrom(fieldGenericType));
            }
        }
    }

    //Returns generic type of any field
    public Class getFieldGenericType(Field field) {
        if (ParameterizedType.class.isAssignableFrom(field.getGenericType().getClass())) {
            ParameterizedType genericType =
             (ParameterizedType) field.getGenericType();
            return ((Class)
              (genericType.getActualTypeArguments()[0])).getSuperclass();
        }
        //Returns dummy Boolean Class to compare with ValueObject & FormBean
        return new Boolean(false).getClass();
    }
}

1
những gì getFieldGenericTypefieldGenericTypenó không thể được tìm thấy
shareef


0

Sử dụng Reflection để có được Fieldnhững thứ này sau đó bạn chỉ có thể làm: field.genericTypeđể có được loại chứa thông tin chung chung.


Xem xét thêm một số mã ví dụ, nếu không thì điều này phù hợp hơn cho một nhận xét và không phải là một câu trả lời
Alexey Kamenskiy
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.