IT story

수퍼 타입 ​​목록을 하위 유형 목록으로 캐스트하는 방법은 무엇입니까?

hot-time 2020. 4. 16. 08:30
반응형

수퍼 타입 ​​목록을 하위 유형 목록으로 캐스트하는 방법은 무엇입니까?


예를 들어, 두 개의 클래스가 있다고 가정 해 봅시다.

public class TestA {}
public class TestB extends TestA{}

나는 a를 반환하는 메소드를 가지고 List<TestA>있으며 그 목록의 모든 객체를 캐스트 TestB하여 a로 끝납니다 List<TestB>.


List<TestB>거의 작업에 캐스팅하기 만하면됩니다 . 그러나 한 유형의 매개 변수를 다른 유형으로 캐스트 할 수 없기 때문에 작동하지 않습니다. 그러나 중간 와일드 카드 유형을 통해 캐스트 할 수 있으며 허용되지 않습니다 (확인하지 않은 경고만으로 와일드 카드 유형으로 캐스트 할 수 있기 때문에).

List<TestB> variable = (List<TestB>)(List<?>) collectionOfListA;

제네릭 캐스팅은 불가능하지만 다른 방법으로 목록을 정의하면 TestB를 저장할 수 있습니다.

List<? extends TestA> myList = new ArrayList<TestA>();

목록의 객체를 사용할 때 유형 검사를 계속해야합니다.


당신은 정말로 할 수 없습니다 * :

이 Java 자습서 에서 예를 가져옵니다.

두 가지 유형이 있습니다 가정 AB같은 그 B extends A. 그런 다음 다음 코드가 맞습니다.

    B b = new B();
    A a = b;

이전 코드는 B의 하위 클래스 이므로 유효합니다 A. 자, 함께 발생 List<A>하고 List<B>?

그것은 밝혀 List<B>의 서브 클래스가 아닌 List<A>그러므로 우리가 할 수없는 쓰기

    List<B> b = new ArrayList<>();
    List<A> a = b; // error, List<B> is not of type List<A>

게다가, 우리 수 없습니다

    List<B> b = new ArrayList<>();
    List<A> a = (List<A>)b; // error, List<B> is not of type List<A>

* : 우리는 모두 공통의 부모를 필요로 캐스팅을 가능하게하는 방법 List<A>List<B>: List<?>예를 들면. 다음 유효합니다 :

    List<B> b = new ArrayList<>();
    List<?> t = (List<B>)b;
    List<A> a = (List<A>)t;

그러나 경고가 표시됩니다. @SuppressWarnings("unchecked")메소드 에 추가 하여 억제 할 수 있습니다 .


Java 8을 사용하면 실제로

List<TestB> variable = collectionOfListA
    .stream()
    .map(e -> (TestB) e)
    .collect(Collectors.toList());

그래도 잘못된 방향으로 캐스팅하고 있다고 생각합니다 ... 메서드가 TestA객체 목록을 반환 하면 실제로 객체를 캐스팅하는 것이 안전하지 않습니다 TestB.

기본적으로 컴파일러에서 지원하지 않는 TestB유형에 대한 작업을 수행 할 수 있도록 컴파일러에 요청 TestA합니다.


이것은 널리 참조되는 질문이며 현재 답변은 왜 그것이 작동하지 않는지 (또는 프로덕션 코드에서 결코 보지 못했던 해킹하고 위험한 솔루션을 제안합니다) 주로 설명하므로 다른 답변을 추가하는 것이 적절하다고 생각합니다. 함정 및 가능한 해결책.


이것이 일반적으로 작동하지 않는 이유는 이미 다른 답변에서 지적되었습니다. 변환이 실제로 유효한지 여부는 원래 목록에 포함 된 객체의 유형에 따라 다릅니다. 목록에 유형이 아닌의 TestB다른 서브 클래스 인 유형의 오브젝트가 있으면 TestA캐스트가 유효 하지 않습니다.


물론 캐스트 유효 할 수 있습니다. 때로는 컴파일러에서 사용할 수없는 유형에 대한 정보가 있습니다. 이 경우 목록을 전송할 수 있지만 일반적으로 권장하지 않습니다 .

하나는 ...

  • ... 전체 목록을 캐스팅하거나
  • ... 목록의 모든 요소를 ​​캐스트

첫 번째 접근 방식 (현재 허용되는 답변에 해당)의 의미는 미묘합니다. 언뜻 보면 제대로 작동하는 것 같습니다. 그러나 입력 목록에 잘못된 유형이 있으면 ClassCastException코드에서 완전히 다른 위치에 a가 발생하고이를 디버깅하고 잘못된 요소가 목록에서 미끄러지는 위치를 찾는 것이 어려울 수 있습니다. 최악의 문제는 목록을 캐스팅 한 후 누군가가 잘못된 요소를 추가하여 디버깅을 더욱 어렵게 한다는 것 입니다.

이러한 스퓨리어스를 디버깅하는 문제는 메소드 계열 ClassCastExceptions로 완화 될 수 있습니다 Collections#checkedCollection.


유형을 기준으로 목록 필터링

a에서 a List<Supertype>변환하는 더 안전한 형식의 방법은 List<Subtype>실제로 목록을 필터링 하고 특정 유형의 요소 만 포함하는 새 목록을 만드는 것입니다. 그러한 방법의 구현에 대한 자유도가 어느 정도 있지만 (예를 들어 null, 엔트리 의 처리와 관련하여 ), 한 가지 가능한 구현은 다음과 같습니다.

/**
 * Filter the given list, and create a new list that only contains
 * the elements that are (subtypes) of the class c
 * 
 * @param listA The input list
 * @param c The class to filter for
 * @return The filtered list
 */
private static <T> List<T> filter(List<?> listA, Class<T> c)
{
    List<T> listB = new ArrayList<T>();
    for (Object a : listA)
    {
        if (c.isInstance(a))
        {
            listB.add(c.cast(a));
        }
    }
    return listB;
}

이 방법은 다음 예제와 같이 임의의 목록을 필터링하기 위해 사용할 수 있습니다 (유형 매개 변수와 관련하여 주어진 하위 유형-슈퍼 유형 관계뿐 아니라).

// A list of type "List<Number>" that actually 
// contains Integer, Double and Float values
List<Number> mixedNumbers = 
    new ArrayList<Number>(Arrays.asList(12, 3.4, 5.6f, 78));

// Filter the list, and create a list that contains
// only the Integer values:
List<Integer> integers = filter(mixedNumbers, Integer.class);

System.out.println(integers); // Prints [12, 78]

당신은 캐스트 수 없습니다 List<TestB>List<TestA>스티브 쿠오 언급하지만 당신의 내용을 덤프 수 List<TestA>에를 List<TestB>. 다음을 시도하십시오 :

List<TestA> result = new List<TestA>();
List<TestB> data = new List<TestB>();
result.addAll(data);

나는이 코드를 시도하지 않았으므로 실수가있을 수 있지만 아이디어는 요소 (TestB 객체)를 List에 추가하는 데이터 객체를 반복해야한다는 것입니다. 나는 그것이 당신을 위해 작동하기를 바랍니다.


가장 안전한 방법은 구현시 AbstractList캐스트 아이템을 구현하는 것입니다. ListUtil도우미 클래스를 만들었습니다 .

public class ListUtil
{
    public static <TCastTo, TCastFrom extends TCastTo> List<TCastTo> convert(final List<TCastFrom> list)
    {
        return new AbstractList<TCastTo>() {
            @Override
            public TCastTo get(int i)
            {
                return list.get(i);
            }

            @Override
            public int size()
            {
                return list.size();
            }
        };
    }

    public static <TCastTo, TCastFrom> List<TCastTo> cast(final List<TCastFrom> list)
    {
        return new AbstractList<TCastTo>() {
            @Override
            public TCastTo get(int i)
            {
                return (TCastTo)list.get(i);
            }

            @Override
            public int size()
            {
                return list.size();
            }
        };
    }
}

cast메소드를 사용 하여 목록의 오브젝트를 맹목적으로 캐스트 convert하고 안전하게 캐스트 하는 방법을 사용할 수 있습니다 . 예:

void test(List<TestA> listA, List<TestB> listB)
{
    List<TestB> castedB = ListUtil.cast(listA); // all items are blindly casted
    List<TestB> convertedB = ListUtil.<TestB, TestA>convert(listA); // wrong cause TestA does not extend TestB
    List<TestA> convertedA = ListUtil.<TestA, TestB>convert(listB); // OK all items are safely casted
}

객체 참조를 캐스트 할 때는 객체의 유형이 아니라 참조의 유형 만 캐스트합니다. 캐스팅은 객체의 실제 유형을 변경하지 않습니다.

Java에는 객체 유형 변환에 대한 암시 적 규칙이 없습니다. (원시와는 달리)

대신 한 유형을 다른 유형으로 변환하고 수동으로 호출하는 방법을 제공해야합니다.

public class TestA {}
public class TestB extends TestA{ 
    TestB(TestA testA) {
        // build a TestB from a TestA
    }
}

List<TestA> result = .... 
List<TestB> data = new List<TestB>();
for(TestA testA : result) {
   data.add(new TestB(testA));
}

직접 지원하는 언어보다 더 장황하지만 작동하기 때문에 자주 수행 할 필요는 없습니다.


내가 아는 유일한 방법은 복사하는 것입니다.

List<TestB> list = new ArrayList<TestB> (
    Arrays.asList (
        testAList.toArray(new TestB[0])
    )
);

유형 삭제로 인해 가능합니다. 당신은 그것을 찾을 것입니다

List<TestA> x = new ArrayList<TestA>();
List<TestB> y = new ArrayList<TestB>();
x.getClass().equals(y.getClass()); // true

내부적으로 두 목록은 모두 유형 List<Object>입니다. 이런 이유로 당신은 다른 것을 던질 수 없습니다-던질 것은 없습니다.


클래스의 객체가 있으면에 TestA캐스팅 할 수 없습니다 TestB. 모두 TestBTestA이지만 다른 방법은 아닙니다.

다음 코드에서 :

TestA a = new TestA();
TestB b = (TestB) a;

두 번째 줄은 ClassCastException.

TestA객체 자체가 인 경우 에만 참조를 캐스트 할 수 있습니다 TestB. 예를 들면 다음과 같습니다.

TestA a = new TestB();
TestB b = (TestB) a;

그래서, 당신은 항상의 목록을 캐스팅하지 않을 수 TestA의 목록 TestB.


문제는 TestB에 TestA가 포함되어 있으면 메소드가 TestA 목록을 반환하지 않는다는 것입니다. 올바르게 입력 한 경우 어떻게됩니까? 그런 다음이 캐스트 :

class TestA{};
class TestB extends TestA{};
List<? extends TestA> listA;
List<TestB> listB = (List<TestB>) listA;

(Eclipse는 당신이하고있는 것과 정확히 일치하지 않는 캐스트에 대해 경고합니다.) 따라서 이것을 사용하여 문제를 해결할 수 있습니까? 실제로 당신은 이것 때문에 할 수 있습니다 :

List<TestA> badlist = null; // Actually contains TestBs, as specified
List<? extends TestA> talist = badlist;  // Umm, works
List<TextB> tblist = (List<TestB>)talist; // TADA!

정확히 네가 요구 한 것 맞지? 또는 정말 정확합니다.

List<TestB> tblist = (List<TestB>)(List<? extends TestA>) badlist;

나를 위해 잘 컴파일 된 것 같습니다.


class MyClass {
  String field;

  MyClass(String field) {
    this.field = field;
  }
}

@Test
public void testTypeCast() {
  List<Object> objectList = Arrays.asList(new MyClass("1"), new MyClass("2"));

  Class<MyClass> clazz = MyClass.class;
  List<MyClass> myClassList = objectList.stream()
      .map(clazz::cast)
      .collect(Collectors.toList());

  assertEquals(objectList.size(), myClassList.size());
  assertEquals(objectList, myClassList);
}

이 테스트 쇼는 어떻게 캐스팅 List<Object>List<MyClass>. 그러나 objectList와 동일한 유형의 인스턴스를 포함해야 한다는 점에주의 해야합니다 MyClass. 이 예 List<T>는 사용될 때 고려 될 수 있습니다 . 이를 위해 Class<T> clazz생성자에서 필드 가져 와서 대신 사용하십시오 MyClass.class.


Eclipse Collections 에서 selectInstances메소드를 사용할 수 있습니다 . 여기에는 새 모음을 만드는 것이 포함되므로 캐스팅을 사용하는 허용 된 솔루션만큼 효율적이지 않습니다.

List<CharSequence> parent =
        Arrays.asList("1","2","3", new StringBuffer("4"));
List<String> strings =
        Lists.adapt(parent).selectInstancesOf(String.class);
Assert.assertEquals(Arrays.asList("1","2","3"), strings);

StringBuffer예제에 포함되어 selectInstances형식을 다운 캐스트 할뿐만 아니라 컬렉션에 혼합 형식이 포함되어 있는지 필터링합니다.

참고 : 저는 Eclipse Collections의 커미터입니다.


이 작동합니다

List<TestA> testAList = new ArrayList<>();
List<TestB> testBList = new ArrayList<>()
testAList.addAll(new ArrayList<>(testBList));

참고 URL : https://stackoverflow.com/questions/933447/how-do-you-cast-a-list-of-supertypes-to-a-list-of-subtypes

반응형