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


244

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

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

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

답변:


578

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

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

88
모든면에서, 저는 이것이 일종의 해키 솔루션이라고 생각합니다. Java가 제공하려고하는 유형 안전을 피하고 있습니다. 최소한이 Java 학습서 ( docs.oracle.com/javase/tutorial/java/generics/subtyping.html )를 보고이 방법으로 수정하기 전에이 문제점이있는 이유를 고려하십시오.
— jfritz42

63
@ jfritz42 "ddging"타입 안전이라고 부르지 않습니다. 이 경우 Java에는없는 지식이 있습니다. 그것이 캐스팅을위한 것입니다.
— Planky

3
이렇게하면 다음과 같이 쓸 수 있습니다. List <String> myList = (List <String>) (List <?>) (new ArrayList <Integer> ()); 런타임에 충돌이 발생합니다. 나는 List <? 확장한다 Number> myList = new ArrayList <Integer> ();
— Bentaye

1
나는 @ jfritz42에 동의한다. 나는 똑같은 문제가 있었고 그가 제공 한 URL을보고 캐스팅없이 내 문제를 해결할 수 있었다.
— Sam Orozco

@ jfritz42 이것은 컴파일러가 허용하는 정수로 객체를 캐스트하는 것과 기능적으로 다르지 않습니다
— kcazllerraf

116

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

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

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


14
이것은 유효한 Java 구문조차 아닙니다.
— newacct December

2
사실이지만 사실보다 정확합니다.
— Salandur

createSingleString (List <? extends Object> objects)이 메서드 내에서 String.valueOf (Object)를 호출하여 목록을 하나의 문자열로 축소합니다. 입력이 List <Long>, List <Boolean> 등일 때 효과적입니다. 감사!
— Chris Sprague 2016 년

2
TestA가 인터페이스라면?
— Suvitruf-Andrei Apanasik

8
이것이 실제로 작동 하는 곳에 MCVE 를 줄 수 있습니까 ? 나는 얻는다 Cannot instantiate the type ArrayList<? extends TestA>.
— Nateowami

42

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

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

두 가지 유형이 있다고 가정 A하고B 같은 그 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")메소드 에 추가 하여 억제 할 수 있습니다 .


2
또한이 링크의 자료를 사용하면 컴파일러가 전체 변환을 해독 할 수 있으므로 확인되지 않은 변환을 피할 수 있습니다.
— Ryan H

29

Java 8을 사용하면 실제로

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

32
실제로 목록을 전송합니까? 전체 목록을 반복하는 일련의 연산과 유사하므로 각 요소를 캐스트 한 다음 원하는 유형의 새로 인스턴스화 된 목록에 추가합니다. 달리 말하면 new List<TestB>, Java7을 사용하여 정확하게 , 루프 및 캐스트를 사용할 수 없습니까?
— Patrick M

5
OP에서 "그리고 그 목록의 모든 객체를 캐스팅하고 싶습니다"-실제로, 이것은 사람들이 여기에서 탐색하는 질문이 아닌 경우에도 언급 된 질문에 대답하는 몇 가지 답변 중 하나입니다.
— Luke Usherwood

17

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

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


11

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


이것이 일반적으로 작동하지 않는 이유는 이미 다른 답변에서 지적되었습니다. 변환이 실제로 유효한지 여부는 원래 목록에 포함 된 객체의 유형에 따라 다릅니다. 목록에 유형이 아닌의 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]

7

당신은 캐스트 수 없습니다 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에 추가하는 데이터 객체를 반복해야한다는 것입니다. 나는 그것이 당신을 위해 작동하기를 바랍니다.


왜이 투표가 거부 되었습니까? 그것은 나에게 유용했고, 나는 투표에 대한 설명을 보지 못했다.
— zod

7
분명히이 게시물은 정확하지 않습니다. 그러나 질문을 한 사람은 마침내 TestB 목록이 필요합니다. 이 경우이 답변은 잘못된 것입니다. List <TestA> .addAll (List <TestB>)을 호출하여 List <TestB>의 모든 요소를 ​​List <TestA>에 추가 할 수 있습니다. 그러나 List <TestB> .addAll (List <TestA>);을 사용할 수 없습니다.
— prageeth

6

가장 안전한 방법은 구현시 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
}

4

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

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

2
나는 이것이 컴파일러에 경고를주지 않는다고 생각합니다.
— Simon Forsberg

이것은 배열과의 이상한 점입니다. 한숨, 자바 배열은 쓸모가 없습니다. 그러나 다른 답변보다 더 나쁘지 않고 읽기 쉽습니다. 나는 캐스트보다 더 이상 안전하지 않다는 것을 알지 못한다.
— Thufir

3

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

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

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


2

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

다음 코드에서 :

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

두 번째 줄은을 던질 것 ClassCastException입니다.

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

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

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


1
나는 그가 무언가에 대해 이야기하고 있다고 생각합니다 .- TestA a = new TestB ()로 생성 된 메소드 인수로 'a'를 얻은 다음 TestB 객체를 가져 와서 캐스팅해야합니다-TestB b = ( 시험 B) a;
— samsamara

2

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의 커미터입니다.


1

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

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

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


"캐스트 할 것이 없습니다"와 "당신은 다른 것을 던질 수 없습니다"는 약간 모순입니다. Integer j = 42; Integer i = (Integer) j;잘 작동합니다. 둘 다 정수입니다. 둘 다 같은 클래스를 가지고 있으므로 "캐스트 할 것이 없습니다".
— Simon Forsberg

1

문제는 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;

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


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


0

이 작동합니다

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

1
이것은 불필요한 사본을 만듭니다.
— defnull

0

목록을 수동으로 캐스팅하는 것은 여전히 ​​다음과 같은 일부 도구 상자에서 제공되지 않습니다.

@SuppressWarnings({ "unchecked", "rawtypes" })
public static <T extends E, E> List<T> cast(List<E> list) {
    return (List) list;
}

물론 이것은 항목을 하나씩 검사하지 않지만 구현이 하위 유형 만 제공한다는 것을 잘 알고 있다면 여기서 피하고 싶은 것입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.