java.util.List의 일반 유형을 가져옵니다.


262

나는 가지고있다;

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

목록의 일반 유형을 검색하는 쉬운 방법이 있습니까?


프로그래밍 방식으로 List 개체를 검사하고 일반 형식을 볼 수 있습니다. 메소드는 콜렉션의 일반 유형을 기반으로 오브젝트를 삽입하려고 할 수 있습니다. 컴파일 타임 대신 런타임에 제네릭을 구현하는 언어에서 가능합니다.
Steve Kuo

4
런타임 감지를 허용하는 유일한 방법은 하위 클래스를 이용하는 것입니다. 실제로 일반 유형을 확장 한 다음 하위 유형이 사용한 리플렉션 찾기 유형 선언을 사용할 수 있습니다. 이것은 약간의 반성이지만 가능합니다. 불행히도 일반 서브 클래스를 사용해야하는 쉬운 방법은 없습니다.
StaxMan

1
분명히 stringList는 문자열과 integerList 정수를 포함합니까? 왜 더 복잡하게 만드나요?
Ben Thurley

답변:


408

이들이 실제로 특정 클래스의 필드라면 약간의 반성으로 필드를 얻을 수 있습니다.

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

매개 변수 유형 및 리턴 유형 메소드에 대해서도이를 수행 할 수 있습니다.

그러나 그들이 당신이 그들에 대해 알아야 할 클래스 / 방법의 동일한 범위 내에 있다면, 당신이 이미 스스로 선언했기 때문에 그것을 알 필요가 없습니다.


17
이것이 유용한 상황 일 수도 있습니다. 예를 들어 구성이없는 ORM 프레임 워크에서.
BalusC 2009

3
..Class # getDeclaredFields ()를 사용하여 필드 이름을 몰라도 모든 필드를 가져올 수 있습니다.
BalusC 2009

1
BalusC : 그것은 저에게 주입 프레임 워크처럼 들립니다. 그것은 어쨌든 내가 사용했던 종류입니다.
falstro

1
@loolooyyyy TypeLiteral을 사용하는 대신 Guava의 TypeToken을 사용하는 것이 좋습니다. github.com/google/guava/wiki/ReflectionExplained
Babyburger

1
@Oleg 변수가 (클래스 유형) <?>대신 (와일드 카드 유형)을 사용하고 <Class>있습니다. <?><Class>또는 무엇이든 교체하십시오 <E>.
BalusC

19

메소드 매개 변수에 대해서도 동일한 작업을 수행 할 수 있습니다.

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

method.getGenericParameterTypes ()에서; 방법은 무엇입니까?
네오 라비

18

짧은 대답 : 아닙니다.

이것은 아마도 중복 일 것입니다. 지금은 적절한 것을 찾을 수 없습니다.

Java는 유형 삭제라는 것을 사용합니다. 즉, 런타임시 두 객체가 동일합니다. 컴파일러는 목록에 정수 또는 문자열이 포함되어 있으므로 형식이 안전한 환경을 유지할 수 있다는 것을 알고 있습니다. 이 정보는 런타임에 (객체 인스턴스별로) 손실되며 목록에는 '개체'만 포함됩니다.

클래스에 대해 조금, 매개 변수화 할 수있는 유형을 알 수 있지만 일반적으로 이것은 "Object"를 확장하는 것, 즉 무엇이든입니다. 유형을 다음과 같이 정의하면

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

AClass.class는 매개 변수 A가 MyClass에 의해 제한된다는 사실 만 포함하지만 그 이상으로 말할 방법이 없습니다.


1
제네릭의 구체적 클래스가 지정되지 않은 경우에도 마찬가지입니다. 그의 예에서, 그는리스트를 List<Integer>and 로 명시 적으로 선언 하고있다 List<String>; 이 경우 유형 삭제가 적용되지 않습니다.
Haroldo_OK

13

컬렉션의 제네릭 형식은 실제로 개체가 들어있는 경우에만 중요합니다. 따라서 수행하기가 쉽지 않습니다.

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

런타임에는 제네릭 형식과 같은 것이 없지만 런타임에 내부의 객체는 선언 된 제네릭과 동일한 형식이어야하므로 처리하기 전에 항목의 클래스를 테스트하기 만하면됩니다.

당신이 할 수있는 또 다른 일은 단순히 목록을 처리하여 올바른 유형의 멤버를 얻거나 다른 사람들을 무시하거나 다르게 처리하는 것입니다.

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

그러면 클래스가 서브 클래스 인 모든 항목의 목록이 표시되며 필요한 항목 Number을 처리 할 수 ​​있습니다. 나머지 항목은 다른 목록으로 필터링되었습니다. 이들이 맵에 있으므로 원하는대로 처리하거나 무시할 수 있습니다.

다른 클래스의 항목을 모두 무시하려면 훨씬 간단 해집니다.

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

목록에 특정 클래스와 일치하는 항목 만 포함되도록 유틸리티 메소드를 작성할 수도 있습니다.

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

5
그리고 빈 컬렉션이 있다면? 또는 정수와 문자열을 가진 Collection <Object>가 있다면?
Falci

클래스 계약에는 최종 객체 목록 만 전달 될 수 있으며 목록이 null이거나 비어 있거나 목록 유형이 유효하지 않은 경우 메서드 호출은 null을 반환합니다.
ggb667

1
빈 컬렉션의 경우 런타임에 모든 목록 유형에 할당하는 것이 안전합니다. 그 안에 아무 것도 없기 때문에 유형 삭제가 발생한 후 목록은 목록입니다. 이 때문에 빈 컬렉션의 유형이 중요한 인스턴스는 생각할 수 없습니다.
Steve K

스레드에서 참조를 유지하고 생산자 소비자 시나리오에서 개체가 표시되기 시작한 후에 처리하면 "중요 할 수 있습니다". 목록에 잘못된 개체 유형이 있으면 나쁜 일이 발생할 수 있습니다. 계속하기 전에 목록에 적어도 하나의 항목이있을 때까지 잠을 자면서 처리를 중단하지 않아야한다고 생각합니다.
ggb667

그런 종류의 생산자 / 소비자 시나리오가있는 경우 목록에 넣을 수있는 항목이 설계가 잘못되었는지 확실하지 않고 특정 일반 유형을 갖도록 목록을 계획하십시오. 대신에 Objects 모음으로 선언 하고 목록에서 가져올 때 유형에 따라 적절한 처리기에 제공합니다.
Steve K

11

한 필드의 일반 유형을 찾는 경우 :

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

10

제네릭 형식의 반환 형식을 가져와야하는 경우 클래스에서 메서드를 찾아서 Collection제네릭 형식에 액세스해야 할 때이 방법 을 사용했습니다.

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

    }

}

출력 :

제네릭 형식은 클래스 java.lang.String입니다.


6

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

Steve K의 답변과 동일한 문제 : 목록이 비어 있으면 어떻게됩니까? 목록이 문자열과 정수를 포함하는 <Object> 유형 인 경우 어떻게합니까? 귀하의 코드는 목록의 첫 번째 항목이 문자열이고 정수가없는 경우에만 더 많은 문자열을 추가 할 수 있음을 의미합니다.
subrunner

목록이 비어 있으면 운이없는 것입니다. 목록이 Object이고 실제로 Object가 포함되어 있으면 괜찮지 만 여러 가지가 혼합되어 있으면 운이 좋지 않습니다. 삭제는 이것이 가능한 한 좋다는 것을 의미합니다. 제 생각에는 소거가 부족합니다. 그것의 결과는 한 번에 잘 이해되지 않았으며 전형적인 관심사에 대한 흥미롭고 바람직한 사용 사례를 다루기에는 불충분합니다. 어떤 유형이 필요한지 묻는 클래스를 만들 수는 없지만 내장 클래스가 작동하는 방식은 아닙니다. 컬렉션은 무엇을 포함하고 있는지 알아야하지만 아아 ...
ggb667

4

런타임시에는 불가능합니다.

그러나 리플렉션을 통해 유형 매개 변수 액세스 할 수 있습니다. 시험

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

이 메소드 getGenericType()는 Type 객체를 반환합니다. 이 경우,의 인스턴스가되며,이 경우 ParametrizedType메소드 getRawType()( List.class이 경우 에는 포함 )와 getActualTypeArguments()배열 (이 경우에는 길이가 1 String.class또는 하나 포함 Integer.class) 을 리턴합니다 .


2
그리고 클래스의 필드 대신 메소드가 수신 한 매개 변수에 대해 작동합니까 ???
열립니다

@opensas method.getGenericParameterTypes()선언 된 메소드 매개 변수 유형을 가져 오는 데 사용할 수 있습니다 .
Radiodef

4

같은 문제가 있었지만 대신 instanceof를 사용했습니다. 이런 식으로 했습니까?

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

여기에는 검사되지 않은 캐스트 사용이 포함되므로 목록임을 알 수 있고 유형이 무엇인지 알 때만 수행하십시오.


3

일반적으로 불가능하기 때문에 List<String>List<Integer>같은 런타임 클래스를 공유 할 수 있습니다.

그러나 목록을 보유한 필드의 선언 된 유형을 반영 할 수 있습니다 (선언 된 유형 자체가 값을 모르는 유형 매개 변수를 참조하지 않는 경우).


2

다른 사람들이 말했듯이, 유일한 정답은 아니오입니다. 유형이 지워졌습니다.

리스트에 0이 아닌 요소가있는 경우 첫 번째 요소의 유형을 조사 할 수 있습니다 (예 : getClass 메소드 사용). 그것은 당신에게리스트의 제네릭 타입을 말하지는 않지만 제네릭 타입이리스트에있는 타입의 수퍼 클래스라고 가정하는 것이 합리적입니다.

나는 접근 방식을 옹호하지는 않지만 바인드에서는 유용 할 수 있습니다.


제네릭의 구체적 클래스가 지정되지 않은 경우에도 마찬가지입니다. 그의 예에서, 그는리스트를 List<Integer>and 로 명시 적으로 선언 하고있다 List<String>; 이 경우 유형 삭제가 적용되지 않습니다.
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
무엇이며 getFieldGenericTypefieldGenericType그것을 찾을 수 없습니다
샤리프


0

Reflection을 사용 Field하여 이것들 을 얻으면 다음과 같이 할 수 있습니다 : field.genericTypegeneric에 대한 정보가 들어있는 유형을 얻으려면.


예제 코드를 추가해보십시오. 그렇지 않으면 주석이 아닌 대답에 더 적합합니다
Alexey Kamenskiy
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.