ฉันเคยเห็นหลายครั้งในเว็บไซต์นี้โพสต์ที่ประณามการใช้งานจาวาทั่วไป ตอนนี้ฉันสามารถพูดได้อย่างตรงไปตรงมาว่าฉันไม่ได้มีปัญหาใด ๆ กับการใช้พวกเขา อย่างไรก็ตามฉันไม่ได้พยายามที่จะสร้างชั้นเรียนตัวเอง ดังนั้นปัญหาของคุณกับการสนับสนุนทั่วไปของ Java คืออะไร
ฉันเคยเห็นหลายครั้งในเว็บไซต์นี้โพสต์ที่ประณามการใช้งานจาวาทั่วไป ตอนนี้ฉันสามารถพูดได้อย่างตรงไปตรงมาว่าฉันไม่ได้มีปัญหาใด ๆ กับการใช้พวกเขา อย่างไรก็ตามฉันไม่ได้พยายามที่จะสร้างชั้นเรียนตัวเอง ดังนั้นปัญหาของคุณกับการสนับสนุนทั่วไปของ Java คืออะไร
คำตอบ:
การดำเนินงานทั่วไปของ Java ใช้ลบออกประเภท ซึ่งหมายความว่าคอลเลกชันทั่วไปที่พิมพ์อย่างรุนแรงของคุณนั้นเป็นประเภทObject
ที่รันไทม์ สิ่งนี้มีข้อควรพิจารณาเกี่ยวกับประสิทธิภาพเนื่องจากมันหมายถึงประเภทดั้งเดิมจะต้องใส่กล่องเมื่อเพิ่มไปยังคอลเลกชันทั่วไป แน่นอนว่าประโยชน์ของการรวบรวมความถูกต้องของเวลาประเภทนั้นมีค่ามากกว่าความโง่ทั่วไปของการลบประเภทและการเน้นไปที่ความเข้ากันได้แบบย้อนหลัง
TypeLiteral
google-guice.googlecode.com/svn/trunk/javadoc/com/google/inject/...
new ArrayList<String>.getClass() == new ArrayList<Integer>.getClass()
template<T> T add(T v1, T v2) { return v1->add(v2); }
นั้นและคุณมีวิธีการทั่วไปในการสร้างฟังก์ชั่นที่add
สองสิ่งไม่ว่าสิ่งนั้นจะเป็นอะไรพวกเขาเพียงแค่ต้องมีวิธีการตั้งชื่อadd()
ด้วยพารามิเตอร์เดียว
Add
ตัวอย่างง่ายๆที่ฉันให้มาได้ไม่มีทางโดยไม่ทราบล่วงหน้าว่าจะใช้คลาสใดซึ่งเป็นข้อเสียเปรียบ
สังเกตว่าคำตอบที่ให้ไว้แล้วนั้นเน้นไปที่การผสมผสานระหว่าง Java-the-language, JVM และ Java Class Library
ไม่มีอะไรผิดปกติกับ generics ของ Java เท่าที่เกี่ยวกับ Java-the-language ตามที่อธิบายไว้ในC # VS generics Java , ยาชื่อสามัญของ Java จะสวยมากดีในระดับภาษาที่1
สิ่งที่ไม่ดีคือ JVM ไม่รองรับยาชื่อสามัญโดยตรงซึ่งมีผลกระทบสำคัญหลายประการ:
ฉันคิดว่าความแตกต่างที่ฉันทำอยู่นั้นเป็นเรื่องอื้อฉาวเพราะ Java-the-language นั้นถูกรวบรวมอย่างแพร่หลายด้วย JVM ซึ่งเป็นเป้าหมายและ Java Class Library เป็นไลบรารีหลัก
1ด้วยข้อยกเว้นที่เป็นไปได้ของ wildcard ซึ่งเชื่อว่าทำให้การอนุมานประเภทไม่สามารถตัดสินใจได้ในกรณีทั่วไป นี่เป็นข้อแตกต่างที่สำคัญระหว่าง C # และ Java generics ที่ไม่ได้กล่าวถึงบ่อยนัก ขอบคุณพลวง
8675309
) และจากนั้นสร้างประเภทที่ไม่ซ้ำกับหมายเลขนั้น (เช่นZ8<Z6<Z7<Z5<Z3<Z0<Z9>>>>>>>
) ซึ่งจะมีสมาชิกที่แตกต่างจากประเภทอื่น ๆ ใน C ++ ทุกประเภทที่สามารถสร้างจากอินพุตใด ๆ จะต้องถูกสร้างขึ้นในเวลารวบรวม
คำวิจารณ์ปกติคือการขาดการ reification กล่าวคือวัตถุที่รันไทม์ไม่มีข้อมูลเกี่ยวกับข้อโต้แย้งทั่วไป (แม้ว่าข้อมูลจะยังคงปรากฏอยู่ในเขตข้อมูลวิธีการตัวสร้างและคลาสและส่วนต่อขยายที่เพิ่มขึ้น) ซึ่งหมายความว่าคุณสามารถโยนพูดไปArrayList<String>
List<File>
คอมไพเลอร์จะให้คำเตือน แต่ก็ยังจะเตือนคุณถ้าคุณใช้ArrayList<String>
กำหนดไปยังอ้างอิงแล้วโยนมันทิ้งไปObject
List<String>
อัพไซด์นั้นด้วย generics คุณอาจไม่ควรทำการแคสติ้งประสิทธิภาพจะดีกว่าหากไม่มีข้อมูลที่ไม่จำเป็นและแน่นอนความเข้ากันได้แบบย้อนหลัง
บางคนบ่นว่าคุณไม่สามารถโหลดมากเกินไปบนพื้นฐานของข้อโต้แย้งทั่วไป ( void fn(Set<String>)
และvoid fn(Set<File>)
) คุณต้องใช้ชื่อวิธีที่ดีกว่าแทน โปรดทราบว่าการโหลดเกินพิกัดนี้ไม่จำเป็นต้องมีการแก้ไขเนื่องจากการโอเวอร์โหลดเป็นปัญหาแบบคงที่และรวบรวมเวลา
ประเภทดั้งเดิมไม่สามารถใช้ได้กับยาชื่อสามัญ
สัญลักษณ์แทนและขอบเขตค่อนข้างซับซ้อน พวกมันมีประโยชน์มาก ถ้า Java ต้องการความไม่เปลี่ยนรูปแบบและอินเทอร์เฟซ tell-Don't-ask ดังนั้นการประกาศด้านแทนที่จะเป็นข้อมูลทั่วไปด้านการใช้จะเหมาะสมกว่า
Object cs = new char[] { 'H', 'i' }; System.out.println(cs);
พิมพ์ไร้สาระ เปลี่ยนชนิดของcs
การและคุณจะได้รับchar[]
Hi
Java generics ดูดเนื่องจากคุณไม่สามารถทำสิ่งต่อไปนี้:
public class AsyncAdapter<Parser,Adapter> extends AsyncTask<String,Integer,Adapter> {
proptected Adapter doInBackground(String... keywords) {
Parser p = new Parser(keywords[0]); // this is an error
/* some more stuff I was hoping for but couldn't do because
the compiler wouldn't let me
*/
}
}
คุณไม่จำเป็นต้องเขียงทุกชั้นในรหัสข้างต้นเพื่อให้มันทำงานได้ตามที่ควรถ้า generics เป็นพารามิเตอร์ระดับสามัญจริง ๆ
Parser
ฉันจะต้องทำให้รหัสโรงงานที่ซับซ้อนยิ่งขึ้น ในขณะที่ถ้า "ทั่วไป" มีความหมายอย่างแท้จริงโดยทั่วไปแล้วก็ไม่จำเป็นต้องมีโรงงานและเรื่องไร้สาระอื่น ๆ ทั้งหมดที่เกิดจากชื่อของรูปแบบการออกแบบ มันเป็นหนึ่งในหลาย ๆ เหตุผลที่ฉันชอบทำงานในภาษาแบบไดนามิก
คำเตือนคอมไพเลอร์สำหรับพารามิเตอร์ทั่วไปที่ขาดหายไปซึ่งไม่มีจุดประสงค์ทำให้ภาษาไม่มีความหมายเช่น public String getName(Class<?> klazz){ return klazz.getName();}
ยาสามัญไม่เล่นดีกับอาร์เรย์
ข้อมูลประเภทที่หายไปทำให้การสะท้อนของการคัดเลือกนักแสดงและเทปพันสายไฟ
HashMap
HashMap<String, String>
ฉันคิดว่าคำตอบอื่น ๆ ได้กล่าวถึงเรื่องนี้ในระดับหนึ่ง แต่ไม่ชัดเจนมาก ปัญหาอย่างหนึ่งของยาชื่อสามัญก็คือการสูญเสียประเภทสามัญในระหว่างกระบวนการสะท้อนกลับ ตัวอย่างเช่น:
List<String> arr = new ArrayList<String>();
assertTrue( ArrayList.class, arr.getClass() );
TypeVarible[] types = arr.getClass().getTypedVariables();
น่าเสียดายที่ประเภทที่ส่งคืนไม่สามารถบอกคุณได้ว่าประเภททั่วไปของ arr คือ String มันแตกต่างกันเล็กน้อย แต่มันสำคัญ เนื่องจาก arr ถูกสร้างขึ้นที่ runtime ชนิดทั่วไปจะถูกลบใน runtime ดังนั้นคุณไม่สามารถหาได้ ในขณะที่บางคนกล่าวว่าArrayList<Integer>
มีลักษณะเหมือนกันArrayList<String>
จากมุมมองการสะท้อน
สิ่งนี้อาจไม่สำคัญสำหรับผู้ใช้ Generics แต่สมมุติว่าเราต้องการสร้างกรอบงานแฟนซีที่ใช้การสะท้อนเพื่อหาสิ่งแฟนซีเกี่ยวกับวิธีที่ผู้ใช้ประกาศประเภททั่วไปที่เป็นรูปธรรมของอินสแตนซ์
Factory<MySpecialObject> factory = new Factory<MySpecialObject>();
MySpecialObject obj = factory.create();
สมมติว่าเราต้องการให้โรงงานทั่วไปสร้างอินสแตนซ์MySpecialObject
เนื่องจากเป็นประเภททั่วไปที่เป็นรูปธรรมที่เราประกาศสำหรับอินสแตนซ์นี้ คลาสของโรงงานไม่สามารถซักถามตัวเองเพื่อค้นหาประเภทที่เป็นรูปธรรมที่ประกาศไว้สำหรับอินสแตนซ์นี้ได้เนื่องจาก Java ลบพวกเขา
ใน generics ของ. Net คุณสามารถทำสิ่งนี้ได้เพราะ ณ รันไทม์วัตถุจะรู้ว่ามันเป็นประเภททั่วไปเพราะคอมไพเลอร์ปฏิบัติตามมันเป็นไบนารี ด้วยการลบ Java ไม่สามารถทำได้
ฉันสามารถพูดสิ่งที่ดีเกี่ยวกับยาชื่อสามัญได้ แต่นั่นไม่ใช่คำถาม ฉันสามารถบ่นว่าพวกเขาไม่สามารถใช้งานได้ในเวลาทำงานและไม่ทำงานกับอาร์เรย์ แต่ถูกกล่าวถึง
ความรำคาญใจอย่างใหญ่หลวง: บางครั้งฉันก็เข้าสู่สถานการณ์ที่ไม่สามารถใช้ยาชื่อสามัญได้ (อาร์เรย์เป็นตัวอย่างที่ง่ายที่สุด) และฉันไม่สามารถเข้าใจได้ว่ายาชื่อสามัญไม่สามารถทำงานได้หรือว่าฉันแค่โง่ ฉันเกลียดที่ ยาชื่อสามัญควรทำงานทุกครั้ง ทุกครั้งที่ฉันไม่สามารถทำสิ่งที่ฉันต้องการโดยใช้ Java-the-language ฉันรู้ว่าปัญหาคือฉันและฉันรู้ว่าถ้าฉันยังคงผลักดันฉันจะไปที่นั่นในที่สุด ด้วยยาชื่อสามัญถ้าฉันขัดขืนเกินไปฉันจะเสียเวลามาก
แต่ปัญหาที่แท้จริงคือยาชื่อสามัญเพิ่มความซับซ้อนมากเกินไปเพื่อผลประโยชน์ที่น้อยเกินไป ในกรณีที่ง่ายที่สุดมันสามารถหยุดฉันจากการเพิ่มแอปเปิ้ลในรายการที่มีรถยนต์ ละเอียด. แต่หากไม่มีข้อมูลทั่วไปข้อผิดพลาดนี้จะทำให้ ClassCastException รวดเร็วมากในขณะใช้งานโดยเสียเวลาเพียงเล็กน้อย หากฉันเพิ่มรถที่มีที่นั่งสำหรับเด็กที่มีเด็กอยู่ในนั้นฉันต้องมีการเตือนเวลารวบรวมว่ารายการนั้นมีไว้สำหรับรถยนต์ที่มีที่นั่งสำหรับเด็กที่มีลิงชิมแปนซีอยู่หรือไม่ รายการอินสแตนซ์ของวัตถุธรรมดาเริ่มดูเหมือนความคิดที่ดี
รหัสทั่วไปมีคำและตัวอักษรจำนวนมากและใช้พื้นที่มากและใช้เวลาอ่านนานกว่า ฉันสามารถใช้เวลามากมายในการรับรหัสพิเศษทั้งหมดเพื่อให้ทำงานได้อย่างถูกต้อง เมื่อใดฉันจะรู้สึกฉลาดอย่างชาญฉลาด ฉันเสียเวลาไปหลายชั่วโมงหรือมากกว่านั้นและฉันก็ต้องสงสัยว่ามีใครอีกไหมที่จะสามารถหารหัสได้ ฉันต้องการที่จะมอบให้เพื่อการบำรุงรักษาให้กับผู้ที่มีความฉลาดน้อยกว่าตัวฉันเองหรือผู้ที่มีเวลาน้อยที่จะเสีย
ในทางกลับกัน (ฉันได้รับบาดเจ็บเล็กน้อยและรู้สึกว่าจำเป็นต้องให้สมดุล) มันดีเมื่อใช้คอลเลกชันที่เรียบง่ายและแผนที่ที่มีการเพิ่มใส่และได้รับการตรวจสอบเมื่อเขียนและมักจะไม่เพิ่มมาก ซับซ้อนรหัส( ถ้า มีคน อื่น เขียนคอลเลกชันหรือแผนที่) และ Java ดีกว่านี้กว่า C # ดูเหมือนว่าไม่มีคอลเลกชัน C # ที่ฉันต้องการใช้ในการจัดการข้อมูลทั่วไป (ฉันยอมรับว่าฉันมีรสนิยมแปลก ๆ ในคอลเลกชัน)
Fine. But without generics this error would throw a ClassCastException really quick at run time with little time wasted.
ขึ้นอยู่กับว่าต้องใช้เวลารันนานเท่าใดระหว่างการเริ่มต้นโปรแกรมและการกดบรรทัดของรหัสที่เป็นปัญหา หากผู้ใช้รายงานข้อผิดพลาดและใช้เวลาหลายนาที (หรือกรณีที่เลวร้ายที่สุดหลายชั่วโมงหรือหลายวัน) ในการทำซ้ำการตรวจสอบเวลาที่คอมไพล์เริ่มดูดีขึ้นเรื่อย ๆ ...