วิธีที่มีประสิทธิภาพที่สุดในการส่ง List <SubClass> ไปยัง List <BaseClass>


139

ฉันมีสิ่งList<SubClass>ที่ฉันต้องการรักษาเป็นไฟล์List<BaseClass>. ดูเหมือนว่ามันไม่น่าจะเป็นปัญหาเนื่องจากการแคสต์ a SubClassto a BaseClassเป็นเรื่องง่าย แต่คอมไพเลอร์ของฉันบ่นว่าการแคสต์เป็นไปไม่ได้

ดังนั้นวิธีที่ดีที่สุดในการอ้างอิงถึงวัตถุเดียวกันกับ a List<BaseClass>คืออะไร?

ตอนนี้ฉันเพิ่งสร้างรายการใหม่และคัดลอกรายการเก่า:

List<BaseClass> convertedList = new ArrayList<BaseClass>(listOfSubClass)

แต่อย่างที่เข้าใจว่าต้องสร้างรายการใหม่ทั้งหมด ฉันต้องการอ้างอิงรายการต้นฉบับถ้าเป็นไปได้!


3
มีคำตอบที่นี่: stackoverflow.com/questions/662508/…
lukastymo

คำตอบ:


179

ไวยากรณ์สำหรับการกำหนดประเภทนี้ใช้สัญลักษณ์แทน:

List<SubClass> subs = ...;
List<? extends BaseClass> bases = subs;

มันเป็นสิ่งสำคัญที่จะทราบว่าList<SubClass>เป็นไม่ได้List<BaseClass>แทนกันด้วย รหัสที่มีการอ้างอิงถึงList<SubClass>จะคาดหวังว่าทุกรายการในรายการจะเป็นไฟล์SubClass. ถ้าส่วนหนึ่งของรหัสอื่นเรียกว่ารายการเป็นList<BaseClass>คอมไพเลอร์จะไม่บ่นเมื่อBaseClassหรือAnotherSubClassถูกแทรก แต่สิ่งนี้จะทำให้เกิดClassCastExceptionโค้ดชิ้นแรกซึ่งถือว่าทุกอย่างในรายการเป็นไฟล์SubClass.

คอลเลกชันทั่วไปไม่ทำงานเหมือนกับอาร์เรย์ใน Java อาร์เรย์มีความแปรปรวนร่วม นั่นคือได้รับอนุญาตให้ทำสิ่งนี้:

SubClass[] subs = ...;
BaseClass[] bases = subs;

สิ่งนี้ได้รับอนุญาตเนื่องจากอาร์เรย์ "รู้" ประเภทขององค์ประกอบ หากมีคนพยายามจัดเก็บสิ่งที่ไม่ใช่อินสแตนซ์ของSubClassอาร์เรย์ (ผ่านการbasesอ้างอิง) ข้อยกเว้นรันไทม์จะถูกโยนทิ้ง

คอลเล็กชันทั่วไปไม่ "ทราบ" ประเภทส่วนประกอบ ข้อมูลนี้จะถูก "ลบ" ในเวลาคอมไพล์ ดังนั้นจึงไม่สามารถเพิ่มข้อยกเว้นรันไทม์เมื่อเกิดการจัดเก็บที่ไม่ถูกต้อง แต่ClassCastExceptionจะมีการเพิ่มจุดที่อยู่ห่างไกลและเชื่อมโยงได้ยากในโค้ดเมื่ออ่านค่าจากคอลเล็กชัน หากคุณปฏิบัติตามคำเตือนของคอมไพลเลอร์เกี่ยวกับความปลอดภัยของประเภทคุณจะหลีกเลี่ยงข้อผิดพลาดประเภทนี้เมื่อรันไทม์


ควรสังเกตว่าด้วย Arrays แทนที่จะเป็น ClassCastException เมื่อดึงอ็อบเจ็กต์ที่ไม่ใช่ประเภท SubClass (หรือได้รับมา) คุณจะได้รับ ArrayStoreException เมื่อทำการแทรก
Axel

ขอบคุณโดยเฉพาะอย่างยิ่งสำหรับคำอธิบายว่าเหตุใดทั้งสองรายการจึงไม่สามารถพิจารณาได้ว่าเหมือนกัน
Riley Lark

ระบบประเภท Java แสร้งทำเป็นว่าอาร์เรย์เป็นโควาเรียกัน แต่ไม่สามารถทดแทนได้จริงซึ่งพิสูจน์โดย ArrayStoreException
Paŭlo Ebermann

39

erickson อธิบายแล้วว่าทำไมคุณถึงทำสิ่งนี้ไม่ได้ แต่นี่คือวิธีแก้ปัญหา:

หากคุณต้องการเพียงแค่นำองค์ประกอบออกจากรายการฐานของคุณโดยหลักการแล้ววิธีการรับของคุณควรได้รับการประกาศว่าใช้กList<? extends BaseClass>.

แต่ถ้าไม่ใช่และคุณไม่สามารถเปลี่ยนแปลงได้คุณสามารถรวมรายการด้วยCollections.unmodifiableList(...)ซึ่งอนุญาตให้ส่งคืน List of supertype ของพารามิเตอร์ของอาร์กิวเมนต์ (หลีกเลี่ยงปัญหาความปลอดภัยประเภทโดยการโยน UnsupportedOperationException ในการพยายามแทรก)


เยี่ยมมาก! ไม่รู้เรื่องนั้น
keuleJ

ขอบคุณมาก!
Woland

14

ดังที่ @erickson อธิบายไว้หากคุณต้องการอ้างอิงถึงรายการต้นฉบับจริงๆอย่าลืมใส่โค้ดใด ๆ ในรายการนั้นหากคุณต้องการใช้อีกครั้งภายใต้การประกาศเดิม วิธีที่ง่ายที่สุดในการรับมันคือเพียงแค่ส่งไปยังรายการที่ไม่ธรรมดาเก่า ๆ

List<BaseClass> baseList = (List)new ArrayList<SubClass>();

ฉันจะไม่แนะนำสิ่งนี้หากคุณไม่ทราบว่าเกิดอะไรขึ้นกับรายการและขอแนะนำให้คุณเปลี่ยนรหัสใด ๆ ที่ต้องการให้รายการยอมรับรายการที่คุณมี


3

ด้านล่างนี้เป็นตัวอย่างข้อมูลที่มีประโยชน์ที่ใช้งานได้ สร้างรายการอาร์เรย์ใหม่ แต่การสร้างวัตถุ JVM เหนือส่วนหัวมีนัยสำคัญ

ฉันเห็นว่าคำตอบอื่น ๆ นั้นซับซ้อนโดยไม่จำเป็น

List<BaseClass> baselist = new ArrayList<>(sublist);

1
ขอบคุณสำหรับข้อมูลโค้ดนี้ซึ่งอาจให้ความช่วยเหลือได้ทันที คำอธิบายที่เหมาะสมจะช่วยเพิ่มคุณค่าทางการศึกษาได้อย่างมากโดยแสดงให้เห็นว่าเหตุใดจึงเป็นวิธีแก้ปัญหาที่ดีและจะทำให้มีประโยชน์มากขึ้นสำหรับผู้อ่านในอนาคตที่มีคำถามคล้ายกัน แต่ไม่เหมือน โปรดแก้ไขคำตอบของคุณเพื่อเพิ่มคำอธิบายและระบุข้อ จำกัด และสมมติฐานที่ใช้ โดยเฉพาะอย่างยิ่งสิ่งนี้แตกต่างจากรหัสในคำถามที่สร้างรายการใหม่อย่างไร
Toby Speight

ค่าโสหุ้ยไม่สำคัญเนื่องจากต้อง (ตื้น) คัดลอกการอ้างอิงทุกรายการ ด้วยเหตุนี้จึงปรับขนาดตามขนาดของรายการดังนั้นจึงเป็นการดำเนินการ O (n)
john16384

2

ฉันพลาดคำตอบที่คุณเพิ่งแคสต์รายการดั้งเดิมโดยใช้นักแสดงสองคน ดังนั้นนี่คือความสมบูรณ์:

List<BaseClass> baseList = (List<BaseClass>)(List<?>)subList;

ไม่มีการคัดลอกและการดำเนินการก็รวดเร็ว อย่างไรก็ตามคุณกำลังหลอกคอมไพเลอร์ที่นี่ดังนั้นคุณต้องแน่ใจอย่างยิ่งว่าจะไม่แก้ไขรายการในลักษณะที่subListเริ่มมีรายการย่อยที่แตกต่างกัน เมื่อจัดการกับรายการที่ไม่เปลี่ยนรูปมักจะไม่เป็นปัญหา


1

List<BaseClass> convertedList = Collections.checkedList(listOfSubClass, BaseClass.class)


5
สิ่งนี้ไม่ควรได้ผลเนื่องจากวิธีนี้อาร์กิวเมนต์ทั้งหมดและผลลัพธ์ใช้พารามิเตอร์ชนิดเดียวกัน
Paŭlo Ebermann

แปลกคุณพูดถูก ด้วยเหตุผลบางประการฉันรู้สึกประทับใจวิธีนี้มีประโยชน์สำหรับการอัปเดตคอลเล็กชันทั่วไป ยังสามารถใช้วิธีนี้ได้และจะให้คอลเล็กชัน "ปลอดภัย" หากคุณต้องการใช้การปราบปราม
jtahlborn

1

สิ่งที่คุณพยายามทำมีประโยชน์มากและฉันพบว่าฉันต้องทำบ่อยๆในโค้ดที่ฉันเขียน ตัวอย่างการใช้งาน:

บอกว่าเรามีอินเตอร์เฟซFooและเรามีzorkingแพคเกจที่มีที่สร้างและจัดการกรณีของแพคเกจและเอกชนZorkingFooManager ZorkingFoo implements Foo(สถานการณ์ที่พบบ่อยมาก)

ดังนั้นZorkingFooManagerจำเป็นต้องมีprivate Collection<ZorkingFoo> zorkingFoosแต่ต้องแสดงไฟล์public Collection<Foo> getAllFoos().

โปรแกรมเมอร์ java ส่วนใหญ่จะไม่คิดซ้ำสองก่อนที่จะใช้getAllFoos()เป็นการจัดสรรใหม่ArrayList<Foo>โดยเติมองค์ประกอบทั้งหมดจากzorkingFoosและส่งคืน ฉันสนุกกับความบันเทิงที่คิดว่าประมาณ 30% ของรอบนาฬิกาทั้งหมดที่ใช้โดยโค้ดจาวาที่ทำงานบนเครื่องนับล้านทั่วโลกไม่ได้ทำอะไรเลยนอกจากการสร้างสำเนา ArrayLists ที่ไร้ประโยชน์ซึ่งเป็นไมโครวินาทีที่เก็บขยะหลังจากการสร้าง

วิธีแก้ปัญหานี้คือการหล่อคอลเลกชันลง นี่คือวิธีที่ดีที่สุด:

static <T,U extends T> List<T> downCastList( List<U> list )
{
    return castList( list );
}

ซึ่งนำเราไปสู่castList()ฟังก์ชัน:

static <T,E> List<T> castList( List<E> list )
{
    @SuppressWarnings( "unchecked" )
    List<T> result = (List<T>)list;
    return result;
}

resultตัวแปรกลางเป็นสิ่งที่จำเป็นเนื่องจากการบิดเบือนของภาษาจาวา:

  • return (List<T>)list;สร้างข้อยกเว้น "การร่ายที่ไม่ถูกตรวจสอบ" จนถึงตอนนี้ดีมาก แต่แล้ว:

  • @SuppressWarnings( "unchecked" ) return (List<T>)list; เป็นการใช้คำอธิบายประกอบการระงับคำเตือนที่ผิดกฎหมาย

ดังนั้นแม้ว่าจะไม่ใช้โคเชอร์@SuppressWarningsในreturnคำสั่ง แต่ก็เห็นได้ชัดว่าสามารถใช้กับงานมอบหมายได้ดังนั้นตัวแปร "ผลลัพธ์" พิเศษจะช่วยแก้ปัญหานี้ได้ (ควรปรับให้เหมาะสมที่สุดไม่ว่าจะโดยคอมไพเลอร์หรือโดย JIT ก็ตาม)


1

วิธีการหล่อองค์ประกอบทั้งหมด มันจะสร้างรายการใหม่ แต่จะอ้างอิงวัตถุเดิมจากรายการเก่า

List<BaseClass> convertedList = listOfSubClass.map(x -> (BaseClass)x).collect(Collectors.toList());

นี่คือโค้ดทั้งหมดที่ใช้ในการแคสรายการคลาสย่อยไปยังระดับซุปเปอร์คลาส
kanaparthikiran

0

นี่คือส่วนการทำงานที่สมบูรณ์ของโค้ดโดยใช้ Generics เพื่อส่งรายการคลาสย่อยไปยังคลาสระดับสูง

วิธีการโทรที่ส่งผ่านประเภทย่อย

List<SubClass> subClassParam = new ArrayList<>();    
getElementDefinitionStatuses(subClassParam);

วิธีการ Callee ที่ยอมรับประเภทย่อยของคลาสพื้นฐาน

private static List<String> getElementDefinitionStatuses(List<? extends 
    BaseClass> baseClassVariableName) {
     return allElementStatuses;
    }
}

-1

สิ่งนี้ควรใช้งานได้เช่นกัน:

public static <T> List<T> convertListWithExtendableClasses(
    final List< ? extends T> originalList,
    final Class<T> clazz )
{
    final List<T> newList = new ArrayList<>();
    for ( final T item : originalList )
    {
        newList.add( item );
    }// for
    return newList;
}

ไม่รู้จริงๆว่าทำไมต้องใช้ clazz ใน Eclipse ..

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.