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


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. 모두 TestBTestA이지만 다른 방법은 아닙니다.

다음 코드에서 :

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.