ฉันคิดว่าจะเสนอซอฟต์บอลนี้ให้ใครก็ได้ที่อยากจะตีมันออกจากสวนสาธารณะ ยาชื่อสามัญคืออะไรข้อดีของยาชื่อสามัญคืออะไรทำไมฉันควรใช้มันที่ไหน? โปรดให้มันเป็นพื้นฐานพอสมควร ขอบคุณ.
ฉันคิดว่าจะเสนอซอฟต์บอลนี้ให้ใครก็ได้ที่อยากจะตีมันออกจากสวนสาธารณะ ยาชื่อสามัญคืออะไรข้อดีของยาชื่อสามัญคืออะไรทำไมฉันควรใช้มันที่ไหน? โปรดให้มันเป็นพื้นฐานพอสมควร ขอบคุณ.
คำตอบ:
เกลียดตัวเองมากจริงๆ ฉันเกลียดการพิมพ์สิ่งเดียวกันบ่อยกว่าที่ฉันต้องทำ ฉันไม่ชอบการพูดซ้ำหลาย ๆ ครั้งโดยมีความแตกต่างเล็กน้อย
แทนที่จะสร้าง:
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>
ทุกคลาสที่เกี่ยวข้องเพื่อสร้างเวอร์ชันที่ปลอดภัยหรือไม่?
ฉันไม่.
การไม่จำเป็นต้องพิมพ์แคสต์เป็นข้อได้เปรียบที่ใหญ่ที่สุดอย่างหนึ่งของ Java genericsเนื่องจากจะทำการตรวจสอบประเภทในเวลาคอมไพล์ สิ่งนี้จะช่วยลดความเป็นไปได้ของClassCastException
s ที่สามารถส่งออกไปที่รันไทม์และสามารถนำไปสู่โค้ดที่มีประสิทธิภาพมากขึ้น
แต่ฉันสงสัยว่าคุณรู้ดีอยู่แล้ว
ทุกครั้งที่ฉันดู 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
ที่เรียกว่าถือInteger
s การพยายามโยนเข้าไปString
อาจทำให้เกิดข้อผิดพลาดในการคอมไพล์
ถัดไปสำหรับแต่ละลูปจะใช้ทั้งสามอย่าง:
for (int i : set) {
total += i;
}
ครั้งแรกที่Set
มีInteger
s ที่ใช้ในสำหรับแต่ละวง แต่ละองค์ประกอบมีการประกาศให้เป็น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
อ็อบเจ็กต์ถูกจัดการในรูปแบบObject
s เนื่องจากไม่มีข้อมูลประเภทใดที่จะช่วยให้คอมไพเลอร์ทราบว่าSet
ควรคาดหวังประเภทใด
for (Object o : set) {
นี่คือส่วนที่มีความสำคัญ เหตุผลที่สำหรับแต่ละลูปทำงานได้เนื่องจากอินเทอร์เฟซSet
ใช้งานIterable
ซึ่งส่งคืนข้อมูลที่Iterator
มีชนิดหากมีอยู่ ( Iterator<T>
นั่นคือ.)
แต่เนื่องจากไม่มีข้อมูลประเภทที่Set
จะกลับIterator
ซึ่งจะกลับค่าในSet
ขณะที่Object
S, และนั่นคือเหตุผลองค์ประกอบถูกเรียกในสำหรับแต่ละวงจะต้องObject
เป็นชนิด
ตอนนี้เมื่อObject
ดึงข้อมูลมาSet
แล้วจำเป็นต้องส่งไปยังInteger
ด้วยตนเองเพื่อดำเนินการเพิ่มเติม:
total += (Integer)o;
นี่เป็น typecast จะดำเนินการจากไปยังObject
Integer
ในกรณีนี้เรารู้ว่าสิ่งนี้จะใช้งานได้เสมอ แต่การพิมพ์ด้วยตนเองมักทำให้ฉันรู้สึกว่าเป็นรหัสที่เปราะบางซึ่งอาจเสียหายได้หากมีการเปลี่ยนแปลงเล็กน้อยที่อื่น (ฉันรู้สึกว่าทุกคนClassCastException
รอคอยที่จะเกิดขึ้น แต่ฉันพูดนอกเรื่อง ... )
Integer
จะไม่มีกล่องตอนนี้เป็นint
และได้รับอนุญาตให้ดำเนินการนอกจากนี้ลงในตัวแปรint
total
ฉันหวังว่าฉันจะแสดงให้เห็นว่าคุณสมบัติใหม่ของ Java 5 สามารถใช้กับโค้ดที่ไม่ใช่ทั่วไปได้ แต่มันก็ไม่สะอาดและตรงไปตรงมาเท่ากับการเขียนโค้ดด้วย generics และในความคิดของฉันเพื่อใช้ประโยชน์อย่างเต็มที่จากคุณสมบัติใหม่ใน Java 5 เราควรพิจารณาถึง generics ถ้าอย่างน้อยที่สุดก็อนุญาตให้มีการตรวจสอบเวลาคอมไพล์เพื่อป้องกันไม่ให้ตัวพิมพ์ที่ไม่ถูกต้องโยนข้อยกเว้นในรันไทม์
หากคุณกำลังจะค้นหาฐานข้อมูลข้อผิดพลาด Java ก่อน 1.5 ได้รับการปล่อยตัวคุณจะพบข้อบกพร่องเจ็ดครั้งมากขึ้นด้วยกว่าNullPointerException
ClassCastException
ดังนั้นดูเหมือนว่าจะไม่เป็นคุณสมบัติที่ยอดเยี่ยมในการค้นหาจุดบกพร่องหรืออย่างน้อยข้อบกพร่องที่ยังคงมีอยู่หลังจากการทดสอบควันเล็กน้อย
สำหรับฉันข้อได้เปรียบอย่างมากของยาชื่อสามัญคือพวกเขาบันทึกข้อมูลประเภทสำคัญของรหัส หากฉันไม่ต้องการให้ข้อมูลประเภทนั้นถูกบันทึกไว้ในโค้ดฉันจะใช้ภาษาที่พิมพ์แบบไดนามิกหรืออย่างน้อยก็เป็นภาษาที่มีการอนุมานประเภทโดยนัยมากกว่า
การเก็บคอลเลกชันของวัตถุไว้กับตัวเองไม่ใช่ลักษณะที่ไม่ดี (แต่สไตล์ทั่วไปคือการละเว้นการห่อหุ้มอย่างมีประสิทธิภาพ) ค่อนข้างขึ้นอยู่กับสิ่งที่คุณกำลังทำ การส่งคอลเลกชันไปยัง "อัลกอริทึม" นั้นง่ายกว่าเล็กน้อยในการตรวจสอบ (ในเวลาหรือก่อนเวลาคอมไพล์) ด้วยข้อมูลทั่วไป
ยาชื่อสามัญในชวาอำนวยความสะดวกในหลายรูปแบบตัวแปร โดยใช้พารามิเตอร์ประเภทคุณสามารถส่งผ่านอาร์กิวเมนต์ไปยังประเภทได้ เช่นเดียวกับวิธีการเช่นเดียวกับ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>
Generics หลีกเลี่ยงการตีชกมวยและการแกะกล่อง โดยทั่วไปให้ดูที่ ArrayList vs List <T> ทั้งสองทำสิ่งสำคัญเหมือนกัน แต่ List <T> จะเร็วกว่ามากเพราะคุณไม่ต้องใส่กล่องไปที่ / จากวัตถุ
ประโยชน์ที่ดีที่สุดสำหรับ 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 ด้านบน สง่างามอย่างแท้จริง!
มีเหตุผลที่ดีอื่น ๆ อีกมากมายในการใช้ยาชื่อสามัญสำหรับงานของคุณนี่คือสิ่งที่ฉันชอบ
ฉันชอบพวกเขาเพราะพวกเขาให้วิธีที่รวดเร็วในการกำหนดประเภทที่กำหนดเอง (ตามที่ฉันใช้อยู่)
ตัวอย่างเช่นแทนที่จะกำหนดโครงสร้างที่ประกอบด้วยสตริงและจำนวนเต็มจากนั้นต้องใช้ชุดของวัตถุและวิธีการทั้งหมดในการเข้าถึงอาร์เรย์ของโครงสร้างเหล่านั้นเป็นต้นคุณสามารถสร้างพจนานุกรมได้
Dictionary<int, string> dictionary = new Dictionary<int, string>();
และคอมไพเลอร์ / IDE จะทำการยกของหนักที่เหลือ พจนานุกรมโดยเฉพาะให้คุณใช้ประเภทแรกเป็นคีย์ (ไม่มีค่าซ้ำ)
คอลเลกชันที่พิมพ์ - แม้ว่าคุณจะไม่ต้องการใช้คุณก็มีแนวโน้มที่จะต้องจัดการกับพวกเขาจากห้องสมุดอื่น ๆ แหล่งอื่น
การพิมพ์ทั่วไปในการสร้างคลาส:
ชั้นสาธารณะ Foo <T> {public T get () ...
หลีกเลี่ยงการแคสต์ - ฉันไม่ชอบสิ่งที่ชอบมาโดยตลอด
ตัวเปรียบเทียบใหม่ {public int CompareTo (Object o) {if (o instanceof classIcareAbout) ...
โดยพื้นฐานแล้วคุณกำลังตรวจสอบเงื่อนไขที่ควรมีอยู่เนื่องจากอินเทอร์เฟซแสดงในรูปของวัตถุ
ปฏิกิริยาแรกเริ่มของฉันต่อยาชื่อสามัญคล้ายกับของคุณ - "ยุ่งเกินไปซับซ้อนเกินไป" ประสบการณ์ของฉันคือหลังจากใช้มันสักหน่อยคุณจะคุ้นเคยกับพวกเขาและรหัสโดยที่พวกเขาไม่ได้ระบุไว้ชัดเจนและไม่ค่อยสะดวก นอกเหนือจากนั้นส่วนที่เหลือของโลก java ก็ใช้พวกมันดังนั้นคุณจะต้องได้รับโปรแกรมในที่สุดใช่ไหม?
เพื่อเป็นตัวอย่างที่ดี ลองนึกภาพคุณมีคลาสชื่อ 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();
}
คุณไม่เคยเขียน method (หรือคลาส) โดยที่แนวคิดหลักของ method / class นั้นไม่ได้ผูกมัดกับชนิดข้อมูลเฉพาะของพารามิเตอร์ / ตัวแปรอินสแตนซ์ (คิดว่ารายการที่เชื่อมโยง, ฟังก์ชันสูงสุด / นาที, การค้นหาแบบไบนารี ฯลฯ ).
คุณไม่เคยหวังว่าคุณจะสามารถนำ algorthm / code มาใช้ซ้ำได้โดยไม่ต้องใช้การตัด n-paste ซ้ำหรือลดทอนการพิมพ์ที่หนักแน่น (เช่นฉันต้องการList
สตริงไม่ใช่List
สิ่งที่ฉันหวังว่าจะเป็นสตริง!)
ว่าทำไมคุณควรต้องการที่จะใช้ยาชื่อสามัญ (หรือสิ่งที่ดีกว่า)
อย่าลืมว่ายาชื่อสามัญไม่ได้ใช้เฉพาะในชั้นเรียนเท่านั้น แต่ยังสามารถใช้โดยวิธีการได้อีกด้วย ตัวอย่างเช่นใช้ข้อมูลโค้ดต่อไปนี้:
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>>
)
Throwable
จากภายในเนื้อความวิธีการที่มีการประกาศข้อยกเว้นเฉพาะ Throwable
ทางเลือกที่เป็นทั้งวิธีการเขียนที่แยกต่างหากเพื่อกลับแต่ละประเภทยกเว้นหรือทำโยนตัวเองด้วยไม่ใช่วิธีทั่วไปที่ผลตอบแทน อดีตนั้นละเอียดเกินไปและไร้ประโยชน์และส่วนหลังจะไม่ได้รับความช่วยเหลือจากคอมไพเลอร์ ด้วยการใช้ generics คอมไพเลอร์จะแทรกการแคสต์ที่ถูกต้องให้คุณโดยอัตโนมัติ คำถามคือมันคุ้มค่ากับความซับซ้อนหรือไม่?
ข้อได้เปรียบหลักตามที่ Mitchel ชี้ให้เห็นคือการพิมพ์ที่ชัดเจนโดยไม่จำเป็นต้องกำหนดหลายชั้นเรียน
ด้วยวิธีนี้คุณสามารถทำสิ่งต่างๆเช่น:
List<SomeCustomClass> blah = new List<SomeCustomClass>();
blah[0].SomeCustomFunction();
หากไม่มียาชื่อสามัญคุณจะต้องโยน blah [0] ไปยังประเภทที่ถูกต้องเพื่อเข้าถึงฟังก์ชันของมัน
jvm จะร่ายต่อไป ... มันสร้างโค้ดโดยปริยายซึ่งถือว่าประเภททั่วไปเป็น "Object" และสร้าง casts ไปยังอินสแตนซ์ที่ต้องการ ชื่อสามัญของ Java เป็นเพียงน้ำตาลที่เป็นประโยค
ฉันรู้ว่านี่เป็นคำถาม 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
จากเอกสาร Sun Java เพื่อตอบสนองต่อ "เหตุใดฉันจึงควรใช้ generics":
"Generics เป็นวิธีที่คุณสามารถสื่อสารประเภทของคอลเล็กชันไปยังคอมไพเลอร์เพื่อให้สามารถตรวจสอบได้เมื่อคอมไพเลอร์ทราบประเภทองค์ประกอบของคอลเล็กชันแล้วคอมไพเลอร์สามารถตรวจสอบได้ว่าคุณใช้คอลเล็กชันอย่างสม่ำเสมอและสามารถแทรก การร่ายที่ถูกต้องสำหรับค่าที่ถูกนำออกจากคอลเลกชัน ... โค้ดที่ใช้ generics นั้นชัดเจนและปลอดภัยกว่า .... คอมไพเลอร์สามารถตรวจสอบได้ในเวลาคอมไพล์ว่าข้อ จำกัด ประเภทไม่ได้ถูกละเมิดในขณะรัน [เน้นของฉัน] เนื่องจาก โปรแกรมคอมไพล์โดยไม่มีคำเตือนเราสามารถระบุด้วยความมั่นใจว่าจะไม่โยน ClassCastException ในขณะทำงานผลสุทธิของการใช้ generics โดยเฉพาะในโปรแกรมขนาดใหญ่คือการปรับปรุงความสามารถในการอ่านและความทนทาน [เน้นของฉัน] "
Generics ช่วยให้คุณสร้างออบเจ็กต์ที่มีการพิมพ์อย่างรุนแรง แต่คุณไม่จำเป็นต้องกำหนดประเภทเฉพาะ ฉันคิดว่าตัวอย่างที่มีประโยชน์ที่สุดคือ List และคลาสที่คล้ายกัน
การใช้รายการทั่วไปคุณสามารถมี List List สิ่งที่คุณต้องการและคุณสามารถอ้างอิงการพิมพ์ที่ชัดเจนได้ตลอดเวลาคุณไม่จำเป็นต้องแปลงหรืออะไรเหมือนกับที่คุณทำกับ Array หรือ Standard List
Generics ช่วยให้คุณใช้การพิมพ์ที่ชัดเจนสำหรับวัตถุและโครงสร้างข้อมูลที่ควรจะถือวัตถุใด ๆ นอกจากนี้ยังช่วยกำจัดการพิมพ์ที่น่าเบื่อและมีราคาแพงเมื่อดึงวัตถุจากโครงสร้างทั่วไป (การชกมวย / แกะกล่อง)
ตัวอย่างหนึ่งที่ใช้ทั้งสองรายการคือรายการที่เชื่อมโยงกัน คลาสลิสต์ที่เชื่อมโยงจะเป็นอย่างไรหากสามารถใช้อ็อบเจกต์ Foo ในการใช้รายการที่เชื่อมโยงที่สามารถจัดการกับวัตถุชนิดใดก็ได้รายการที่เชื่อมโยงและโหนดในคลาสภายในของโหนดสมมุติฐานต้องเป็นแบบทั่วไปหากคุณต้องการให้รายการมีวัตถุเพียงประเภทเดียว
หากคอลเล็กชันของคุณมีประเภทค่าพวกเขาไม่จำเป็นต้องใส่กล่อง / unbox ให้กับวัตถุเมื่อใส่เข้าไปในคอลเลกชันดังนั้นประสิทธิภาพของคุณจึงเพิ่มขึ้นอย่างมาก ส่วนเสริมเจ๋ง ๆ เช่น resharper สามารถสร้างโค้ดให้คุณได้มากขึ้นเช่น foreach loops
ข้อดีอีกอย่างของการใช้ Generics (โดยเฉพาะกับ Collections / Lists) คือคุณจะได้รับ Compile Time Type Checking สิ่งนี้มีประโยชน์มากเมื่อใช้ Generic List แทน List of Objects
เหตุผลเดียวคือพวกเขาให้ความปลอดภัยประเภท
List<Customer> custCollection = new List<Customer>;
ตรงข้ามกับ,
object[] custCollection = new object[] { cust1, cust2 };
เป็นตัวอย่างง่ายๆ
โดยสรุปยาชื่อสามัญช่วยให้คุณระบุสิ่งที่คุณตั้งใจจะทำได้อย่างแม่นยำมากขึ้น
สิ่งนี้มีประโยชน์หลายประการสำหรับคุณ:
เนื่องจากคอมไพเลอร์รู้มากขึ้นเกี่ยวกับสิ่งที่คุณต้องการทำจึงช่วยให้คุณไม่ต้องใส่ type-casting จำนวนมากเพราะรู้อยู่แล้วว่า type นั้นจะเข้ากันได้
นอกจากนี้ยังช่วยให้คุณได้รับคำติชมก่อนหน้านี้เกี่ยวกับความถูกต้องของโปรแกรมของคุณ สิ่งที่ก่อนหน้านี้จะล้มเหลวในรันไทม์ (เช่นเนื่องจากไม่สามารถแคสต์อ็อบเจ็กต์ในประเภทที่ต้องการได้) ตอนนี้ล้มเหลวในเวลาคอมไพล์และคุณสามารถแก้ไขข้อผิดพลาดก่อนที่แผนกทดสอบของคุณจะส่งรายงานข้อบกพร่องที่เป็นความลับ
คอมไพเลอร์สามารถทำการเพิ่มประสิทธิภาพได้มากขึ้นเช่นการหลีกเลี่ยงการชกมวยเป็นต้น
สิ่งที่ควรเพิ่ม / ขยาย (พูดจากมุมมอง. NET):
ประเภททั่วไปช่วยให้คุณสร้างคลาสและอินเทอร์เฟซตามบทบาทได้ สิ่งนี้ได้กล่าวไปแล้วในแง่พื้นฐาน แต่ฉันพบว่าคุณเริ่มออกแบบโค้ดของคุณด้วยคลาสที่ใช้งานในรูปแบบที่ไม่เชื่อเรื่องพระเจ้า - ซึ่งส่งผลให้โค้ดใช้ซ้ำได้สูง
ข้อโต้แย้งทั่วไปเกี่ยวกับวิธีการสามารถทำสิ่งเดียวกันได้ แต่ยังช่วยนำหลักการ "บอกอย่าถาม" ไปใช้ในการแคสต์เช่น "ให้สิ่งที่ฉันต้องการและถ้าคุณทำไม่ได้คุณก็บอกฉันว่าทำไม"
ฉันใช้ตัวอย่างเช่นใน 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
}
กรอบงานเล็ก ๆ ของฉันมีประสิทธิภาพมากขึ้น (มีสิ่งต่างๆเช่นการกรองการโหลดแบบขี้เกียจการค้นหา) ฉันเพิ่งทำให้ง่ายขึ้นที่นี่เพื่อให้คุณเป็นตัวอย่าง
ฉันเหมือนสตีฟและคุณพูดในตอนแรกว่า "ยุ่งและซับซ้อนเกินไป"แต่ตอนนี้ฉันเห็นข้อดีของมันแล้ว
ประโยชน์ที่เห็นได้ชัดเช่น "ความปลอดภัย" และ "ไม่หล่อ" ได้กล่าวไว้แล้วดังนั้นฉันอาจพูดถึง "ประโยชน์" อื่น ๆ ซึ่งฉันหวังว่ามันจะช่วยได้
ประการแรก generics เป็นแนวคิดที่ไม่ขึ้นกับภาษาและ IMO อาจเหมาะสมกว่าถ้าคุณคิดเกี่ยวกับความแตกต่างแบบปกติ (รันไทม์) ในเวลาเดียวกัน
ตัวอย่างเช่นความหลากหลายตามที่เราทราบจากการออกแบบเชิงวัตถุมีแนวคิดเกี่ยวกับรันไทม์โดยที่วัตถุผู้เรียกถูกคิดออกที่รันไทม์เมื่อการทำงานของโปรแกรมดำเนินไปและวิธีการที่เกี่ยวข้องจะถูกเรียกตามประเภทรันไทม์ ในยาชื่อสามัญความคิดค่อนข้างคล้ายกัน แต่ทุกอย่างเกิดขึ้นในเวลารวบรวม นั่นหมายความว่าอย่างไรและคุณใช้ประโยชน์จากมันอย่างไร?
(ลองใช้วิธีการทั่วไปเพื่อให้กะทัดรัด) หมายความว่าคุณยังสามารถมีเมธอดเดิมในคลาสที่แยกจากกันได้ (เช่นเดียวกับที่คุณทำก่อนหน้านี้ในคลาสโพลีมอร์ฟิก) แต่คราวนี้คอมไพเลอร์สร้างขึ้นโดยอัตโนมัติขึ้นอยู่กับประเภทที่ตั้งไว้ ในเวลารวบรวม คุณกำหนดวิธีการของคุณในประเภทที่คุณให้ในเวลาคอมไพล์ ดังนั้นแทนที่จะเขียนเมธอดตั้งแต่เริ่มต้นสำหรับทุกประเภทที่คุณมีเหมือนที่คุณทำในความหลากหลายของรันไทม์ (การแทนที่วิธีการ) คุณปล่อยให้คอมไพเลอร์ทำงานระหว่างการคอมไพล์ สิ่งนี้มีข้อได้เปรียบที่ชัดเจนเนื่องจากคุณไม่จำเป็นต้องอนุมานประเภทที่เป็นไปได้ทั้งหมดที่อาจใช้ในระบบของคุณซึ่งทำให้สามารถปรับขนาดได้มากขึ้นโดยไม่ต้องเปลี่ยนรหัส
ชั้นเรียนทำงานในลักษณะเดียวกัน คุณเพิ่มชนิดและรหัสถูกสร้างโดยคอมไพเลอร์
เมื่อคุณได้แนวคิดเกี่ยวกับ "เวลารวบรวม" คุณสามารถใช้ประเภท "ขอบเขต" และ จำกัด สิ่งที่สามารถส่งผ่านเป็นประเภทพารามิเตอร์ผ่านคลาส / วิธีการ ดังนั้นคุณสามารถควบคุมสิ่งที่จะส่งผ่านซึ่งเป็นสิ่งที่ทรงพลังโดยเฉพาะอย่างยิ่งคุณเป็นกรอบที่คนอื่นใช้
public interface Foo<T extends MyObject> extends Hoo<T>{
...
}
ไม่มีใครสามารถตั้งค่า sth ได้นอกจาก MyObject ในขณะนี้
นอกจากนี้คุณสามารถ "บังคับใช้" ข้อ จำกัด ประเภทกับอาร์กิวเมนต์วิธีการของคุณซึ่งหมายความว่าคุณสามารถตรวจสอบให้แน่ใจว่าอาร์กิวเมนต์วิธีการของคุณทั้งสองจะขึ้นอยู่กับประเภทเดียวกัน
public <T extends MyObject> foo(T t1, T t2){
...
}
หวังว่าทั้งหมดนี้จะสมเหตุสมผล
การใช้ยาสามัญสำหรับคอลเลกชันนั้นง่ายและสะอาด แม้ว่าคุณจะถ่อไปที่อื่น แต่การได้รับจากคอลเลกชันก็ชนะฉัน
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();
}
เพียงอย่างเดียวก็คุ้มค่ากับ "ต้นทุน" เล็กน้อยของยาชื่อสามัญและคุณไม่จำเป็นต้องเป็นปราชญ์ทั่วไปเพื่อใช้สิ่งนี้และได้รับคุณค่า
Generics ยังช่วยให้คุณสามารถสร้างวัตถุ / วิธีการที่ใช้ซ้ำได้มากขึ้นในขณะที่ยังคงให้การสนับสนุนเฉพาะประเภท คุณยังได้รับประสิทธิภาพมากมายในบางกรณี ฉันไม่ทราบข้อมูลจำเพาะทั้งหมดของ Java Generics แต่ใน. NET ฉันสามารถระบุข้อ จำกัด เกี่ยวกับพารามิเตอร์ Type เช่น Implements a Interface, Constructor และ Derivation
การเปิดใช้งานโปรแกรมเมอร์เพื่อใช้อัลกอริทึมทั่วไป - โดยการใช้ generics โปรแกรมเมอร์สามารถใช้อัลกอริทึมทั่วไปที่ทำงานกับคอลเลกชันประเภทต่างๆสามารถปรับแต่งได้และเป็นประเภทที่ปลอดภัยและอ่านง่าย
การตรวจสอบประเภทที่แข็งแกร่งขึ้นในเวลาคอมไพล์ - คอมไพเลอร์ Java ใช้การตรวจสอบประเภทที่แข็งแกร่งกับโค้ดทั่วไปและออกข้อผิดพลาดหากรหัสละเมิดความปลอดภัย การแก้ไขข้อผิดพลาดเวลาคอมไพล์ทำได้ง่ายกว่าการแก้ไขข้อผิดพลาดรันไทม์ซึ่งอาจหาได้ยาก
การกำจัดการร่าย