ฉันใช้จาวาและ generics เป็นส่วนใหญ่ค่อนข้างใหม่ ฉันอ่านต่อไปว่า Java ทำการตัดสินใจผิดหรือว่า. NET มีการใช้งานที่ดีขึ้น ฯลฯ
ดังนั้นอะไรคือความแตกต่างที่สำคัญระหว่าง C ++, C #, Java ใน generics? ข้อดี / ข้อเสียของแต่ละคน?
ฉันใช้จาวาและ generics เป็นส่วนใหญ่ค่อนข้างใหม่ ฉันอ่านต่อไปว่า Java ทำการตัดสินใจผิดหรือว่า. NET มีการใช้งานที่ดีขึ้น ฯลฯ
ดังนั้นอะไรคือความแตกต่างที่สำคัญระหว่าง C ++, C #, Java ใน generics? ข้อดี / ข้อเสียของแต่ละคน?
คำตอบ:
ฉันจะเพิ่มเสียงของฉันไปที่เสียงรบกวนและทำสิ่งที่ชัดเจน:
List<Person> foo = new List<Person>();
จากนั้นคอมไพเลอร์จะป้องกันไม่ให้คุณใส่สิ่งที่ไม่ได้Person
อยู่ในรายการ
เบื้องหลังคอมไพเลอร์ C # เพิ่งใส่List<Person>
ลงในไฟล์. dll dll แต่ในขณะรันไทม์คอมไพเลอร์ JIT จะไปและสร้างชุดรหัสใหม่ราวกับว่าคุณได้เขียนคลาสรายการพิเศษเพื่อบรรจุบุคคลListOfPerson
สิ่งที่ชอบ
ประโยชน์ของมันคือทำให้มันเร็วจริงๆ ไม่มีการแคสต์หรือสิ่งอื่นใดและเนื่องจาก dll มีข้อมูลว่านี่คือ List of Person
รหัสอื่นที่มองในภายหลังโดยใช้การสะท้อนสามารถบอกได้ว่ามันมีPerson
วัตถุ (ดังนั้นคุณจะได้รับ Intellisense เป็นต้น)
ข้อเสียของสิ่งนี้คือรหัสเก่า C # 1.0 และ 1.1 (ก่อนที่พวกเขาจะเพิ่มชื่อสามัญ) ไม่เข้าใจสิ่งใหม่เหล่านี้List<something>
ดังนั้นคุณต้องเปลี่ยนสิ่งต่าง ๆ กลับไปเป็นแบบเก่าด้วยตนเองList
เพื่อทำงานร่วมกับพวกเขา นี่ไม่ใช่ปัญหาใหญ่เพราะรหัสไบนารี่ C # 2.0 ไม่รองรับการใช้งานย้อนหลัง ครั้งเดียวที่จะเกิดขึ้นคือถ้าคุณกำลังอัพเกรดรหัส C # 1.0 / 1.1 เก่าเป็น C # 2.0
ArrayList<Person> foo = new ArrayList<Person>();
บนพื้นผิวมันมีลักษณะเหมือนกันและเรียงลำดับตาม คอมไพเลอร์จะป้องกันคุณจากการวางสิ่งที่ไม่ใช่Person
อยู่ในรายการ
ความแตกต่างคือสิ่งที่เกิดขึ้นเบื้องหลัง แตกต่างจาก C #, Java ไม่ได้ไปและสร้างพิเศษListOfPerson
- เพียงแค่ใช้เก่าธรรมดาArrayList
ที่ได้รับเสมอใน Java เมื่อคุณได้รับสิ่งต่าง ๆ จากแถวลำดับการPerson p = (Person)foo.get(1);
ร่ายรำแบบปกติยังคงต้องทำ คอมไพเลอร์กำลังบันทึกคุณกดปุ่ม แต่ความเร็วในการตี / การหล่อยังคงเกิดขึ้นเหมือนเดิม
เมื่อมีคนพูดถึง "การลบประเภท" นี่คือสิ่งที่พวกเขากำลังพูดถึง คอมไพเลอร์แทรกการปลดเปลื้องสำหรับคุณแล้ว 'ลบ' ความจริงที่ว่ามันหมายถึงการเป็นรายการPerson
ไม่เพียงObject
ประโยชน์ของวิธีการนี้คือรหัสเก่าที่ไม่เข้าใจข้อมูลทั่วไปไม่จำเป็นต้องสนใจ มันยังคงติดต่อกับคนเก่าArrayList
เหมือนที่เคยมีมา สิ่งนี้มีความสำคัญมากกว่าในโลกของจาวาเพราะพวกเขาต้องการที่จะรองรับการคอมไพล์โค้ดโดยใช้ Java 5 กับ generics และทำให้มันทำงานบน 1.4 หรือ JVM รุ่นก่อนหน้าของไมโครซอฟท์ซึ่งไมโครซอฟท์ได้ตัดสินใจแล้วว่าจะไม่สนใจ
ข้อเสียคือความเร็วที่ฉันกล่าวถึงก่อนหน้านี้และเนื่องจากไม่มีListOfPerson
คลาสหลอกหรือสิ่งใดที่เหมือนกับการเข้าไปในไฟล์. class รหัสที่ดูในภายหลัง (พร้อมการสะท้อนกลับหรือถ้าคุณดึงมันออกมาจากคอลเล็กชันอื่น ที่มันถูกแปลงเป็นObject
หรืออื่น ๆ ) ไม่สามารถบอกได้ว่ามันหมายถึงว่าเป็นรายการที่มีเฉพาะPerson
และไม่ใช่แค่รายการอาร์เรย์อื่น ๆ
std::list<Person>* foo = new std::list<Person>();
ดูเหมือนว่า C # และ Java generics และจะทำในสิ่งที่คุณคิดว่าควรทำ แต่เบื้องหลังจะมีสิ่งต่าง ๆ เกิดขึ้น
มันมีประโยชน์ร่วมกันมากที่สุดกับ C # generics เพราะมันสร้างเป็นพิเศษpseudo-classes
มากกว่าเพียงแค่โยนข้อมูลประเภทออกไปอย่างที่จาวาใช้ แต่มันเป็นปลากาต้มน้ำที่แตกต่างกันโดยสิ้นเชิง
ทั้ง C # และ Java ผลิตเอาต์พุตที่ออกแบบมาสำหรับเครื่องเสมือน หากคุณเขียนโค้ดที่มีPerson
คลาสในทั้งสองกรณีข้อมูลบางอย่างเกี่ยวกับPerson
คลาสจะเข้าไปในไฟล์. dll หรือ. class และ JVM / CLR จะทำสิ่งนี้
C ++ สร้างรหัสไบนารี x86 แบบ raw ทุกอย่างไม่ใช่วัตถุและไม่มีเครื่องเสมือนที่ต้องรู้เกี่ยวกับPerson
ชั้นเรียน ไม่มีการชกมวยหรือ unboxing และฟังก์ชั่นไม่จำเป็นต้องเป็นของชั้นเรียนหรืออะไรก็ตาม
ด้วยเหตุนี้คอมไพเลอร์ C ++ จึงไม่มีข้อ จำกัด เกี่ยวกับสิ่งที่คุณสามารถทำได้กับเทมเพลตโดยทั่วไปโค้ดใด ๆ ที่คุณสามารถเขียนด้วยตนเองได้คุณจะได้รับเทมเพลตสำหรับเขียน
ตัวอย่างที่ชัดเจนที่สุดคือการเพิ่มสิ่งต่าง ๆ :
ใน C # และ Java ระบบ generics จำเป็นต้องรู้ว่ามีวิธีใดบ้างสำหรับคลาสและต้องผ่านสิ่งนี้ไปยังเครื่องเสมือน วิธีเดียวที่จะบอกได้ว่าสิ่งนี้คือโดยการเข้ารหัสฮาร์ดคลาสจริงหรือใช้อินเตอร์เฟส ตัวอย่างเช่น:
string addNames<T>( T first, T second ) { return first.Name() + second.Name(); }
รหัสนั้นจะไม่คอมไพล์ใน C # หรือ Java เพราะไม่ทราบว่าประเภทT
นั้นมีวิธีการที่เรียกว่าชื่อ () คุณต้องบอก - ใน C # เช่นนี้:
interface IHasName{ string Name(); };
string addNames<T>( T first, T second ) where T : IHasName { .... }
จากนั้นคุณต้องตรวจสอบให้แน่ใจว่าสิ่งที่คุณส่งผ่านไปยัง addNames ใช้อินเทอร์เฟซ IHasName และอื่น ๆ ไวยากรณ์ของ java แตกต่างกัน ( <T extends IHasName>
) แต่ได้รับปัญหาจากปัญหาเดียวกัน
กรณี 'คลาสสิค' สำหรับปัญหานี้กำลังพยายามเขียนฟังก์ชันที่ทำสิ่งนี้
string addNames<T>( T first, T second ) { return first + second; }
คุณไม่สามารถเขียนรหัสนี้ได้เนื่องจากไม่มีวิธีในการประกาศส่วนต่อประสานที่มี+
วิธีการนั้น คุณล้มเหลว
C ++ ไม่ประสบปัญหาเหล่านี้ คอมไพเลอร์ไม่สนใจเกี่ยวกับการส่งประเภทลงไปยัง VM ของใด ๆ - ถ้าวัตถุของคุณทั้งสองมีฟังก์ชั่น. ชื่อ () มันจะรวบรวม หากพวกเขาทำไม่ได้มันจะไม่ ง่าย
ดังนั้นคุณมีมัน :-)
int addNames<T>( T first, T second ) { return first + second; }
ใน C # ประเภททั่วไปสามารถถูก จำกัด คลาสได้แทนที่จะเป็นอินเตอร์เฟสและมีวิธีการประกาศคลาสที่มี+
โอเปอเรเตอร์อยู่
C ++ ไม่ค่อยใช้คำศัพท์ "generics" แต่จะใช้คำว่า“ เทมเพลต” และมีความแม่นยำมากกว่า เทมเพลตอธิบายเทคนิคเดียวเพื่อให้ได้การออกแบบทั่วไป
เทมเพลต C ++ นั้นแตกต่างอย่างมากจากสิ่งที่ทั้ง C # และ Java ใช้งานด้วยเหตุผลหลักสองประการ เหตุผลแรกคือเทมเพลต C ++ ไม่เพียง แต่อนุญาตให้มีการขัดแย้งประเภทเวลาคอมไพล์ แต่ยังรวมถึงข้อโต้แย้งค่า const เวลาค่า: แม่แบบสามารถได้รับเป็นจำนวนเต็มหรือฟังก์ชั่นลายเซ็น ซึ่งหมายความว่าคุณสามารถทำสิ่งที่ค่อนข้างขี้ขลาดในเวลารวบรวมเช่นการคำนวณ:
template <unsigned int N>
struct product {
static unsigned int const VALUE = N * product<N - 1>::VALUE;
};
template <>
struct product<1> {
static unsigned int const VALUE = 1;
};
// Usage:
unsigned int const p5 = product<5>::VALUE;
รหัสนี้ยังใช้คุณสมบัติที่แตกต่างอื่น ๆ ของแม่แบบ C ++ คือความเชี่ยวชาญเฉพาะทาง รหัสกำหนดหนึ่งคลาสเทมเพลตproduct
ที่มีอาร์กิวเมนต์ค่าหนึ่งค่า นอกจากนี้ยังกำหนดความเชี่ยวชาญสำหรับเทมเพลตนั้นที่ใช้เมื่อใดก็ตามที่อาร์กิวเมนต์ประเมินค่าเป็น 1 ซึ่งช่วยให้ฉันสามารถกำหนดการสอบถามซ้ำตามข้อกำหนดเทมเพลต ผมเชื่อว่าเรื่องนี้ถูกค้นพบครั้งแรกโดยอังเดร Alexandrescu
เทมเพลตเชี่ยวชาญเป็นสิ่งสำคัญสำหรับ C ++ เพราะช่วยให้ความแตกต่างของโครงสร้างในโครงสร้างข้อมูล เทมเพลตโดยรวมเป็นวิธีการรวมอินเตอร์เฟซข้ามประเภท อย่างไรก็ตามถึงแม้จะเป็นสิ่งที่น่าพึงพอใจ แต่ก็ไม่สามารถใช้งานได้ทุกประเภทภายในการใช้งาน เทมเพลต C ++ คำนึงถึงสิ่งนี้ นี่เป็นสิ่งที่แตกต่างกันอย่างมากที่ OOP ทำระหว่างส่วนต่อประสานและส่วนนำไปใช้งานด้วยการแทนที่ของวิธีเสมือน
เทมเพลต C ++ มีความจำเป็นสำหรับกระบวนทัศน์การเขียนโปรแกรมแบบอัลกอริทึม ตัวอย่างเช่นอัลกอริทึมเกือบทั้งหมดสำหรับคอนเทนเนอร์ถูกกำหนดเป็นฟังก์ชันที่ยอมรับประเภทคอนเทนเนอร์เป็นประเภทแม่แบบและปฏิบัติต่อพวกเขาอย่างสม่ำเสมอ ที่จริงแล้วมันไม่ถูกต้อง: C ++ ใช้งานไม่ได้กับคอนเทนเนอร์ แต่อยู่ในช่วงที่กำหนดโดยตัววนซ้ำสองตัวชี้ไปที่จุดเริ่มต้นและด้านหลังจุดสิ้นสุดของคอนเทนเนอร์ ดังนั้นเนื้อหาทั้งหมดจะถูก จำกัด โดยตัววนซ้ำ: เริ่มต้น <= องค์ประกอบ <สิ้นสุด
การใช้ตัววนซ้ำแทนคอนเทนเนอร์มีประโยชน์เพราะช่วยให้สามารถทำงานกับชิ้นส่วนของคอนเทนเนอร์แทนการใช้ทั้งหมด
คุณสมบัติที่แตกต่างอีกประการของ C ++ คือความเป็นไปได้ของความเชี่ยวชาญเฉพาะบางส่วนสำหรับแม่แบบคลาส สิ่งนี้เกี่ยวข้องกับการจับคู่รูปแบบกับข้อโต้แย้งใน Haskell และภาษาที่ใช้งานอื่น ๆ ตัวอย่างเช่นลองพิจารณาคลาสที่เก็บองค์ประกอบ:
template <typename T>
class Store { … }; // (1)
สิ่งนี้ใช้ได้กับองค์ประกอบทุกประเภท แต่สมมุติว่าเราสามารถเก็บพอยน์เตอร์ได้อย่างมีประสิทธิภาพมากกว่าประเภทอื่น ๆ โดยใช้เคล็ดลับพิเศษ เราสามารถทำได้โดยมีความเชี่ยวชาญบางส่วนสำหรับตัวชี้ทุกประเภท:
template <typename T>
class Store<T*> { … }; // (2)
ตอนนี้เมื่อใดก็ตามที่เราสร้างเทมเพลตคอนเทนเนอร์สำหรับประเภทเดียวเราจะใช้คำจำกัดความที่เหมาะสม:
Store<int> x; // Uses (1)
Store<int*> y; // Uses (2)
Store<string**> z; // Uses (2), with T = string*.
Anders Hejlsberg อธิบายความแตกต่างได้ที่นี่ " Generics ใน C #, Java และ C ++ "
มีอยู่เป็นจำนวนมากของคำตอบที่ดีในสิ่งที่แตกต่างเพื่อให้ฉันให้มุมมองที่แตกต่างกันเล็กน้อยและเพิ่มทำไม
ตามที่ได้อธิบายไปแล้วความแตกต่างหลักคือการลบประเภทนั่นคือความจริงที่ว่าคอมไพเลอร์ Java ลบประเภททั่วไปและพวกเขาไม่ได้จบลงใน bytecode ที่สร้างขึ้น อย่างไรก็ตามคำถามคือทำไมทุกคนจะทำเช่นนั้น? มันไม่สมเหตุสมผลเลย! หรือไม่
ทางเลือกคืออะไร หากคุณไม่ได้ใช้งาน generics ในภาษาคุณจะใช้งานที่ไหน? และคำตอบคือ: ในเครื่องเสมือน ซึ่งแบ่งความเข้ากันได้ย้อนหลัง
ในทางกลับกันการลบประเภทช่วยให้คุณสามารถผสมไคลเอนต์ทั่วไปกับไลบรารีที่ไม่ใช่ทั่วไปได้ กล่าวอีกนัยหนึ่ง: รหัสที่รวบรวมบน Java 5 ยังคงสามารถปรับใช้กับ Java 1.4 ได้
อย่างไรก็ตาม Microsoft ตัดสินใจที่จะทำลายความเข้ากันได้ของข้อมูลทั่วไป นั่นเป็นเหตุผลที่. NET Generics "ดีกว่า" กว่า Java Generics
แน่นอนว่าซันไม่ใช่คนงี่เง่าหรือคนขี้ขลาด เหตุผลที่พวกเขา "chickened out" คือ Java นั้นมีอายุมากกว่าและแพร่หลายมากกว่า NET เมื่อพวกเขาแนะนำ generics (พวกเขาได้รับการแนะนำอย่างเกรี้ยวกราดในเวลาเดียวกันในทั้งสองโลก) การทำลายความเข้ากันได้แบบย้อนหลังจะเป็นความเจ็บปวดครั้งใหญ่
อีกวิธีหนึ่ง: ใน Java, Generics เป็นส่วนหนึ่งของภาษา (ซึ่งหมายความว่าพวกเขาใช้เฉพาะกับ Java ไม่ใช่ภาษาอื่น ๆ ) ใน. NET พวกเขาเป็นส่วนหนึ่งของVirtual Machine (ซึ่งหมายความว่าพวกเขาใช้กับทุกภาษาไม่ใช่ เพียงแค่ C # และ Visual Basic.NET)
เปรียบเทียบกับ .NET คุณสมบัติเช่น LINQ แสดงออกแลมบ์ดาท้องถิ่นชนิดตัวแปรอนุมานชนิดไม่ระบุชื่อและต้นไม้แสดงออกเหล่านี้เป็นทุกภาษาคุณสมบัติ นั่นเป็นเหตุผลที่มีความแตกต่างเล็กน้อยระหว่าง VB.NET และ C #: หากคุณสมบัติเหล่านั้นเป็นส่วนหนึ่งของ VM พวกเขาจะเหมือนกันในทุกภาษา แต่ CLR ไม่เปลี่ยนแปลง: ยังคงเหมือนเดิมใน. NET 3.5 SP1 เหมือนเดิมใน. NET 2.0 คุณสามารถคอมไพล์โปรแกรม C # ที่ใช้ LINQ กับ. NET 3.5 คอมไพเลอร์และยังคงรันมันบน. NET 2.0 โดยที่คุณไม่ต้องใช้ไลบรารี. NET 3.5 ที่จะไม่ทำงานกับ generics และ. NET 1.1 แต่จะทำงานกับ Java และ Java 1.4
ArrayList<T>
สามารถปล่อยออกมาเป็นชนิดที่มีชื่อภายในใหม่พร้อมกับClass<T>
ฟิลด์สแตติก (ซ่อน) ตราบใดที่เวอร์ชันใหม่ของ lib ทั่วไปได้รับการปรับใช้ด้วยรหัส 1.5+ ไบต์มันจะสามารถทำงานบน 1.4- JVM ได้
ติดตามการโพสต์ก่อนหน้าของฉัน
เทมเพลตเป็นหนึ่งในสาเหตุหลักว่าทำไม C ++ จึงล้มเหลวอย่างมากในระบบ Intellisense โดยไม่คำนึงถึง IDE ที่ใช้ เนื่องจากความเชี่ยวชาญเทมเพลต IDE จึงไม่สามารถแน่ใจได้อย่างแน่นอนว่าสมาชิกที่ระบุนั้นมีอยู่หรือไม่ พิจารณา:
template <typename T>
struct X {
void foo() { }
};
template <>
struct X<int> { };
typedef int my_int_type;
X<my_int_type> a;
a.|
ตอนนี้เคอร์เซอร์อยู่ในตำแหน่งที่ระบุและมันก็ยากสำหรับ IDE ที่จะพูด ณ จุดนั้นถ้าและสิ่งที่สมาชิกa
มี สำหรับภาษาอื่น ๆ การแยกวิเคราะห์จะเรียบง่าย แต่สำหรับ C ++ จำเป็นต้องมีการประเมินล่วงหน้าเล็กน้อย
มันแย่ลงเรื่อย ๆ ถ้าหากmy_int_type
นิยามไว้ในเทมเพลตของชั้นเรียนด้วยล่ะ ตอนนี้ชนิดของมันจะขึ้นอยู่กับอาร์กิวเมนต์ประเภทอื่น และที่นี่แม้แต่คอมไพเลอร์ก็ล้มเหลว
template <typename T>
struct Y {
typedef T my_type;
};
X<Y<int>::my_type> b;
หลังจากคิดไปเล็กน้อยโปรแกรมเมอร์จะสรุปว่ารหัสนี้เหมือนกับข้างต้น: Y<int>::my_type
แก้ไขint
ดังนั้นb
ควรเป็นประเภทเดียวกันa
ใช่ไหม?
ไม่ถูกต้อง. ณ จุดที่คอมไพเลอร์พยายามแก้ไขข้อความนี้มันยังไม่รู้จริง ๆY<int>::my_type
! ดังนั้นจึงไม่ทราบว่าเป็นประเภทใด อาจเป็นอย่างอื่นเช่นฟังก์ชันสมาชิกหรือเขตข้อมูล สิ่งนี้อาจทำให้เกิดความกำกวม (แม้ว่าจะไม่ใช่ในกรณีปัจจุบัน) ดังนั้นคอมไพเลอร์จึงล้มเหลว เราต้องบอกอย่างชัดเจนว่าเราอ้างถึงชื่อประเภท:
X<typename Y<int>::my_type> b;
ตอนนี้รหัสรวบรวม เพื่อดูว่าเกิดความคลุมเครือจากสถานการณ์นี้อย่างไรให้พิจารณาโค้ดต่อไปนี้:
Y<int>::my_type(123);
คำสั่งนี้เป็นรหัสที่ถูกต้องสมบูรณ์และบอก c ++ Y<int>::my_type
ที่จะดำเนินการเรียกใช้ฟังก์ชันเพื่อ อย่างไรก็ตามถ้าmy_type
ไม่ใช่ฟังก์ชั่น แต่เป็นประเภทข้อความนี้จะยังคงใช้งานได้และทำการส่งแบบพิเศษ คอมไพเลอร์ไม่สามารถบอกได้ว่าเราหมายความว่าอย่างไรเราจึงต้องแก้ความกำกวมที่นี่
ทั้ง Java และ C # ได้นำเสนอข้อมูลทั่วไปหลังจากการเปิดตัวภาษาแรก อย่างไรก็ตามมีความแตกต่างในวิธีการที่ห้องสมุดหลักเปลี่ยนไปเมื่อมีการนำเสนอชื่อสามัญ ข้อมูลทั่วไปของ C # ไม่ได้เป็นเพียงแค่คอมไพเลอร์เวทมนต์ดังนั้นจึงเป็นไปไม่ได้ที่จะสร้างคลาสไลบรารีที่มีอยู่
ยกตัวอย่างเช่นใน Java ที่มีอยู่ในคอลเลกชันกรอบถูกgenericised สมบูรณ์ Java ไม่มีทั้งคลาสทั่วไปและรุ่นที่ไม่ใช่รุ่นดั้งเดิมของคลาสคอลเล็กชัน ในบางวิธีการนี้จะสะอาดกว่ามาก - ถ้าคุณต้องการใช้คอลเล็กชันใน C # มีเหตุผลน้อยมากที่จะไปกับเวอร์ชันที่ไม่ใช่แบบทั่วไป แต่คลาสดั้งเดิมเหล่านั้นยังคงอยู่
ข้อแตกต่างที่น่าสังเกตอีกอย่างคือคลาส Enum ใน Java และ C # Enum ของ Java มีคำจำกัดความที่ดูค่อนข้างคดเคี้ยว:
// java.lang.Enum Definition in Java
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable {
(ดูคำอธิบายที่ชัดเจนของ Angelika Langer ว่าทำไมถึงเป็นเช่นนั้นโดยพื้นฐานแล้วนี่หมายถึง Java สามารถให้การเข้าถึงที่ปลอดภัยจากสตริงกับค่า Enum:
// Parsing String to Enum in Java
Colour colour = Colour.valueOf("RED");
เปรียบเทียบกับรุ่น C #:
// Parsing String to Enum in C#
Colour colour = (Colour)Enum.Parse(typeof(Colour), "RED");
เนื่องจาก Enum มีอยู่แล้วใน C # ก่อน generics ได้รับการแนะนำให้รู้จักกับภาษาความหมายไม่สามารถเปลี่ยนแปลงได้โดยไม่ทำลายรหัสที่มีอยู่ เช่นเดียวกับคอลเล็กชันมันยังคงอยู่ในไลบรารีหลักในสถานะดั้งเดิมนี้
ArrayList
ไปList<T>
และใส่ลงใน namespace ที่ใหม่ ความจริงก็คือถ้ามีคลาสปรากฏอยู่ในซอร์สโค้ดเพราะArrayList<T>
มันจะกลายเป็นคอมไพเลอร์ที่สร้างชื่อคลาสที่แตกต่างในรหัส IL ดังนั้นจึงไม่มีความขัดแย้งของชื่อที่สามารถเกิดขึ้นได้
11 เดือนสาย แต่ฉันคิดว่าคำถามนี้พร้อมสำหรับ Java Wildcard บางอย่าง
นี่คือคุณสมบัติการสร้างประโยคของ Java สมมติว่าคุณมีวิธี:
public <T> void Foo(Collection<T> thing)
และสมมติว่าคุณไม่จำเป็นต้องอ้างถึงประเภท T ในเนื้อความของเมธอด คุณกำลังประกาศชื่อ T จากนั้นใช้เพียงครั้งเดียวดังนั้นทำไมคุณควรคิดถึงชื่อของมัน คุณสามารถเขียน:
public void Foo(Collection<?> thing)
เครื่องหมายคำถามขอให้คอมไพเลอร์ทำท่าว่าคุณประกาศพารามิเตอร์ประเภทที่มีชื่อปกติซึ่งจะต้องปรากฏเพียงครั้งเดียวในจุดนั้น
ไม่มีสิ่งใดที่คุณสามารถทำได้ด้วยอักขระตัวแทนที่คุณไม่สามารถทำได้ด้วยพารามิเตอร์ชนิดที่มีชื่อ (ซึ่งเป็นสิ่งที่ทำสิ่งเหล่านี้ใน C ++ และ C # เสมอ
class Foo<T extends List<?>>
และใช้Foo<StringList>
แต่ใน C # คุณต้องเพิ่มว่าพารามิเตอร์ชนิดพิเศษclass Foo<T, T2> where T : IList<T2>
และใช้ Foo<StringList, String>
clunky
Wikipedia มีการเขียนบทความที่ยอดเยี่ยมเมื่อเปรียบเทียบทั้งสองอย่าง Java / C # genericsและJava generics / C ++ เท็มเพลต บทความหลักใน Genericsดูเหมือนว่าบิตรก แต่จะมีข้อมูลที่ดีบางอย่างในนั้น
การร้องเรียนที่ใหญ่ที่สุดคือการลบประเภท ในนั้น generics ไม่ได้บังคับใช้ที่รันไทม์ นี่คือลิงค์ไปยังเอกสารของซันในหัวข้อนี้นี้
Generics ถูกนำมาใช้โดยการลบประเภท: ข้อมูลประเภททั่วไปจะปรากฏเฉพาะในเวลารวบรวมหลังจากนั้นจะถูกลบโดยคอมไพเลอร์
เทมเพลต C ++ นั้นมีประสิทธิภาพมากกว่า C # และจาวาของคู่หูอย่างมากเนื่องจากมีการประเมิน ณ เวลารวบรวมและความเชี่ยวชาญเฉพาะด้าน สิ่งนี้ทำให้เทมเพลต Meta-Programming และทำให้คอมไพเลอร์ C ++ เทียบเท่ากับเครื่องทัวริง (เช่นในระหว่างกระบวนการรวบรวมคุณสามารถคำนวณสิ่งที่คำนวณได้ด้วยเครื่องทัวริง)
ใน Java generics เป็นระดับคอมไพเลอร์เท่านั้นดังนั้นคุณจะได้รับ:
a = new ArrayList<String>()
a.getClass() => ArrayList
โปรดทราบว่าชนิดของ 'a' เป็นรายการอาร์เรย์ไม่ใช่รายการสตริง ดังนั้นประเภทของรายการกล้วยจะเท่ากับ () รายการลิง
ดังนั้นที่จะพูด
ดูเหมือนว่าในบรรดาข้อเสนอที่น่าสนใจอื่น ๆ มีสิ่งหนึ่งที่เกี่ยวกับการกลั่นยาสามัญและทำลายความเข้ากันได้ย้อนหลัง:
ปัจจุบันข้อมูลทั่วไปถูกนำมาใช้โดยการลบซึ่งหมายความว่าข้อมูลประเภททั่วไปไม่สามารถใช้งานได้ในขณะทำงานซึ่งทำให้รหัสบางประเภทยากที่จะเขียน Generics ถูกใช้งานด้วยวิธีนี้เพื่อรองรับความเข้ากันได้ย้อนหลังกับรหัสที่ไม่ใช่แบบทั่วไปที่เก่ากว่า ข้อมูลทั่วไปของ Reified จะทำให้ข้อมูลประเภททั่วไปมีอยู่ที่รันไทม์ซึ่งจะทำลายรหัสที่ไม่ใช่ทั่วไป อย่างไรก็ตาม Neal Gafter ได้เสนอการสร้างประเภทที่สามารถใช้งานได้ต่อเมื่อมีการระบุไว้เพื่อไม่ให้เกิดความเข้ากันได้แบบย้อนหลัง
NB: ฉันไม่มีจุดพอที่จะแสดงความคิดเห็นดังนั้นอย่าลังเลที่จะย้ายสิ่งนี้เป็นความคิดเห็นเพื่อคำตอบที่เหมาะสม
ตรงกันข้ามกับความเชื่อที่ได้รับความนิยมซึ่งฉันไม่เคยเข้าใจมาก่อนว่ามันมาจากอะไร. net ได้นำเอายาสามัญมาใช้โดยไม่ต้องใช้ความเข้ากันได้แบบย้อนหลังและพวกเขาก็ใช้ความพยายามอย่างชัดเจน คุณไม่จำเป็นต้องเปลี่ยนรหัส. net 1.0 ที่ไม่ใช่แบบทั่วไปเป็น generics เพียงเพื่อใช้ใน. net 2.0 ทั้งรายการทั่วไปและไม่ใช่ทั่วไปยังคงมีอยู่ใน. NET Framework 2.0 แม้กระทั่ง 4.0 ไม่ว่าจะด้วยเหตุผลใด ๆ ดังนั้นรหัสเก่าที่ยังคงใช้ ArrayList ที่ไม่ใช่แบบทั่วไปจะยังคงใช้งานได้และใช้คลาส ArrayList เหมือนเดิม ความเข้ากันได้ของรหัสย้อนหลังนั้นได้รับการปรับปรุงตั้งแต่ 1.0 จนถึงปัจจุบัน ... ดังนั้นแม้ใน. net 4.0 คุณยังคงต้องเลือกใช้คลาสที่ไม่ใช่ชื่อสามัญจาก 1.0 BCL หากคุณเลือกที่จะทำ
ดังนั้นฉันจึงไม่คิดว่าจาวาต้องแยกความเข้ากันได้แบบย้อนหลังเพื่อรองรับข้อมูลทั่วไปที่แท้จริง
ArrayList<Foo>
ที่มันต้องการที่จะส่งผ่านไปยังวิธีการเก่าซึ่งควรจะเติมกับกรณีของArrayList
Foo
หากArrayList<foo>
ไม่ใช่สิ่งArrayList
ใดสิ่งนั้นจะทำงานอย่างไร