ยาชื่อสามัญมีประโยชน์อย่างไรทำไมต้องใช้?


87

ฉันคิดว่าจะเสนอซอฟต์บอลนี้ให้ใครก็ได้ที่อยากจะตีมันออกจากสวนสาธารณะ ยาชื่อสามัญคืออะไรข้อดีของยาชื่อสามัญคืออะไรทำไมฉันควรใช้มันที่ไหน? โปรดให้มันเป็นพื้นฐานพอสมควร ขอบคุณ.


1
ซ้ำของstackoverflow.com/questions/77632/... แต่คำตอบง่ายๆแม้ว่าคอลเล็กชันจะไม่ได้เป็นส่วนหนึ่งของ API ของคุณฉันก็ไม่ชอบการแคสต์ที่ไม่จำเป็นแม้ในการใช้งานภายใน
Matthew Flaschen

คำถามค่อนข้างคล้ายกัน แต่ฉันไม่คิดว่าคำตอบที่ยอมรับจะตอบคำถามนี้
Tom Hawtin - แทคไลน์

1
ชำระเงินด้วยstackoverflow.com/questions/520527
Clint Miller

4
@MatthewFlasc แปลกที่ซ้ำกันของคุณนำไปสู่คำถามนี้ ... ผิดพลาดในเมทริกซ์!
jcollum

@jcollum ฉันคิดว่าคำถามเดิมที่ฉันโพสต์ความคิดเห็นนั้นรวมเข้ากับคำถามนี้
Matthew Flaschen

คำตอบ:


126
  • ช่วยให้คุณสามารถเขียนโค้ด / ใช้วิธีการไลบรารีซึ่งเป็นประเภทที่ปลอดภัยกล่าวคือรายการ <string> รับประกันว่าเป็นรายการสตริง
  • อันเป็นผลมาจากการใช้งานทั่วไปคอมไพลเลอร์สามารถทำการตรวจสอบเวลาคอมไพล์เกี่ยวกับรหัสเพื่อความปลอดภัยของชนิดกล่าวคือคุณกำลังพยายามใส่ int ลงในรายการสตริงนั้นหรือไม่? การใช้ ArrayList จะทำให้เกิดข้อผิดพลาดรันไทม์ที่โปร่งใสน้อยลง
  • เร็วกว่าการใช้ออบเจ็กต์เนื่องจากหลีกเลี่ยงการชกมวย / แกะกล่อง (โดยที่. net ต้องแปลงประเภทค่าเป็นชนิดอ้างอิงหรือในทางกลับกัน ) หรือการส่งจากวัตถุเป็นประเภทอ้างอิงที่ต้องการ
  • ช่วยให้คุณสามารถเขียนโค้ดที่ใช้ได้กับหลายประเภทที่มีลักษณะการทำงานที่เหมือนกันกล่าวคือ Dictionary <string, int> ใช้รหัสอ้างอิงเดียวกันกับ Dictionary <DateTime, double>; โดยใช้ชื่อสามัญทีมเฟรมเวิร์กต้องเขียนโค้ดเพียงชิ้นเดียวเพื่อให้ได้ผลลัพธ์ทั้งสองอย่างด้วยข้อดีดังกล่าว

9
อ่าดีมากและฉันชอบรายการหัวข้อย่อย ฉันคิดว่านี่เป็นคำตอบที่สมบูรณ์และกระชับที่สุดจนถึงตอนนี้
MrBoJangles

คำอธิบายทั้งหมดอยู่ในบริบท "คอลเลกชัน" ฉันกำลังมองหารูปลักษณ์ที่กว้างขึ้น มีความคิด?
jungle_mole

คำตอบที่ดีฉันแค่ต้องการเพิ่มข้อดีอีก 2 ข้อข้อ จำกัด ทั่วไปมีประโยชน์ 2 ประการข้าง 1- คุณสามารถใช้คุณสมบัติของประเภทข้อ จำกัด ในประเภททั่วไป (เช่นที่ T: IComparable ให้ใช้ CompareTo) 2- คนที่จะเขียน รหัสหลังจากที่คุณจะรู้ว่าจะทำอะไร
Hamit YILDIRIM

52

เกลียดตัวเองมากจริงๆ ฉันเกลียดการพิมพ์สิ่งเดียวกันบ่อยกว่าที่ฉันต้องทำ ฉันไม่ชอบการพูดซ้ำหลาย ๆ ครั้งโดยมีความแตกต่างเล็กน้อย

แทนที่จะสร้าง:

class MyObjectList  {
   MyObject get(int index) {...}
}
class MyOtherObjectList  {
   MyOtherObject get(int index) {...}
}
class AnotherObjectList  {
   AnotherObject get(int index) {...}
}

ฉันสามารถสร้างคลาสที่ใช้ซ้ำได้ 1 คลาส ... (ในกรณีที่คุณไม่ต้องการใช้คอลเลกชันดิบด้วยเหตุผลบางประการ)

class MyList<T> {
   T get(int index) { ... }
}

ตอนนี้ฉันมีประสิทธิภาพมากขึ้น 3 เท่าและฉันต้องรักษาสำเนาไว้เพียงชุดเดียว ทำไมคุณไม่ต้องการรักษารหัสให้น้อยลง?

นอกจากนี้ยังเป็นจริงสำหรับคลาสที่ไม่ใช่คอลเลกชันเช่น a Callable<T>หรือคลาสReference<T>ที่ต้องโต้ตอบกับคลาสอื่น คุณต้องการขยายCallable<T>และ Future<T>ทุกคลาสที่เกี่ยวข้องเพื่อสร้างเวอร์ชันที่ปลอดภัยหรือไม่?

ฉันไม่.


1
ฉันไม่แน่ใจว่าฉันเข้าใจ ฉันไม่ได้แนะนำให้คุณทำกระดาษห่อหุ้ม "MyObject" ซึ่งจะดูน่ากลัว ฉันแนะนำว่าหากคอลเล็กชันวัตถุของคุณเป็นคอลเลคชันรถยนต์คุณควรเขียนวัตถุ Garage มันจะไม่ได้รับ (รถยนต์) มันจะมีวิธีการเช่น buyFuel (100) ที่จะซื้อน้ำมัน 100 ดอลลาร์และแจกจ่ายให้กับรถยนต์ที่ต้องการมากที่สุด ฉันกำลังพูดถึงคลาสธุรกิจที่แท้จริงไม่ใช่แค่ "Wrapper" ตัวอย่างเช่นคุณแทบจะไม่ได้รับ (รถ) หรือวนซ้ำพวกมันนอกคอลเลกชัน - คุณไม่ได้ขอให้ Object เป็นสมาชิก แต่คุณขอให้ดำเนินการให้
Bill K

แม้แต่ในโรงรถของคุณคุณก็ควรมีความปลอดภัย ดังนั้นคุณมี List <Cars>, ListOfCars หรือคุณแคสต์ทุกที่ ฉันไม่รู้สึกว่าจำเป็นต้องระบุประเภทมากกว่าเวลาขั้นต่ำที่จำเป็น
James Schek

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

Bill - คุณอาจมีประเด็นเกี่ยวกับการไม่ใช้การห่อหุ้ม แต่ข้อโต้แย้งที่เหลือของคุณเป็นการเปรียบเทียบที่ไม่ดี ค่าใช้จ่ายในการเรียนรู้ไวยากรณ์ใหม่มีน้อยในกรณีนี้ (เปรียบเทียบกับ lambdas ใน C #); การเปรียบเทียบสิ่งนี้กับการแก้ไขรัฐธรรมนูญไม่มีเหตุผล รัฐธรรมนูญไม่มีเจตนาที่จะระบุรหัสจราจร เหมือนกับการเปลี่ยนไปใช้เวอร์ชันพิมพ์ Serif-Font บนบอร์ดโปสเตอร์แทนที่จะเป็นสำเนาที่เขียนด้วยมือบนกระดาษ เป็นความหมายเดียวกัน แต่ชัดเจนและอ่านง่ายกว่ามาก
James Schek

ตัวอย่างทำให้แนวคิดสามารถนำไปใช้ได้จริง ขอบคุณสำหรับสิ่งนั้น
RayLoveless

22

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

แต่ฉันสงสัยว่าคุณรู้ดีอยู่แล้ว

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

ตอนแรกฉันไม่เห็นประโยชน์ของยาชื่อสามัญเหมือนกัน ฉันเริ่มเรียนรู้ Java จากไวยากรณ์ 1.4 (แม้ว่า Java 5 จะหมดในเวลานั้น) และเมื่อฉันพบกับ generics ฉันรู้สึกว่ามันต้องเขียนโค้ดมากกว่าและฉันก็ไม่เข้าใจประโยชน์จริงๆ

IDE สมัยใหม่ทำให้การเขียนโค้ดด้วย generics ง่ายขึ้น

IDE ที่ทันสมัยและเหมาะสมส่วนใหญ่ฉลาดพอที่จะช่วยในการเขียนโค้ดด้วย generics โดยเฉพาะอย่างยิ่งการเติมโค้ด

นี่คือตัวอย่างของการสร้างMap<String, Integer>ไฟล์HashMap. รหัสที่ฉันต้องพิมพ์คือ:

Map<String, Integer> m = new HashMap<String, Integer>();

HashMapและแน่นอนว่าเป็นจำนวนมากที่จะพิมพ์เพียงเพื่อให้ใหม่ อย่างไรก็ตามในความเป็นจริงฉันต้องพิมพ์เท่านี้ก่อนที่ Eclipse จะรู้ว่าฉันต้องการอะไร:

Map<String, Integer> m = new Ha Ctrl+Space

จริงอยู่ฉันไม่จำเป็นต้องเลือกHashMapจากรายชื่อผู้สมัคร แต่โดยพื้นฐานแล้ว IDE รู้ว่าจะต้องเพิ่มอะไรบ้างรวมถึงประเภททั่วไปด้วย ด้วยเครื่องมือที่เหมาะสมการใช้ยาชื่อสามัญก็ไม่ได้แย่เกินไป

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

ข้อได้เปรียบที่สำคัญของ generics มาจากวิธีที่เล่นได้ดีกับคุณสมบัติใหม่ของ Java 5 นี่คือตัวอย่างของการโยนจำนวนเต็มเป็น a Setและคำนวณผลรวม:

Set<Integer> set = new HashSet<Integer>();
set.add(10);
set.add(42);

int total = 0;
for (int i : set) {
  total += i;
}

ในโค้ดนั้นมีคุณลักษณะใหม่ของ Java 5 อยู่สามประการ:

ประการแรกชื่อสามัญและการทำออโตบ็อกซ์แบบดั้งเดิมอนุญาตให้มีบรรทัดต่อไปนี้:

set.add(10);
set.add(42);

จำนวนเต็ม10จะถูกทำให้เป็นออโต้บ็อกซ์Integerโดยมีค่า10เป็น (และเหมือนกันสำหรับ42) จากนั้นIntegerจะถูกโยนเข้าไปในสิ่งSetที่เรียกว่าถือIntegers การพยายามโยนเข้าไปStringอาจทำให้เกิดข้อผิดพลาดในการคอมไพล์

ถัดไปสำหรับแต่ละลูปจะใช้ทั้งสามอย่าง:

for (int i : set) {
  total += i;
}

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

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

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

แก้ไข - ตัวอย่างที่ไม่มีชื่อสามัญ

ต่อไปนี้เป็นภาพประกอบของSetตัวอย่างข้างต้นโดยไม่ต้องใช้ยาชื่อสามัญ เป็นไปได้ แต่ไม่น่าพอใจ:

Set set = new HashSet();
set.add(10);
set.add(42);

int total = 0;
for (Object o : set) {
  total += (Integer)o;
}

(หมายเหตุ: โค้ดด้านบนจะสร้างคำเตือนการแปลงที่ไม่ได้ตรวจสอบในเวลาคอมไพล์)

เมื่อมีการใช้คอลเลกชันที่ไม่ใช่ยาชื่อสามัญ, Objectประเภทที่มีการลงนามในคอลเลกชันนี้เป็นวัตถุชนิด ดังนั้นในตัวอย่างนี้ a Objectคือสิ่งที่ถูกaddแก้ไขในชุด

set.add(10);
set.add(42);

ในบรรทัดข้างต้นการทำกล่องอัตโนมัติกำลังอยู่ในระหว่างการเล่นซึ่งเป็นintค่าดั้งเดิม10และ42กำลังถูกใส่กล่องอัตโนมัติลงในIntegerวัตถุซึ่งจะถูกเพิ่มลงในไฟล์Set. อย่างไรก็ตามโปรดทราบว่าIntegerอ็อบเจ็กต์ถูกจัดการในรูปแบบObjects เนื่องจากไม่มีข้อมูลประเภทใดที่จะช่วยให้คอมไพเลอร์ทราบว่าSetควรคาดหวังประเภทใด

for (Object o : set) {

นี่คือส่วนที่มีความสำคัญ เหตุผลที่สำหรับแต่ละลูปทำงานได้เนื่องจากอินเทอร์เฟซSetใช้งานIterableซึ่งส่งคืนข้อมูลที่Iteratorมีชนิดหากมีอยู่ ( Iterator<T>นั่นคือ.)

แต่เนื่องจากไม่มีข้อมูลประเภทที่SetจะกลับIteratorซึ่งจะกลับค่าในSetขณะที่ObjectS, และนั่นคือเหตุผลองค์ประกอบถูกเรียกในสำหรับแต่ละวงจะต้องObjectเป็นชนิด

ตอนนี้เมื่อObjectดึงข้อมูลมาSetแล้วจำเป็นต้องส่งไปยังIntegerด้วยตนเองเพื่อดำเนินการเพิ่มเติม:

  total += (Integer)o;

นี่เป็น typecast จะดำเนินการจากไปยังObject Integerในกรณีนี้เรารู้ว่าสิ่งนี้จะใช้งานได้เสมอ แต่การพิมพ์ด้วยตนเองมักทำให้ฉันรู้สึกว่าเป็นรหัสที่เปราะบางซึ่งอาจเสียหายได้หากมีการเปลี่ยนแปลงเล็กน้อยที่อื่น (ฉันรู้สึกว่าทุกคนClassCastExceptionรอคอยที่จะเกิดขึ้น แต่ฉันพูดนอกเรื่อง ... )

Integerจะไม่มีกล่องตอนนี้เป็นintและได้รับอนุญาตให้ดำเนินการนอกจากนี้ลงในตัวแปรinttotal

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


การรวมเข้ากับการปรับปรุงสำหรับลูปนั้นดีมาก (แม้ว่าฉันคิดว่าวงแช่งนั้นพังเพราะฉันไม่สามารถใช้ไวยากรณ์เดียวกันกับคอลเลคชันที่ไม่ใช่แบบทั่วไปได้ แต่ควรใช้ cast / autobox เป็น int แต่ก็มี ข้อมูลทั้งหมดที่ต้องการ!) แต่คุณพูดถูกความช่วยเหลือใน IDE น่าจะดี ฉันยังไม่รู้ว่ามันเพียงพอที่จะปรับไวยากรณ์เพิ่มเติมหรือไม่ แต่อย่างน้อยนั่นก็เป็นสิ่งที่ฉันจะได้รับประโยชน์ (ซึ่งตอบคำถามของฉันได้)
Bill K

@Bill K: ฉันได้เพิ่มตัวอย่างการใช้คุณสมบัติ Java 5 โดยไม่ต้องใช้ generics
coobird

นั่นเป็นจุดที่ดีฉันไม่ได้ใช้มัน (ฉันบอกว่าฉันติดอยู่ที่ 1.4 และเร็วมาหลายปีแล้ว) ฉันยอมรับว่ามีข้อดี แต่ข้อสรุปที่ฉันไปถึงคือ A) พวกเขามักจะได้เปรียบคนที่ไม่ได้ใช้ OO อย่างถูกต้องและมีประโยชน์น้อยกว่าสำหรับคนที่ทำและ B) ข้อดีของคุณ รายการที่ระบุไว้เป็นข้อดี แต่แทบจะไม่คุ้มค่าพอที่จะพิสูจน์ได้แม้แต่การเปลี่ยนแปลงไวยากรณ์เพียงเล็กน้อยนับประสาการเปลี่ยนแปลงขนาดใหญ่ที่จำเป็นสำหรับการใช้งานทั่วไปตามที่ใช้ใน Java แต่อย่างน้อยก็ยังมีข้อดี
Bill K

15

หากคุณกำลังจะค้นหาฐานข้อมูลข้อผิดพลาด Java ก่อน 1.5 ได้รับการปล่อยตัวคุณจะพบข้อบกพร่องเจ็ดครั้งมากขึ้นด้วยกว่าNullPointerException ClassCastExceptionดังนั้นดูเหมือนว่าจะไม่เป็นคุณสมบัติที่ยอดเยี่ยมในการค้นหาจุดบกพร่องหรืออย่างน้อยข้อบกพร่องที่ยังคงมีอยู่หลังจากการทดสอบควันเล็กน้อย

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

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


6
ไม่เพียง แต่ได้รับการบันทึกไว้ (ซึ่งโดยตัวมันเองเป็นชัยชนะที่ยิ่งใหญ่) คอมไพเลอร์บังคับใช้
James Schek

จุดดี แต่ไม่สามารถทำได้โดยการใส่คอลเล็กชันในคลาสที่กำหนด "คอลเล็กชันของวัตถุประเภทนี้" ใช่หรือไม่
Bill K

1
เจมส์: ใช่ที่จุดเกี่ยวกับเรื่องที่มีการจัดทำเอกสารในรหัส
Tom Hawtin - แทคไลน์

ฉันไม่คิดว่าฉันรู้จักห้องสมุดดีๆที่ใช้คอลเลกชันสำหรับ "อัลกอริทึม" (อาจหมายความว่าคุณกำลังพูดถึง "ฟังก์ชัน" ซึ่งทำให้ฉันเชื่อว่าควรเป็นวิธีการในวัตถุของคอลเล็กชัน) ฉันคิดว่า JDK มักจะแยกอาร์เรย์ที่ดีและสะอาดซึ่งเป็นสำเนาของข้อมูลเพื่อไม่ให้ยุ่งเหยิง - อย่างน้อยก็บ่อยกว่านั้น
Bill K

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

11

ยาชื่อสามัญในชวาอำนวยความสะดวกในหลายรูปแบบตัวแปร โดยใช้พารามิเตอร์ประเภทคุณสามารถส่งผ่านอาร์กิวเมนต์ไปยังประเภทได้ เช่นเดียวกับวิธีการเช่นเดียวกับString foo(String s)รูปแบบพฤติกรรมบางอย่างที่ไม่เพียง แต่สำหรับสตริงโดยเฉพาะอย่างยิ่ง แต่สำหรับสตริงใด ๆsดังนั้นประเภทเช่นList<T>แบบจำลองพฤติกรรมบางอย่างที่ไม่เพียง แต่สำหรับประเภทที่เฉพาะเจาะจง แต่สำหรับประเภทใดList<T>กล่าวว่าสำหรับประเภทใดTมีประเภทของListซึ่งเป็นธาตุT s ดังนั้นListเป็นจริงเป็นตัวสร้างประเภท ใช้ประเภทเป็นอาร์กิวเมนต์และสร้างประเภทอื่นเป็นผลลัพธ์

นี่คือตัวอย่างสองสามประเภทของประเภททั่วไปที่ฉันใช้ทุกวัน ขั้นแรกอินเทอร์เฟซทั่วไปที่มีประโยชน์มาก:

public interface F<A, B> {
  public B f(A a);
}

อินเทอร์เฟซนี้บอกว่าสำหรับสองประเภทAและBมีฟังก์ชัน (เรียกว่าf) ที่รับAและส่งคืนไฟล์B. เมื่อคุณใช้อินเทอร์เฟซนี้AและBสามารถเป็นประเภทใดก็ได้ที่คุณต้องการตราบเท่าที่คุณมีฟังก์ชันfที่ใช้เวลาเดิมและส่งคืนค่าหลัง นี่คือตัวอย่างการใช้งานอินเทอร์เฟซ:

F<Integer, String> intToString = new F<Integer, String>() {
  public String f(int i) {
    return String.valueOf(i);
  }
}

ก่อนชื่อสามัญความหลากหลายสามารถทำได้โดยการคลาสย่อยโดยใช้extendsคีย์เวิร์ด ด้วย generics เราสามารถกำจัด subclassing และใช้ความหลากหลายเชิงพาราเมตริกแทนได้ ตัวอย่างเช่นพิจารณาคลาสพารามิเตอร์ (ทั่วไป) ที่ใช้ในการคำนวณรหัสแฮชสำหรับประเภทใด ๆ แทนที่จะแทนที่ Object.hashCode () เราจะใช้คลาสทั่วไปเช่นนี้:

public final class Hash<A> {
  private final F<A, Integer> hashFunction;

  public Hash(final F<A, Integer> f) {
    this.hashFunction = f;
  }

  public int hash(A a) {
    return hashFunction.f(a);
  }
}

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

generics ของ Java ยังไม่สมบูรณ์แบบ คุณสามารถนามธรรมเหนือประเภทได้ แต่คุณไม่สามารถสร้างนามธรรมทับตัวสร้างประเภทได้ กล่าวคือคุณสามารถพูดว่า "สำหรับ T ประเภทใดก็ได้" แต่คุณไม่สามารถพูดว่า "สำหรับ T ประเภทใดก็ได้ที่รับพารามิเตอร์ประเภท A"

ฉันเขียนบทความเกี่ยวกับขีด จำกัด ของ Java generics ที่นี่

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

Wereas ก่อน generics คุณอาจมีชั้นเรียนเช่นWidgetขยายFooWidget, BarWidgetและBazWidgetมียาชื่อสามัญที่คุณสามารถมีชั้นเดียวทั่วไปWidget<A>ที่ใช้Foo, BarหรือBazในตัวสร้างที่จะให้คุณWidget<Foo>, และWidget<Bar>Widget<Baz>


มันจะเป็นจุดที่ดีเกี่ยวกับคลาสย่อยหาก Java ไม่มีอินเทอร์เฟซ จะไม่ถูกต้องที่จะมี FooWidget, BarWidtet และ BazWidget ทั้งหมดใช้ Widget? ฉันอาจจะผิดในเรื่องนี้ก็ได้ .. แต่นี่เป็นประเด็นที่ดีจริงๆ
Bill K

เหตุใดคุณจึงต้องมีคลาสทั่วไปแบบไดนามิกเพื่อคำนวณค่าแฮช ฟังก์ชั่นคงที่พร้อมพารามิเตอร์ที่เหมาะสมกับงานมีประสิทธิภาพมากขึ้นไม่ใช่หรือ
Ant_222

8

Generics หลีกเลี่ยงการตีชกมวยและการแกะกล่อง โดยทั่วไปให้ดูที่ ArrayList vs List <T> ทั้งสองทำสิ่งสำคัญเหมือนกัน แต่ List <T> จะเร็วกว่ามากเพราะคุณไม่ต้องใส่กล่องไปที่ / จากวัตถุ


นั่นคือแก่นแท้ของมันไม่ใช่เหรอ? ดี.
MrBoJangles

ไม่ใช่ทั้งหมด มันมีประโยชน์สำหรับประเภทความปลอดภัย + การหลีกเลี่ยงการร่ายสำหรับประเภทการอ้างอิงด้วยโดยไม่ต้องต่อยมวย / แกะกล่องเลย
ljs

ดังนั้นฉันเดาคำถามที่ตามมาคือ "ทำไมฉันถึงต้องการใช้ ArrayList?" และเป็นอันตรายต่อคำตอบฉันขอบอกว่า ArrayLists นั้นใช้ได้ตราบใดที่คุณไม่คาดหวังที่จะใส่กล่องและคลายค่าของพวกเขามากนัก ความคิดเห็น?
MrBoJangles

ไม่พวกเขาเสียชีวิตเมื่อ. net 2; มีประโยชน์สำหรับความเข้ากันได้แบบย้อนหลังเท่านั้น ArrayLists ช้ากว่าไม่ใช่ประเภทที่ปลอดภัยและต้องการการชกมวย / แกะกล่องหรือการคัดเลือก
ljs

หากคุณไม่ได้จัดเก็บประเภทค่า (เช่น ints หรือ structs) จะไม่มีการชกมวยเมื่อใช้ ArrayList - คลาสนั้นเป็น 'วัตถุ' อยู่แล้ว การใช้ List <T> ยังดีกว่ามากเนื่องจากประเภทของความปลอดภัยและถ้าคุณต้องการจัดเก็บวัตถุจริงๆการใช้ List <object> จะดีกว่า
Wilka

6

ประโยชน์ที่ดีที่สุดสำหรับ Generics คือการใช้โค้ดซ้ำ สมมติว่าคุณมีวัตถุทางธุรกิจจำนวนมากและคุณจะเขียนโค้ดที่คล้ายกันมากสำหรับแต่ละเอนทิตีเพื่อดำเนินการแบบเดียวกัน (IE Linq กับการดำเนินการ SQL)

ด้วย generics คุณสามารถสร้างคลาสที่จะสามารถใช้งานได้โดยกำหนดประเภทใด ๆ ที่สืบทอดมาจากคลาสพื้นฐานที่กำหนดหรือใช้อินเทอร์เฟซที่กำหนดดังนี้:

public interface IEntity
{

}

public class Employee : IEntity
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int EmployeeID { get; set; }
}

public class Company : IEntity
{
    public string Name { get; set; }
    public string TaxID { get; set }
}

public class DataService<ENTITY, DATACONTEXT>
    where ENTITY : class, IEntity, new()
    where DATACONTEXT : DataContext, new()
{

    public void Create(List<ENTITY> entities)
    {
        using (DATACONTEXT db = new DATACONTEXT())
        {
            Table<ENTITY> table = db.GetTable<ENTITY>();

            foreach (ENTITY entity in entities)
                table.InsertOnSubmit (entity);

            db.SubmitChanges();
        }
    }
}

public class MyTest
{
    public void DoSomething()
    {
        var dataService = new DataService<Employee, MyDataContext>();
        dataService.Create(new Employee { FirstName = "Bob", LastName = "Smith", EmployeeID = 5 });
        var otherDataService = new DataService<Company, MyDataContext>();
            otherDataService.Create(new Company { Name = "ACME", TaxID = "123-111-2233" });

    }
}

สังเกตการใช้บริการเดิมซ้ำโดยใช้ประเภทต่างๆในวิธี DoSomething ด้านบน สง่างามอย่างแท้จริง!

มีเหตุผลที่ดีอื่น ๆ อีกมากมายในการใช้ยาชื่อสามัญสำหรับงานของคุณนี่คือสิ่งที่ฉันชอบ


5

ฉันชอบพวกเขาเพราะพวกเขาให้วิธีที่รวดเร็วในการกำหนดประเภทที่กำหนดเอง (ตามที่ฉันใช้อยู่)

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

Dictionary<int, string> dictionary = new Dictionary<int, string>();

และคอมไพเลอร์ / IDE จะทำการยกของหนักที่เหลือ พจนานุกรมโดยเฉพาะให้คุณใช้ประเภทแรกเป็นคีย์ (ไม่มีค่าซ้ำ)


5
  • คอลเลกชันที่พิมพ์ - แม้ว่าคุณจะไม่ต้องการใช้คุณก็มีแนวโน้มที่จะต้องจัดการกับพวกเขาจากห้องสมุดอื่น ๆ แหล่งอื่น

  • การพิมพ์ทั่วไปในการสร้างคลาส:

    ชั้นสาธารณะ Foo <T> {public T get () ...

  • หลีกเลี่ยงการแคสต์ - ฉันไม่ชอบสิ่งที่ชอบมาโดยตลอด

    ตัวเปรียบเทียบใหม่ {public int CompareTo (Object o) {if (o instanceof classIcareAbout) ...

โดยพื้นฐานแล้วคุณกำลังตรวจสอบเงื่อนไขที่ควรมีอยู่เนื่องจากอินเทอร์เฟซแสดงในรูปของวัตถุ

ปฏิกิริยาแรกเริ่มของฉันต่อยาชื่อสามัญคล้ายกับของคุณ - "ยุ่งเกินไปซับซ้อนเกินไป" ประสบการณ์ของฉันคือหลังจากใช้มันสักหน่อยคุณจะคุ้นเคยกับพวกเขาและรหัสโดยที่พวกเขาไม่ได้ระบุไว้ชัดเจนและไม่ค่อยสะดวก นอกเหนือจากนั้นส่วนที่เหลือของโลก java ก็ใช้พวกมันดังนั้นคุณจะต้องได้รับโปรแกรมในที่สุดใช่ไหม?


1
เพื่อความถูกต้องเกี่ยวกับการหลีกเลี่ยงการหล่อ: การหล่อเกิดขึ้นตามเอกสารของ Sun แต่ทำโดยคอมไพเลอร์ คุณเพียงแค่หลีกเลี่ยงการเขียนแคสต์ในโค้ดของคุณ
Demi

บางทีดูเหมือนว่าฉันจะติดอยู่ในกลุ่ม บริษัท ที่ยาวนานโดยไม่ต้องการที่จะไปสูงกว่า 1.4 ดังนั้นใครจะรู้ฉันอาจเกษียณก่อนอายุ 5 ขวบฉันคิดอยู่เหมือนกันว่าเมื่อเร็ว ๆ นี้การปลอม "SimpleJava" อาจเป็นแนวคิดที่น่าสนใจ - Java จะเพิ่มคุณสมบัติไปเรื่อย ๆ และสำหรับโค้ดจำนวนมากที่ฉันเคยเห็นมันจะไม่ดีต่อสุขภาพ ฉันจัดการกับคนที่ไม่สามารถเขียนโค้ดลูปได้แล้ว - ฉันเกลียดที่เห็นพวกเขาพยายามฉีดข้อมูลทั่วไปเข้าไปในลูปที่ไม่มีการควบคุม
Bill K

@ บิล K: ไม่กี่คนที่ต้องใช้พลังของยาชื่อสามัญอย่างเต็มที่ จำเป็นเฉพาะเมื่อคุณเขียนคลาสที่เป็นแบบทั่วไป
Eddie

5

เพื่อเป็นตัวอย่างที่ดี ลองนึกภาพคุณมีคลาสชื่อ Foo

public class Foo
{
   public string Bar() { return "Bar"; }
}

ตัวอย่างที่ 1 ตอนนี้คุณต้องการมีคอลเลกชันของวัตถุ Foo คุณมีสองตัวเลือก LIst หรือ ArrayList ซึ่งทั้งสองอย่างทำงานในลักษณะที่คล้ายกัน

Arraylist al = new ArrayList();
List<Foo> fl = new List<Foo>();

//code to add Foos
al.Add(new Foo());
f1.Add(new Foo());

ในโค้ดด้านบนถ้าฉันพยายามเพิ่มคลาสของ FireTruck แทน Foo ArrayList จะเพิ่ม แต่ Generic List ของ Foo จะทำให้เกิดข้อยกเว้น

ตัวอย่างที่สอง

ตอนนี้คุณมีรายการอาร์เรย์สองรายการแล้วและคุณต้องการเรียกใช้ฟังก์ชัน Bar () ในแต่ละรายการ เนื่องจาก hte ArrayList เต็มไปด้วย Objects คุณจึงต้องร่ายก่อนจึงจะสามารถเรียก bar ได้ แต่เนื่องจากรายการทั่วไปของ Foo มีได้เฉพาะ Foos คุณจึงเรียก Bar () ได้โดยตรง

foreach(object o in al)
{
    Foo f = (Foo)o;
    f.Bar();
}

foreach(Foo f in fl)
{
   f.Bar();
}

ฉันเดาว่าคุณตอบก่อนอ่านคำถาม นี่เป็นสองกรณีที่ไม่ได้ช่วยฉันมากนัก (อธิบายไว้ในคำถาม) มีกรณีอื่นที่เป็นประโยชน์หรือไม่?
Bill K

4

คุณไม่เคยเขียน method (หรือคลาส) โดยที่แนวคิดหลักของ method / class นั้นไม่ได้ผูกมัดกับชนิดข้อมูลเฉพาะของพารามิเตอร์ / ตัวแปรอินสแตนซ์ (คิดว่ารายการที่เชื่อมโยง, ฟังก์ชันสูงสุด / นาที, การค้นหาแบบไบนารี ฯลฯ ).

คุณไม่เคยหวังว่าคุณจะสามารถนำ algorthm / code มาใช้ซ้ำได้โดยไม่ต้องใช้การตัด n-paste ซ้ำหรือลดทอนการพิมพ์ที่หนักแน่น (เช่นฉันต้องการListสตริงไม่ใช่Listสิ่งที่ฉันหวังว่าจะเป็นสตริง!)

ว่าทำไมคุณควรต้องการที่จะใช้ยาชื่อสามัญ (หรือสิ่งที่ดีกว่า)


ฉันไม่เคยต้องคัดลอก / วางซ้ำสำหรับสิ่งประเภทนี้ (ไม่แน่ใจว่าจะเกิดขึ้นได้อย่างไร) คุณใช้อินเทอร์เฟซที่เหมาะกับความต้องการของคุณและใช้อินเทอร์เฟซนั้น กรณีที่หายากที่คุณไม่สามารถทำได้คือเมื่อคุณเขียนยูทิลิตี้บริสุทธิ์ (เช่นคอลเลกชัน) และสำหรับสิ่งเหล่านั้น (ตามที่ฉันอธิบายไว้ในคำถาม) มันไม่เคยเป็นปัญหาเลย ฉันประนีประนอมการพิมพ์ที่หนักแน่น - เช่นเดียวกับที่คุณต้องทำหากคุณใช้การสะท้อน คุณปฏิเสธที่จะใช้การสะท้อนอย่างแน่นอนเพราะคุณไม่สามารถพิมพ์แรง ๆ ได้หรือไม่? (ถ้าฉันต้องใช้การสะท้อนแสงฉันก็แค่ห่อหุ้มมันเหมือนคอลเลกชัน)
Bill K

3

อย่าลืมว่ายาชื่อสามัญไม่ได้ใช้เฉพาะในชั้นเรียนเท่านั้น แต่ยังสามารถใช้โดยวิธีการได้อีกด้วย ตัวอย่างเช่นใช้ข้อมูลโค้ดต่อไปนี้:

private <T extends Throwable> T logAndReturn(T t) {
    logThrowable(t); // some logging method that takes a Throwable
    return t;
}

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

    ...
} catch (MyException e) {
    throw logAndReturn(e);
}

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

นี่เป็นเพียงตัวอย่างง่ายๆของการใช้วิธีการทั่วไป ยังมีอีกหลายอย่างที่คุณสามารถทำได้ด้วยวิธีการทั่วไป ที่เจ๋งที่สุดในความคิดของฉันคือประเภทที่อนุมานด้วยยาชื่อสามัญ ใช้ตัวอย่างต่อไปนี้ (นำมาจาก Effective Java 2nd Edition ของ Josh Bloch):

...
Map<String, Integer> myMap = createHashMap();
...
public <K, V> Map<K, V> createHashMap() {
    return new HashMap<K, V>();
}

สิ่งนี้ไม่ได้ทำมากนัก แต่จะลดความยุ่งเหยิงลงเมื่อประเภททั่วไปมีความยาว (หรือซ้อนกันเช่นMap<String, List<String>>)


ฉันไม่คิดว่าเป็นเรื่องจริงเกี่ยวกับข้อยกเว้น มันทำให้ฉันคิด แต่ฉันแน่ใจ 95% ว่าคุณจะได้ผลลัพธ์เหมือนกันทุกประการโดยไม่ต้องใช้ generics เลย (และรหัสที่ชัดเจนกว่า) ข้อมูลประเภทเป็นส่วนหนึ่งของวัตถุไม่ใช่สิ่งที่คุณส่งไป ฉันอยากจะลองดู - บางทีฉันอาจจะพรุ่งนี้ที่ทำงานและติดต่อกลับไปหาคุณ .. บอกคุณว่าฉันจะลองและถ้าคุณถูกฉันจะผ่านไป รายชื่อโพสต์ของคุณและโหวตให้ 5 โพสต์ของคุณ :) หากไม่เป็นเช่นนั้นคุณอาจต้องการพิจารณาว่าความพร้อมใช้งานของยาชื่อสามัญทำให้คุณซับซ้อนโค้ดของคุณโดยไม่มีข้อได้เปรียบ
Bill K

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

โอ้ฉันเข้าใจละ. ดังนั้นจึงหลีกเลี่ยงไม่ให้นักแสดงออกมา ... จุดที่ดี ฉันเป็นหนี้คุณ 5.
Bill K

2

ข้อได้เปรียบหลักตามที่ Mitchel ชี้ให้เห็นคือการพิมพ์ที่ชัดเจนโดยไม่จำเป็นต้องกำหนดหลายชั้นเรียน

ด้วยวิธีนี้คุณสามารถทำสิ่งต่างๆเช่น:

List<SomeCustomClass> blah = new List<SomeCustomClass>();
blah[0].SomeCustomFunction();

หากไม่มียาชื่อสามัญคุณจะต้องโยน blah [0] ไปยังประเภทที่ถูกต้องเพื่อเข้าถึงฟังก์ชันของมัน


เมื่อเลือกแล้วฉันไม่ควรแคสมากกว่านักแสดง
MrBoJangles

การพิมพ์ที่ชัดเจนเป็นลักษณะที่ดีที่สุดของ imho ทั่วไปโดยเฉพาะอย่างยิ่งเมื่อได้รับการตรวจสอบประเภทเวลาคอมไพล์ที่อนุญาต
ljs

2

jvm จะร่ายต่อไป ... มันสร้างโค้ดโดยปริยายซึ่งถือว่าประเภททั่วไปเป็น "Object" และสร้าง casts ไปยังอินสแตนซ์ที่ต้องการ ชื่อสามัญของ Java เป็นเพียงน้ำตาลที่เป็นประโยค


2

ฉันรู้ว่านี่เป็นคำถาม C # แต่ชื่อสามัญก็มีการใช้ในภาษาอื่นเช่นกันและการใช้ / เป้าหมายก็ค่อนข้างคล้ายกัน

คอลเลกชัน Java ใช้genericsตั้งแต่ Java 1.5 ดังนั้นจุดที่ดีที่จะใช้คือเมื่อคุณสร้างวัตถุที่มีลักษณะคล้ายคอลเลกชันของคุณเอง

ตัวอย่างที่ฉันเห็นเกือบทุกที่คือคลาส Pair ซึ่งมีสองวัตถุ แต่ต้องการจัดการกับวัตถุเหล่านั้นด้วยวิธีทั่วไป

class Pair<F, S> {
    public final F first;
    public final S second;

    public Pair(F f, S s)
    { 
        first = f;
        second = s;   
    }
}  

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

Generics ยังสามารถกำหนดขอบเขตด้วยคำหลัก 'super' และ 'expands' ตัวอย่างเช่นหากคุณต้องการจัดการกับประเภททั่วไป แต่ต้องการให้แน่ใจว่าขยายคลาสที่เรียกว่า Foo (ซึ่งมีเมธอด setTitle):

public class FooManager <F extends Foo>{
    public void setTitle(F foo, String title) {
        foo.setTitle(title);
    }
}

แม้ว่าจะไม่ค่อยน่าสนใจในตัวเอง แต่ก็มีประโยชน์ที่จะทราบว่าเมื่อใดก็ตามที่คุณจัดการกับ FooManager คุณก็รู้ว่ามันจะจัดการประเภท MyClass และ MyClass นั้นขยาย Foo


2

จากเอกสาร Sun Java เพื่อตอบสนองต่อ "เหตุใดฉันจึงควรใช้ generics":

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


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

@ บิล K: ถ้ารหัสถูกควบคุมอย่างแน่นหนา (นั่นคือคุณใช้เท่านั้น) บางทีคุณอาจจะไม่เจอปัญหานี้ เยี่ยมมาก! ความเสี่ยงที่ใหญ่ที่สุดคือในโครงการขนาดใหญ่ที่มีคนทำงานหลายคนเช่น IMO เป็นตาข่ายนิรภัยและ "แนวทางปฏิบัติที่ดีที่สุด"
Demi

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

@ บิล K: ฉันเคยทำงานกับคนที่มีระดับความสามารถแตกต่างกันมาก ฉันไม่มีความหรูหราในการเลือกว่าฉันจะแบ่งปันโครงการกับใคร ดังนั้นความปลอดภัยของประเภทจึงสำคัญมากสำหรับฉัน ใช่ฉันพบข้อบกพร่อง (และแก้ไข) โดยการแปลงรหัสเพื่อใช้ Generics
Eddie

1

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

การใช้รายการทั่วไปคุณสามารถมี List List สิ่งที่คุณต้องการและคุณสามารถอ้างอิงการพิมพ์ที่ชัดเจนได้ตลอดเวลาคุณไม่จำเป็นต้องแปลงหรืออะไรเหมือนกับที่คุณทำกับ Array หรือ Standard List


1

Generics ช่วยให้คุณใช้การพิมพ์ที่ชัดเจนสำหรับวัตถุและโครงสร้างข้อมูลที่ควรจะถือวัตถุใด ๆ นอกจากนี้ยังช่วยกำจัดการพิมพ์ที่น่าเบื่อและมีราคาแพงเมื่อดึงวัตถุจากโครงสร้างทั่วไป (การชกมวย / แกะกล่อง)

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


1

หากคอลเล็กชันของคุณมีประเภทค่าพวกเขาไม่จำเป็นต้องใส่กล่อง / unbox ให้กับวัตถุเมื่อใส่เข้าไปในคอลเลกชันดังนั้นประสิทธิภาพของคุณจึงเพิ่มขึ้นอย่างมาก ส่วนเสริมเจ๋ง ๆ เช่น resharper สามารถสร้างโค้ดให้คุณได้มากขึ้นเช่น foreach loops


1

ข้อดีอีกอย่างของการใช้ Generics (โดยเฉพาะกับ Collections / Lists) คือคุณจะได้รับ Compile Time Type Checking สิ่งนี้มีประโยชน์มากเมื่อใช้ Generic List แทน List of Objects


1

เหตุผลเดียวคือพวกเขาให้ความปลอดภัยประเภท

List<Customer> custCollection = new List<Customer>;

ตรงข้ามกับ,

object[] custCollection = new object[] { cust1, cust2 };

เป็นตัวอย่างง่ายๆ


พิมพ์ความปลอดภัยการอภิปรายในตัวเอง อาจมีคนถามคำถามเกี่ยวกับเรื่องนี้
MrBoJangles

ใช่ แต่คุณกำลังบอกใบ้อะไรอยู่? จุดรวมของ Type Safety?
Vin

1

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

สิ่งนี้มีประโยชน์หลายประการสำหรับคุณ:

  • เนื่องจากคอมไพเลอร์รู้มากขึ้นเกี่ยวกับสิ่งที่คุณต้องการทำจึงช่วยให้คุณไม่ต้องใส่ type-casting จำนวนมากเพราะรู้อยู่แล้วว่า type นั้นจะเข้ากันได้

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

  • คอมไพเลอร์สามารถทำการเพิ่มประสิทธิภาพได้มากขึ้นเช่นการหลีกเลี่ยงการชกมวยเป็นต้น


1

สิ่งที่ควรเพิ่ม / ขยาย (พูดจากมุมมอง. NET):

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

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


1

ฉันใช้ตัวอย่างเช่นใน GenericDao ที่ใช้กับ SpringORM และ Hibernate ซึ่งมีลักษณะเช่นนี้

public abstract class GenericDaoHibernateImpl<T> 
    extends HibernateDaoSupport {

    private Class<T> type;

    public GenericDaoHibernateImpl(Class<T> clazz) {
        type = clazz;
    }

    public void update(T object) {
        getHibernateTemplate().update(object);
    }

    @SuppressWarnings("unchecked")
    public Integer count() {
    return ((Integer) getHibernateTemplate().execute(
        new HibernateCallback() {
            public Object doInHibernate(Session session) {
                    // Code in Hibernate for getting the count
                }
        }));
    }
  .
  .
  .
}

ด้วยการใช้ generics การใช้งาน DAO นี้ของฉันบังคับให้นักพัฒนาส่งผ่านเฉพาะเอนทิตีที่พวกเขาออกแบบมาโดยเพียงแค่คลาสย่อย GenericDao

public class UserDaoHibernateImpl extends GenericDaoHibernateImpl<User> {
    public UserDaoHibernateImpl() {
        super(User.class);     // This is for giving Hibernate a .class
                               // work with, as generics disappear at runtime
    }

    // Entity specific methods here
}

กรอบงานเล็ก ๆ ของฉันมีประสิทธิภาพมากขึ้น (มีสิ่งต่างๆเช่นการกรองการโหลดแบบขี้เกียจการค้นหา) ฉันเพิ่งทำให้ง่ายขึ้นที่นี่เพื่อให้คุณเป็นตัวอย่าง

ฉันเหมือนสตีฟและคุณพูดในตอนแรกว่า "ยุ่งและซับซ้อนเกินไป"แต่ตอนนี้ฉันเห็นข้อดีของมันแล้ว


1

ประโยชน์ที่เห็นได้ชัดเช่น "ความปลอดภัย" และ "ไม่หล่อ" ได้กล่าวไว้แล้วดังนั้นฉันอาจพูดถึง "ประโยชน์" อื่น ๆ ซึ่งฉันหวังว่ามันจะช่วยได้

ประการแรก generics เป็นแนวคิดที่ไม่ขึ้นกับภาษาและ IMO อาจเหมาะสมกว่าถ้าคุณคิดเกี่ยวกับความแตกต่างแบบปกติ (รันไทม์) ในเวลาเดียวกัน

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

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

ชั้นเรียนทำงานในลักษณะเดียวกัน คุณเพิ่มชนิดและรหัสถูกสร้างโดยคอมไพเลอร์

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

public interface Foo<T extends MyObject> extends Hoo<T>{
    ...
}

ไม่มีใครสามารถตั้งค่า sth ได้นอกจาก MyObject ในขณะนี้

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

public <T extends MyObject> foo(T t1, T t2){
    ...
}   

หวังว่าทั้งหมดนี้จะสมเหตุสมผล


0

ฉันเคยพูดคุยในหัวข้อนี้ครั้งหนึ่ง คุณสามารถค้นหาภาพนิ่งรหัสและการบันทึกเสียงของฉันที่http://www.adventuresinsoftware.com/generics/


0

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

List<Stuff> stuffList = getStuff();
for(Stuff stuff : stuffList) {
    stuff.do();
}

เทียบกับ

List stuffList = getStuff();
Iterator i = stuffList.iterator();
while(i.hasNext()) {
    Stuff stuff = (Stuff)i.next();
    stuff.do();
}

หรือ

List stuffList = getStuff();
for(int i = 0; i < stuffList.size(); i++) {
    Stuff stuff = (Stuff)stuffList.get(i);
    stuff.do();
}

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


1
หากไม่มียาชื่อสามัญคุณสามารถทำได้: รายการ stuffList = getStuff (); สำหรับ (Object stuff: stuffList) {((Stuff) stuff) .doStuff (); } ซึ่งไม่แตกต่างกันมากนัก
Tom Hawtin - แทคไลน์

หากไม่มียาสามัญฉันทำ stuffList.doAll (); ฉันบอกว่าฉันมักจะมีคลาส wrapper ซึ่งเป็นที่สำหรับโค้ดแบบนั้น มิฉะนั้นคุณจะหลีกเลี่ยงการคัดลอกลูปนี้ไปที่อื่นได้อย่างไร คุณจะนำไปใช้ซ้ำได้ที่ไหน? คลาส Wrapper จะมีการวนซ้ำในบางประเภท แต่ในคลาสนั้นเนื่องจากคลาสนั้นเกี่ยวกับ "The stuff" การใช้ casted for loop เหมือนที่ Tom แนะนำไว้ข้างต้นนั้นค่อนข้างชัดเจน / การจัดทำเอกสารด้วยตนเอง
Bill K

@Jherico นั่นน่าสนใจจริงๆ ฉันสงสัยว่ามีเหตุผลที่คุณรู้สึกแบบนั้นหรือไม่และนั่นคือสิ่งที่ฉันไม่เข้าใจ - มีบางอย่างในธรรมชาติของผู้คนที่พบว่ามีการคัดเลือกนักแสดงไม่ว่าด้วยเหตุผลใดก็ตามที่ลามกอนาจารและเต็มใจที่จะใช้ความยาวที่ผิดธรรมชาติ (เช่น เพิ่มไวยากรณ์ที่ซับซ้อนมากขึ้น) เพื่อหลีกเลี่ยง ทำไม "ถ้าคุณต้องโยนบางสิ่งคุณสูญเสียไปแล้ว"
Bill K

@Jherico: ตามเอกสารของ Sun Generics ช่วยให้ผู้รวบรวมความรู้เกี่ยวกับสิ่งที่จะโยนวัตถุไป เนื่องจากคอมไพลเลอร์ทำการแคสต์สำหรับ generics แทนที่จะเป็นผู้ใช้สิ่งนี้จะเปลี่ยนคำสั่งของคุณหรือไม่?
Demi

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

0

Generics ยังช่วยให้คุณสามารถสร้างวัตถุ / วิธีการที่ใช้ซ้ำได้มากขึ้นในขณะที่ยังคงให้การสนับสนุนเฉพาะประเภท คุณยังได้รับประสิทธิภาพมากมายในบางกรณี ฉันไม่ทราบข้อมูลจำเพาะทั้งหมดของ Java Generics แต่ใน. NET ฉันสามารถระบุข้อ จำกัด เกี่ยวกับพารามิเตอร์ Type เช่น Implements a Interface, Constructor และ Derivation


0
  1. การเปิดใช้งานโปรแกรมเมอร์เพื่อใช้อัลกอริทึมทั่วไป - โดยการใช้ generics โปรแกรมเมอร์สามารถใช้อัลกอริทึมทั่วไปที่ทำงานกับคอลเลกชันประเภทต่างๆสามารถปรับแต่งได้และเป็นประเภทที่ปลอดภัยและอ่านง่าย

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

  3. การกำจัดการร่าย

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