ตัวอย่างเช่นสมมติว่าคุณมีสองคลาส:
public class TestA {}
public class TestB extends TestA{}
ฉันมีวิธีที่ผลตอบแทนList<TestA>
และฉันต้องการที่จะโยนวัตถุทั้งหมดในรายการที่จะเพื่อที่ฉันจบลงด้วยTestB
List<TestB>
ตัวอย่างเช่นสมมติว่าคุณมีสองคลาส:
public class TestA {}
public class TestB extends TestA{}
ฉันมีวิธีที่ผลตอบแทนList<TestA>
และฉันต้องการที่จะโยนวัตถุทั้งหมดในรายการที่จะเพื่อที่ฉันจบลงด้วยTestB
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
เป็น subclass A
ของ ตอนนี้เกิดอะไรขึ้นกับList<A>
และList<B>
?
ปรากฎว่าList<B>
ไม่ใช่ subclass ของ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());
new List<TestB>
, loop และ cast?
ฉันคิดว่าคุณจะหล่อในทิศทางที่ผิด แต่ ... ถ้าวิธีการส่งกลับรายการของวัตถุแล้วจริงๆมันไม่ปลอดภัยที่จะโยนให้แก่TestA
TestB
โดยทั่วไปคุณกำลังขอให้คอมไพเลอร์ให้คุณดำเนินTestB
การกับชนิดTestA
ที่ไม่สนับสนุน
เนื่องจากนี่เป็นคำถามที่อ้างอิงกันอย่างกว้างขวางและคำตอบปัจจุบันส่วนใหญ่อธิบายว่าทำไมมันไม่ทำงาน (หรือเสนอแฮ็ค, การแก้ปัญหาที่เป็นอันตรายที่ฉันไม่เคยต้องการที่จะเห็นในรหัสการผลิต) ฉันคิดว่ามันเหมาะสมที่จะเพิ่มคำตอบอื่น ข้อผิดพลาดและทางออกที่เป็นไปได้
สาเหตุที่ทำให้วิธีนี้ใช้งานไม่ได้มีการระบุไว้แล้วในคำตอบอื่น ๆ : การแปลงที่ถูกต้องจริงหรือไม่ขึ้นอยู่กับประเภทของวัตถุที่มีอยู่ในรายการต้นฉบับ เมื่อมีวัตถุในรายการที่มีประเภทไม่เป็นประเภทTestB
แต่เป็นประเภทย่อยที่แตกต่างกันจากTestA
นั้นนักแสดงที่ไม่ถูกต้อง
แน่นอนการปลดเปลื้องอาจถูกต้อง บางครั้งคุณมีข้อมูลเกี่ยวกับประเภทที่ไม่พร้อมใช้งานสำหรับคอมไพเลอร์ ในกรณีเหล่านี้เป็นไปได้ที่จะส่งรายการ แต่โดยทั่วไปไม่แนะนำ :
อย่างใดอย่างหนึ่งสามารถ ...
ความหมายของวิธีแรก (ซึ่งสอดคล้องกับคำตอบที่ยอมรับในปัจจุบัน) นั้นบอบบาง ดูเหมือนว่ามันจะทำงานได้อย่างถูกต้องตั้งแต่แรกเห็น แต่ถ้ามีประเภทที่ไม่ถูกต้องในรายการอินพุทแล้วClassCastException
จะถูกโยนอาจจะอยู่ในตำแหน่งที่แตกต่างอย่างสิ้นเชิงในรหัสและมันอาจเป็นเรื่องยากที่จะแก้จุดบกพร่องนี้และเพื่อหาว่าองค์ประกอบที่ผิดลื่นลงในรายการ ปัญหาที่เลวร้ายที่สุดคือบางคนอาจเพิ่มองค์ประกอบที่ไม่ถูกต้องหลังจากนั้นรายการถูกโยนทำให้การดีบักยากขึ้น
ปัญหาของการแก้จุดบกพร่องเหล่านี้ปลอมClassCastExceptions
สามารถบรรเทากับCollections#checkedCollection
ครอบครัวของวิธีการ
วิธีที่ปลอดภัยกว่าสำหรับการแปลงจาก a เป็นList<Supertype>
a 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;
}
วิธีนี้สามารถนำมาใช้เพื่อกรองรายการโดยพลการ (ไม่เพียง แต่มีความสัมพันธ์ Subtype-Supertype ที่กำหนดเกี่ยวกับพารามิเตอร์ประเภท) ดังที่แสดงในตัวอย่างนี้:
// 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>
ขณะที่สตีฟ Kuo กล่าวถึง แต่คุณสามารถถ่ายโอนข้อมูลเนื้อหาของเข้าList<TestA>
List<TestB>
ลองทำสิ่งต่อไปนี้:
List<TestA> result = new List<TestA>();
List<TestB> data = new List<TestB>();
result.addAll(data);
ฉันไม่ได้ลองใช้รหัสนี้ดังนั้นอาจมีข้อผิดพลาด แต่แนวคิดก็คือควรวนซ้ำข้อมูลวัตถุที่เพิ่มองค์ประกอบ (วัตถุ TestB) ลงในรายการ ฉันหวังว่าจะเหมาะกับคุณ
วิธีที่ปลอดภัยที่สุดคือการนำ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
}
วิธีเดียวที่ฉันรู้คือการคัดลอก:
List<TestB> list = new ArrayList<TestB> (
Arrays.asList (
testAList.toArray(new TestB[0])
)
);
เมื่อคุณส่งการอ้างอิงวัตถุคุณเพียงแคสต์ชนิดของการอ้างอิงไม่ใช่ประเภทของวัตถุ การคัดเลือกนักแสดงจะไม่เปลี่ยนประเภทของวัตถุจริง
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));
}
นี่เป็น verbose มากกว่าในภาษาที่มีการสนับสนุนโดยตรง แต่ใช้งานได้และคุณไม่จำเป็นต้องทำสิ่งนี้บ่อยนัก
ถ้าคุณมีวัตถุของคลาสที่คุณไม่สามารถโยนมันทิ้งไป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
คุณสามารถใช้selectInstances
วิธีการในEclipse คอลเลกชัน สิ่งนี้จะเกี่ยวข้องกับการสร้างคอลเลกชันใหม่อย่างไรก็ตามจะไม่มีประสิทธิภาพเท่ากับโซลูชันที่ยอมรับซึ่งใช้การคัดเลือกนักแสดง
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> 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;
ทำงานได้ดีทั้งคู่เป็นจำนวนเต็มทั้งคู่มีคลาสเดียวกันดังนั้น "ไม่มีอะไรที่จะส่ง"
ปัญหาคือว่าวิธีการของคุณจะไม่ส่งกลับรายการ TestA หากมันมี TestB ดังนั้นถ้ามันถูกพิมพ์อย่างถูกต้อง? จากนั้นนักแสดงนี้:
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
สิ่งนี้น่าจะใช้ได้
List<TestA> testAList = new ArrayList<>();
List<TestB> testBList = new ArrayList<>()
testAList.addAll(new ArrayList<>(testBList));
ค่อนข้างแปลกที่การส่งรายการด้วยตนเองยังไม่ได้รับการจัดเตรียมโดยกล่องเครื่องมือบางอย่างที่ใช้สิ่งต่อไปนี้:
@SuppressWarnings({ "unchecked", "rawtypes" })
public static <T extends E, E> List<T> cast(List<E> list) {
return (List) list;
}
แน่นอนว่าสิ่งนี้จะไม่ตรวจสอบรายการทีละรายการ แต่นั่นคือสิ่งที่เราต้องการหลีกเลี่ยงที่นี่หากเรารู้ดีว่าการใช้งานของเรามีเพียงประเภทย่อยเท่านั้น