อะไรคือสาเหตุที่ฉันไม่สามารถสร้างประเภทอาร์เรย์ทั่วไปใน Java ได้


273

เหตุผลที่ Java ไม่อนุญาตให้เราทำอะไร

private T[] elements = new T[initialCapacity];

ฉันสามารถเข้าใจ. NET ไม่อนุญาตให้เราทำเช่นนั้นใน. NET คุณมีประเภทค่าที่เวลาทำงานสามารถมีขนาดแตกต่างกัน แต่ใน Java T ทุกชนิดจะเป็นการอ้างอิงวัตถุจึงมีขนาดเดียวกัน ( ช่วยแก้ให้ด้วยนะถ้าฉันผิด).

เหตุผลคืออะไร


29
คุณกำลังพูดเรื่องอะไร คุณสามารถทำสิ่งนี้ได้ใน. NET - ฉันอยู่ที่นี่พยายามคิดว่าทำไมฉันไม่สามารถทำใน Java
BrainSlugs83

@ BrainSlugs83 - โปรดเพิ่มลิงก์ไปยังตัวอย่างโค้ดหรือบทช่วยสอนซึ่งแสดงให้เห็นว่า
MasterJoe2


1
@ MasterJoe2 รหัสด้านบนในคำถามของ OP คือสิ่งที่ฉันอ้างถึง มันทำงานได้ดีใน C # แต่ไม่ใช่ใน Java - คำถามระบุว่ามันใช้งานได้ในทั้งสองซึ่งไม่ถูกต้อง - ไม่แน่ใจว่ามีคุณค่าในการพูดคุยเพิ่มเติม
BrainSlugs83

คำตอบ:


204

เป็นเพราะอาร์เรย์ของ Java (ต่างจาก generics) มีข้อมูลเกี่ยวกับประเภทส่วนประกอบ ดังนั้นคุณต้องรู้ประเภทส่วนประกอบเมื่อคุณสร้างอาร์เรย์ เนื่องจากคุณไม่ทราบว่าเกิดอะไรขึ้นTตอนรันไทม์คุณไม่สามารถสร้างอาร์เรย์ได้


29
แต่สิ่งที่เกี่ยวกับการลบ? ทำไมจึงไม่ใช้
Qix - MONICA ถูกยกเลิก

14
แล้วจะArrayList <SomeType>ทำอย่างไร?
Thumbz

10
@Thumbz: คุณหมายถึงnew ArrayList<SomeType>()อะไร ประเภททั่วไปไม่มีพารามิเตอร์ชนิดที่รันไทม์ พารามิเตอร์ type ไม่ถูกใช้ในการสร้าง ไม่มีความแตกต่างในรหัสที่สร้างขึ้นโดยเป็นnew ArrayList<SomeType>()หรือnew ArrayList<String>()หรือnew ArrayList()ที่ทุกคน
newacct

8
ฉันถูกถามเพิ่มเติมเกี่ยวกับวิธีArrayList<T>ทำงานร่วมกับมัน private T[] myArrayที่ไหนสักแห่งในรหัสมันจะต้องมีอาร์เรย์ประเภททั่วไป T ดังนั้นอย่างไร
Thumbz

21
@Thumbz: T[]มันไม่ได้มีอาร์เรย์ของประเภทรันไทม์ มันมีอาร์เรย์ของประเภทรันไทม์Object[]และ 1) รหัสที่มาทั้งมีตัวแปรของObject[](นี่คือวิธีการที่จะอยู่ในแหล่งที่มาใหม่ล่าสุดของ Oracle Java); หรือ 2) ซอร์สโค้ดมีตัวแปรชนิดT[]ซึ่งเป็นเรื่องโกหก แต่ไม่ทำให้เกิดปัญหาเนื่องจากTถูกลบออกภายในขอบเขตของคลาส
newacct

137

อ้างถึง:

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

class Box<T> {
    final T x;
    Box(T x) {
        this.x = x;
    }
}

class Loophole {
    public static void main(String[] args) {
        Box<String>[] bsa = new Box<String>[3];
        Object[] oa = bsa;
        oa[0] = new Box<Integer>(3); // error not caught by array store check
        String s = bsa[0].x; // BOOM!
    }
}

เราได้เสนอให้แก้ไขปัญหานี้โดยใช้อาร์เรย์ที่ปลอดภัยแบบคงที่ (aka Variance) bute ที่ถูกปฏิเสธสำหรับ Tiger

- gafter

(ฉันเชื่อว่านี่คือNeal Gafterแต่ไม่แน่ใจ)

ดูในบริบทที่นี่: http://forums.sun.com/thread.jspa?threadID=457033&forumID=316


3
โปรดทราบว่าฉันทำมันเป็น CW เนื่องจากคำตอบไม่ใช่ของฉัน
บาร์ต Kiers

10
สิ่งนี้อธิบายว่าทำไมมันจึงไม่ปลอดภัย แต่คอมไพเลอร์อาจมีปัญหาเรื่องความปลอดภัย new T()ความจริงก็คือว่ามันเป็นไปไม่ได้แม้จะทำมันเกือบเหตุผลเดียวกันว่าทำไมคุณไม่สามารถทำ แต่ละอาร์เรย์ใน Java โดยการออกแบบจะเก็บประเภทส่วนประกอบ (เช่นT.class) ภายใน ดังนั้นคุณต้องคลาสของ T ที่รันไทม์เพื่อสร้างอาร์เรย์ดังกล่าว
newacct

2
คุณยังสามารถใช้งานnew Box<?>[n]ได้ซึ่งบางครั้งก็เพียงพอแม้ว่ามันจะไม่ช่วยในตัวอย่างของคุณ
Bartosz Klimek

1
@BartKiers ฉันไม่เข้าใจเลย ... สิ่งนี้ยังไม่ได้รวบรวม (java-8): Box<String>[] bsa = new Box<String>[3];มีการเปลี่ยนแปลงอะไรใน java-8 ขึ้นไปฉันคิดว่า?
Eugene

1
@Eugene ไม่อนุญาตให้ใช้อาร์เรย์ประเภททั่วไปโดยเฉพาะเพราะอาจทำให้เกิดการสูญเสียความปลอดภัยประเภทดังที่แสดงในตัวอย่าง ไม่อนุญาตใน Java เวอร์ชันใด ๆ คำตอบที่จะเริ่มต้นเป็น "อะเรย์ของประเภททั่วไปไม่ได้รับอนุญาตเพราะพวกเขาไม่ได้เสียง."
โกเมน

47

เมื่อไม่สามารถให้ทางออกที่ดีคุณจะพบกับสิ่งที่เลวร้ายยิ่งกว่า IMHO

การทำงานทั่วไปมีดังนี้

T[] ts = new T[n];

ถูกแทนที่ด้วย (สมมติว่า T ขยายวัตถุและไม่ใช่คลาสอื่น)

T[] ts = (T[]) new Object[n];

ฉันชอบตัวอย่างแรก แต่มีประเภทวิชาการมากกว่าที่จะชอบอันดับที่สองหรือเพียง แต่ไม่ชอบคิด

ตัวอย่างส่วนใหญ่ของสาเหตุที่คุณไม่สามารถใช้ Object [] นำไปใช้กับรายการหรือชุดสะสม (ซึ่งได้รับการสนับสนุน) อย่างเท่าเทียมกันดังนั้นฉันจึงเห็นว่ามันเป็นอาร์กิวเมนต์ที่แย่มาก

หมายเหตุ: นี่เป็นหนึ่งในเหตุผลที่ห้องสมุด Collections ไม่ได้รวบรวมโดยไม่มีคำเตือน หากคุณไม่สามารถใช้งานเคสได้หากไม่มีคำเตือนจะมีบางอย่างเสียไปกับรุ่นทั่วไปของ IMHO


6
คุณต้องระวังด้วยอันที่สอง หากคุณส่งคืนอาร์เรย์ที่สร้างขึ้นในลักษณะที่มีต่อคนที่คาดหวังพูด a String[](หรือถ้าคุณเก็บไว้ในเขตข้อมูลที่สาธารณชนสามารถเข้าถึงได้จากประเภทT[]และมีคนเรียกคืน) พวกเขาจะได้รับ ClassCastException
newacct

4
ฉันโหวตให้คำตอบนี้เพราะตัวอย่างที่คุณต้องการไม่ได้รับอนุญาตใน Java และตัวอย่างที่สองของคุณอาจแสดง ClassCastException
José Roberto

5
@ JoséRobertoAraújoJúniorมันค่อนข้างชัดเจนว่าตัวอย่างแรกจะต้องถูกแทนที่ด้วยตัวอย่างที่สอง มันจะมีประโยชน์มากขึ้นสำหรับคุณที่จะอธิบายว่าทำไมตัวอย่างที่สองสามารถโยน ClassCastException ได้เนื่องจากทุกคนจะไม่ชัดเจน
Peter Lawrey

3
@PeterLawrey ฉันสร้างคำถามที่ตอบเองแล้วแสดงว่าทำไมT[] ts = (T[]) new Object[n];ความคิดที่ไม่ดี: stackoverflow.com/questions/21577493/…
José Roberto AraújoJúnior

1
@MarkoTopolnik ฉันควรได้รับเหรียญสำหรับตอบความคิดเห็นทั้งหมดของคุณเพื่ออธิบายสิ่งเดียวกับที่ฉันได้พูดไปแล้วสิ่งเดียวที่เปลี่ยนไปจากเหตุผลดั้งเดิมของฉันคือฉันแม้ว่าเขาจะบอกว่าT[] ts = new T[n];เป็นตัวอย่างที่ถูกต้อง ฉันจะให้คะแนนเพราะเขาตอบอาจทำให้เกิดปัญหาและ confusions กับ devs อื่น ๆ และเป็นนอกหัวข้อ นอกจากนี้ฉันจะหยุดความคิดเห็นเกี่ยวกับเรื่องนี้
José Roberto AraújoJúnior

38

อาร์เรย์มีความแปรปรวน

อาร์เรย์จะกล่าวว่าเป็น covariant ซึ่งโดยทั่วไปหมายความว่าให้กฎ subtyping ชวาอาร์เรย์ของชนิดT[]อาจมีองค์ประกอบของประเภทหรือชนิดย่อยของT Tตัวอย่างเช่น

Number[] numbers = new Number[3];
numbers[0] = newInteger(10);
numbers[1] = newDouble(3.14);
numbers[2] = newByte(0);

แต่ไม่เพียงแค่นั้นกฎการพิมพ์ย่อยของ Java ยังระบุว่าอาเรย์S[]นั้นเป็นประเภทย่อยของอาเรย์ด้วยT[]หากSเป็นประเภทย่อยTดังนั้นบางสิ่งเช่นนี้ก็ใช้ได้เช่นกัน:

Integer[] myInts = {1,2,3,4};
Number[] myNumber = myInts;

เนื่องจากตามกฎการพิมพ์ย่อยใน Java อาร์เรย์Integer[]จึงเป็นชนิดย่อยของอาร์เรย์Number[]เนื่องจากจำนวนเต็มเป็นประเภทย่อยของ Number

แต่กฎย่อยนี้สามารถนำไปสู่คำถามที่น่าสนใจ: จะเกิดอะไรขึ้นถ้าเราพยายามทำเช่นนี้?

myNumber[0] = 3.14; //attempt of heap pollution

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

ซึ่งหมายความว่าเราสามารถหลอกผู้แปล แต่เราไม่สามารถหลอกระบบประเภทเวลาทำงาน และนี่ก็เป็นเช่นนั้นเพราะอาร์เรย์เป็นสิ่งที่เราเรียกว่าชนิดที่เรียกคืนได้ ซึ่งหมายความว่าในช่วงเวลาทำงาน Java Number[]รู้ว่าอาร์เรย์นี้ที่อินสแตนซ์จริงเป็นอาร์เรย์ของจำนวนเต็มซึ่งก็เกิดขึ้นจะเข้าถึงได้ผ่านการอ้างอิงของพิมพ์

อย่างที่เราเห็นสิ่งหนึ่งคือประเภทของวัตถุจริงอีกอย่างคือประเภทของการอ้างอิงที่เราใช้เข้าถึงมันใช่ไหม?

ปัญหาเกี่ยวกับ Java Generics

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

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

ลองพิจารณารหัสที่ไม่ปลอดภัยต่อไปนี้

List<Integer> myInts = newArrayList<Integer>();
myInts.add(1);
myInts.add(2);
List<Number> myNums = myInts; //compiler error
myNums.add(3.14); //heap polution

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

ดังนั้นผู้ออกแบบของ Java ทำให้แน่ใจว่าเราไม่สามารถหลอกผู้แปล ถ้าเราไม่สามารถหลอกคอมไพเลอร์ได้ (อย่างที่เราสามารถทำได้กับอาเรย์) จากนั้นเราก็ไม่สามารถหลอกระบบประเภทเวลาทำงานได้เช่นกัน

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

ฉันข้ามบางส่วนของคำตอบนี้คุณสามารถอ่านบทความเต็มได้ที่นี่: https://dzone.com/articles/covariance-and-contravariance


32

เหตุผลที่เป็นไปไม่ได้คือ Java ใช้ Generics ในระดับคอมไพเลอร์และมีไฟล์คลาสเดียวที่สร้างขึ้นสำหรับแต่ละคลาส นี้เรียกว่าประเภทลบ

ณ รันไทม์คลาสที่คอมไพล์ต้องจัดการกับการใช้งานทั้งหมดด้วย bytecode เดียวกัน ดังนั้นnew T[capacity]จะไม่มีความคิดแน่นอนว่าประเภทใดที่ต้องมีการยกตัวอย่าง


17

ได้รับคำตอบแล้ว แต่ถ้าคุณมีอินสแตนซ์ของ T อยู่แล้วคุณสามารถทำได้:

T t; //Assuming you already have this object instantiated or given by parameter.
int length;
T[] ts = (T[]) Array.newInstance(t.getClass(), length);

หวังว่าฉันจะช่วยได้ Ferdi265


1
นี่เป็นทางออกที่ดี แต่สิ่งนี้จะได้รับคำเตือนที่ไม่ถูกตรวจสอบ (จาก Object to T []) อีก "ช้า" แต่ "เตือนฟรี" T[] ts = t.clone(); for (int i=0; i<ts.length; i++) ts[i] = null;วิธีการแก้ปัญหาจะเป็น:
midnite

1
นอกจากนี้หากสิ่งที่เราเก็บไว้เป็นแล้วมันจะเป็นT[] t ฉันไม่ใช้จ่ายบางครั้งจะคิดออก(T[]) Array.newInstance(t.getClass().getComponentType(), length); getComponentType()หวังว่านี่จะช่วยผู้อื่น
midnite

1
@midnite จะไม่กลับมาt.clone() T[]เพราะtไม่ใช่ Array ในคำตอบนี้
xmen

6

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

มีภาพรวมที่ดีเป็นที่นี่


ฉันไม่เห็นวิธีที่คุณสามารถรองรับ "new T [5]" แม้จะมีอาร์เรย์ที่ไม่เปลี่ยนแปลง
Dimitris Andreou

2
@DimitrisAndreou ดีสิ่งทั้งหมดเป็นเรื่องตลกของข้อผิดพลาดในการออกแบบ Java ทุกอย่างเริ่มต้นด้วยความแปรปรวนร่วมของอาร์เรย์ จากนั้นเมื่อคุณมีความแปรปรวนอาร์เรย์คุณสามารถโยนString[]ไปObjectและเก็บIntegerอยู่ในนั้น ดังนั้นพวกเขาจึงต้องเพิ่มการตรวจสอบประเภทรันไทม์สำหรับร้านค้าอาร์เรย์ ( ArrayStoreException) เพราะปัญหาไม่สามารถตรวจจับได้ในเวลารวบรวม (มิฉะนั้นIntegerอาจติดอยู่ในString[]จริงและคุณจะได้รับข้อผิดพลาดเมื่อคุณพยายามดึงมันซึ่งจะน่ากลัว) ...
Radon Rosborough

2
@DimitrisAndreou ... จากนั้นเมื่อคุณทำการตรวจสอบรันไทม์แทนการตรวจสอบเวลารวบรวมเสียงที่ไกลคุณเรียกใช้การลบประเภท (เช่นข้อบกพร่องการออกแบบที่โชคร้าย การลบประเภทหมายความว่าคุณไม่สามารถทำการตรวจสอบประเภทรันไทม์สำหรับประเภททั่วไป ดังนั้นเพื่อหลีกเลี่ยงปัญหาประเภทการจัดเก็บข้อมูลอาร์เรย์คุณก็ไม่สามารถมีอาร์เรย์ทั่วไป หากพวกเขาสร้างอาร์เรย์ที่ไม่เปลี่ยนแปลงในตอนแรกเราสามารถทำการตรวจสอบประเภทเวลาแบบคอมไพล์โดยไม่ต้องลบออก
Radon Rosborough

…ฉันเพิ่งค้นพบระยะเวลาการแก้ไขห้านาทีสำหรับความคิดเห็น Objectน่าจะObject[]เป็นความคิดเห็นแรกของฉัน
Radon Rosborough

3

ผมชอบคำตอบที่ได้รับทางอ้อมโดยGafter อย่างไรก็ตามฉันเสนอว่ามันผิด ฉันเปลี่ยนรหัสของ Gafter เล็กน้อย มันรวบรวมและวิ่งไปชั่วขณะหนึ่งแล้วมันก็จะระเบิดซึ่งหลังจากนั้นก็ทำนายว่ามันจะ

class Box<T> {

    final T x;

    Box(T x) {
        this.x = x;
    }
}

class Loophole {

    public static <T> T[] array(final T... values) {
        return (values);
    }

    public static void main(String[] args) {

        Box<String> a = new Box("Hello");
        Box<String> b = new Box("World");
        Box<String> c = new Box("!!!!!!!!!!!");
        Box<String>[] bsa = array(a, b, c);
        System.out.println("I created an array of generics.");

        Object[] oa = bsa;
        oa[0] = new Box<Integer>(3);
        System.out.println("error not caught by array store check");

        try {
            String s = bsa[0].x;
        } catch (ClassCastException cause) {
            System.out.println("BOOM!");
            cause.printStackTrace();
        }
    }
}

ผลลัพธ์คือ

I created an array of generics.
error not caught by array store check
BOOM!
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
    at Loophole.main(Box.java:26)

ดังนั้นสำหรับฉันคุณสามารถสร้างประเภทอาร์เรย์ทั่วไปใน java ได้ ฉันเข้าใจผิดคำถามหรือไม่?


ตัวอย่างของคุณแตกต่างจากสิ่งที่ฉันถาม สิ่งที่คุณอธิบายคืออันตรายของความแปรปรวนร่วมของอาร์เรย์ ลองดู (สำหรับ. NET: blogs.msdn.com/b/ericlippert/archive/2007/10/17/ … )
กลืน elysium

หวังว่าคุณจะได้รับคำเตือนเรื่องความปลอดภัยจากคอมไพเลอร์ใช่ไหม
Matt McHenry

1
ใช่ฉันได้รับคำเตือนเรื่องความปลอดภัย ใช่ฉันเห็นว่าตัวอย่างของฉันไม่ตอบสนองต่อคำถาม
emory

ที่จริงคุณได้รับคำเตือนหลายอันเนื่องจากการเริ่มต้นเลอะเทอะของ a, b, c นอกจากนี้ยังเป็นที่รู้จักกันดีและมีผลต่อไลบรารีหลักเช่น <T> java.util.Arrays.asList (T ... ) หากคุณผ่านประเภทที่ไม่สามารถขอคืนได้สำหรับ T คุณจะได้รับคำเตือน (เนื่องจากอาร์เรย์ที่สร้างขึ้นนั้นมีประเภทที่แม่นยำน้อยกว่าการเสแสร้งโค้ด) และมันก็น่าเกลียดสุด ๆ มันจะดีกว่าถ้าผู้เขียนวิธีนี้ได้รับคำเตือนแทนที่จะปล่อยไว้ที่ไซต์การใช้งานเนื่องจากวิธีการนั้นปลอดภัย แต่จะไม่เปิดเผยอาร์เรย์ให้กับผู้ใช้
Dimitris Andreou

1
คุณไม่ได้สร้างอาร์เรย์ทั่วไปที่นี่ คอมไพเลอร์สร้างอาร์เรย์ (ไม่ใช่ทั่วไป) สำหรับคุณ
newacct

2

ฉันรู้ว่าฉันมางานปาร์ตี้ช้านิดหน่อย แต่ฉันคิดว่าฉันอาจจะสามารถช่วยเหลือชาว Google ในอนาคตได้เนื่องจากไม่มีคำตอบใด ๆ เหล่านี้ที่สามารถแก้ไขปัญหาของฉันได้ คำตอบของ Ferdi265 ช่วยได้มาก

ฉันกำลังพยายามสร้างรายการที่เชื่อมโยงของตัวเองดังนั้นรหัสต่อไปนี้เป็นสิ่งที่ใช้ได้กับฉัน:

package myList;
import java.lang.reflect.Array;

public class MyList<TYPE>  {

    private Node<TYPE> header = null;

    public void clear() {   header = null;  }

    public void add(TYPE t) {   header = new Node<TYPE>(t,header);    }

    public TYPE get(int position) {  return getNode(position).getObject();  }

    @SuppressWarnings("unchecked")
    public TYPE[] toArray() {       
        TYPE[] result = (TYPE[])Array.newInstance(header.getObject().getClass(),size());        
        for(int i=0 ; i<size() ; i++)   result[i] = get(i); 
        return result;
    }


    public int size(){
         int i = 0;   
         Node<TYPE> current = header;
         while(current != null) {   
           current = current.getNext();
           i++;
        }
        return i;
    }  

ในเมธอด toArray () เป็นวิธีสร้างอาร์เรย์ของประเภททั่วไปสำหรับฉัน:

TYPE[] result = (TYPE[])Array.newInstance(header.getObject().getClass(),size());    

2

ในกรณีของฉันฉันแค่ต้องการชุดของสแต็คดังนี้:

Stack<SomeType>[] stacks = new Stack<SomeType>[2];

เนื่องจากสิ่งนี้เป็นไปไม่ได้ฉันจึงใช้วิธีการต่อไปนี้เป็นวิธีแก้ปัญหา:

  1. สร้างคลาส wrapper ที่ไม่ใช่แบบทั่วไปรอบ ๆ สแต็ก (พูด MyStack)
  2. MyStack [] stacks = ใหม่ MyStack [2] ทำงานได้ดีอย่างสมบูรณ์

น่าเกลียด แต่ Java มีความสุข

หมายเหตุ: ดังกล่าวโดย BrainSlugs83 ในความคิดเห็นคำถามมันเป็นไปได้โดยสิ้นเชิงที่จะมีอาร์เรย์ของ generics ใน. NET


2

จากบทช่วยสอนของ Oracle :

คุณไม่สามารถสร้างอาร์เรย์ของประเภทพารามิเตอร์ ตัวอย่างเช่นรหัสต่อไปนี้ไม่ได้รวบรวม:

List<Integer>[] arrayOfLists = new List<Integer>[2];  // compile-time error

รหัสต่อไปนี้แสดงให้เห็นถึงสิ่งที่เกิดขึ้นเมื่อใส่ประเภทที่แตกต่างกันลงในอาร์เรย์:

Object[] strings = new String[2];
strings[0] = "hi";   // OK
strings[1] = 100;    // An ArrayStoreException is thrown.

หากคุณลองทำสิ่งเดียวกันกับรายการทั่วไปจะมีปัญหา:

Object[] stringLists = new List<String>[];  // compiler error, but pretend it's allowed
stringLists[0] = new ArrayList<String>();   // OK
stringLists[1] = new ArrayList<Integer>();  // An ArrayStoreException should be thrown,
                                            // but the runtime can't detect it.

หากอนุญาตให้ใช้อาร์เรย์ของรายการที่กำหนดพารามิเตอร์รหัสก่อนหน้านี้จะล้มเหลวในการโยน ArrayStoreException ที่ต้องการ

สำหรับฉันมันฟังดูอ่อนแอมาก ฉันคิดว่าทุกคนที่มีความเข้าใจอย่างเพียงพอเกี่ยวกับยาชื่อสามัญจะดีอย่างสมบูรณ์และยังคาดหวังว่า ArrayStoredException จะไม่ถูกโยนในกรณีดังกล่าว


0

ต้องมีวิธีที่ดีรอบ ๆ (อาจใช้การสะท้อน) เพราะฉันคิดว่านั่นเป็นสิ่งที่ArrayList.toArray(T[] a)ทำ ฉันพูด:

public <T> T[] toArray(T[] a)

ส่งคืนอาร์เรย์ที่มีองค์ประกอบทั้งหมดในรายการนี้ตามลำดับที่ถูกต้อง ชนิดรันไทม์ของอาร์เรย์ที่ส่งคืนคือของอาร์เรย์ที่ระบุ หากรายการเหมาะสมกับอาร์เรย์ที่ระบุรายการนั้นจะถูกส่งคืน มิฉะนั้นอาร์เรย์ใหม่จะถูกจัดสรรด้วยประเภทรันไทม์ของอาร์เรย์ที่ระบุและขนาดของรายการนี้

ดังนั้นวิธีหนึ่งก็คือการใช้ฟังก์ชั่นนี้เช่นสร้างArrayListวัตถุที่คุณต้องการในอาร์เรย์จากนั้นใช้toArray(T[] a)เพื่อสร้างอาร์เรย์จริง มันจะไม่เร็ว แต่คุณไม่ได้พูดถึงความต้องการของคุณ

ดังนั้นไม่มีใครรู้วิธีtoArray(T[] a)การใช้งาน?


3
List.toArray (T []) ใช้งานได้เพราะคุณให้ประเภทคอมโพเนนต์ T ที่ runtime (คุณให้อินสแตนซ์ของประเภทอาร์เรย์ที่ต้องการซึ่งสามารถรับคลาสอาเรย์แล้วจากนั้นคลาสคอมโพเนนต์ T ) Array.newInstance()มีประเภทองค์ประกอบที่เกิดขึ้นจริงที่รันไทม์คุณสามารถสร้างอาร์เรย์ชนิดรันไทม์ที่ใช้ คุณจะพบว่ามีการกล่าวถึงในคำถามมากมายที่ถามถึงวิธีการสร้างอาร์เรย์ด้วยประเภทที่ไม่รู้จัก ณ เวลารวบรวม แต่ OP ได้ถามเฉพาะว่าทำไมคุณไม่สามารถใช้new T[]ไวยากรณ์ซึ่งเป็นคำถามที่แตกต่าง
newacct

0

มันเป็นเพราะยาชื่อสามัญถูกเพิ่มลงในจาวาหลังจากที่พวกเขาทำมันดังนั้นจึงเป็นเรื่องที่ไม่น่าสนใจเพราะผู้ผลิตจาวาดั้งเดิมคิดว่าเมื่อสร้างอาร์เรย์ประเภทจะถูกระบุในการสร้างมัน ดังนั้นมันจึงไม่ทำงานกับยาชื่อสามัญดังนั้นคุณต้องทำ E [] array = (E []) วัตถุใหม่ [15]; คอมไพล์นี้ แต่มันให้คำเตือน


0

หากเราไม่สามารถยกตัวอย่างอาร์เรย์ทั่วไปทำไมภาษามีประเภทอาร์เรย์ทั่วไป? จุดที่มีประเภทไม่มีวัตถุคืออะไร?

เหตุผลเดียวที่ฉันสามารถคิดเป็น varargs foo(T...)- มิฉะนั้นพวกเขาอาจมีประเภทอาร์เรย์ทั่วไปที่ขัดอย่างสมบูรณ์ (พวกเขาไม่จำเป็นต้องใช้อาร์เรย์สำหรับ varargs เนื่องจาก varargs ไม่มีอยู่ก่อน1.5นั่นอาจเป็นความผิดพลาดอีกครั้ง)

ดังนั้นจึงเป็นเรื่องโกหกคุณสามารถยกตัวอย่างอาร์เรย์ทั่วไปผ่าน varargs!

แน่นอนปัญหาเกี่ยวกับอาร์เรย์ทั่วไปยังคงเป็นจริงเช่น

static <T> T[] foo(T... args){
    return args;
}
static <T> T[] foo2(T a1, T a2){
    return foo(a1, a2);
}

public static void main(String[] args){
    String[] x2 = foo2("a", "b"); // heap pollution!
}

เราสามารถใช้ตัวอย่างนี้เพื่อแสดงให้เห็นถึงอันตรายของอาร์เรย์ทั่วไป

ในทางกลับกันเราได้ใช้วาร์argsทั่วไปมาสิบปีแล้วและท้องฟ้ายังไม่ตกลง ดังนั้นเราสามารถยืนยันได้ว่าปัญหามีการพูดเกินจริง มันไม่ได้เป็นเรื่องใหญ่. หากอนุญาตให้สร้างอาร์เรย์ทั่วไปอย่างชัดเจนเราจะมีจุดบกพร่องที่นี่และที่นั่น แต่เราคุ้นเคยกับปัญหาของการลบและเราสามารถอยู่กับมันได้

และเราสามารถชี้ไปที่foo2การหักล้างข้อเรียกร้องที่สเป็คทำให้เราไม่ได้รับจากปัญหาที่พวกเขาอ้างว่าปกป้องเราจาก ถ้าซอนมีเวลาและทรัพยากรมากขึ้นสำหรับ1.5ฉันเชื่อว่าพวกเขาจะได้รับการแก้ไขที่น่าพอใจมากขึ้น


0

เป็นคนอื่น ๆ ที่กล่าวถึงแล้วคุณแน่นอนสามารถสร้างทางเทคนิคบางอย่าง

แต่ไม่แนะนำ

เพราะการลบประเภทและที่สำคัญกว่าคือcovarianceในอาเรย์ที่เพิ่งอนุญาตให้อาเรย์ย่อยสามารถกำหนดให้กับอาเรย์แบบ Supertype ซึ่งบังคับให้คุณใช้การพิมพ์แบบชัดแจ้งเมื่อพยายามรับค่ากลับมาClassCastExceptionซึ่งเป็นหนึ่งในวัตถุประสงค์หลัก ที่ generics พยายามที่จะกำจัด: Stronger การตรวจสอบชนิดที่รวบรวมเวลา

Object[] stringArray = { "hi", "me" };
stringArray[1] = 1;
String aString = (String) stringArray[1]; // boom! the TypeCastException

ตัวอย่างโดยตรงมากขึ้นสามารถพบได้ในที่มีประสิทธิภาพ Java: รายการที่ 25


ความแปรปรวนร่วม : อาร์เรย์ของประเภท S [] เป็นประเภทย่อยของ T [] ถ้า S เป็นประเภทย่อยของ T


0

หากคลาสใช้เป็นชนิดที่กำหนดพารามิเตอร์จะสามารถประกาศอาร์เรย์ประเภท T [] แต่ไม่สามารถสร้างอินสแตนซ์ของอาร์เรย์นั้นได้โดยตรง แต่วิธีการทั่วไปคือการสร้างอาเรย์ของประเภทวัตถุ [] จากนั้นจึงทำการร่ายแบบแคบ ๆ เพื่อพิมพ์ T [] ดังที่แสดงในรายการต่อไปนี้:

  public class Portfolio<T> {
  T[] data;
 public Portfolio(int capacity) {
   data = new T[capacity];                 // illegal; compiler error
   data = (T[]) new Object[capacity];      // legal, but compiler warning
 }
 public T get(int index) { return data[index]; }
 public void set(int index, T element) { data[index] = element; }
}

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