수퍼 타입 목록을 하위 유형 목록으로 캐스트하는 방법은 무엇입니까?
예를 들어, 두 개의 클래스가 있다고 가정 해 봅시다.
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 자습서 에서 예를 가져옵니다.
두 가지 유형이 있습니다 가정 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")
메소드 에 추가 하여 억제 할 수 있습니다 .
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
. 모두 TestB
는 TestA
이지만 다른 방법은 아닙니다.
다음 코드에서 :
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));
'IT story' 카테고리의 다른 글
줄 바꿈으로 PHP 문자열을 분해 (0) | 2020.04.16 |
---|---|
Ruby의 파일 경로에서 확장자가없는 파일 이름을 얻는 방법 (0) | 2020.04.16 |
C ++보다 C ++이 얼마나 빠릅니까? (0) | 2020.04.16 |
iOS 테스트 / 사양 TDD / BDD 및 통합 및 수락 테스트 (0) | 2020.04.16 |
Android 용 TextView 자동 맞춤 (0) | 2020.04.16 |