Java Generics เป็นหนึ่งในคุณสมบัติที่สำคัญที่สุดของภาษา Java แนวคิดเบื้องหลังยาชื่อสามัญนั้นค่อนข้างง่าย อย่างไรก็ตาม บางครั้งก็ดูซับซ้อนเนื่องจากการเปลี่ยนจากไวยากรณ์ปกติที่เกี่ยวข้อง
จุดประสงค์ของบทช่วยสอนนี้คือเพื่อแนะนำให้คุณรู้จักกับแนวคิดที่เป็นประโยชน์เกี่ยวกับยาชื่อสามัญในลักษณะที่เข้าใจง่าย
แต่ก่อนที่จะเจาะลึกถึงยาสามัญ เรามาทำความเข้าใจกันว่าทำไมถึงต้องการยาชื่อสามัญของ Java ก่อน
วัตถุประสงค์ของ Java Generics
ก่อนเริ่มใช้ generics ใน Java 5 คุณสามารถเขียนและคอมไพล์ข้อมูลโค้ดแบบนี้ได้โดยไม่ต้องแสดงข้อผิดพลาดหรือคำเตือน:
List list = new ArrayList();
list.add("hey");
list.add(new Object());
คุณสามารถเพิ่มค่าประเภทใดก็ได้ในรายการหรือ Java Collection อื่นโดยไม่ต้องประกาศประเภทของข้อมูลที่จัดเก็บ แต่เมื่อคุณดึงค่าจากรายการ คุณจะต้องแปลงเป็นค่าบางประเภทอย่างชัดเจน
พิจารณาซ้ำจากรายการด้านบน
for (int i=0; i< list.size(); i++) {
String value = (String) list.get(i); //CastClassException when i=1
}
การอนุญาตให้สร้างรายการโดยไม่ประกาศประเภทข้อมูลที่เก็บไว้ก่อน เช่นที่เราทำ อาจส่งผลให้โปรแกรมเมอร์ทำผิดพลาดเหมือนข้างบนซึ่งส่ง ClassCastExceptions
ระหว่างรันไทม์
มีการแนะนำ Generics เพื่อป้องกันไม่ให้โปรแกรมเมอร์ทำผิดพลาดดังกล่าว
เมื่อใช้ generics คุณสามารถประกาศประเภทข้อมูลที่จะจัดเก็บได้อย่างชัดเจนเมื่อสร้าง Java Collection ตามตัวอย่างต่อไปนี้
หมายเหตุ:คุณยังสามารถสร้างวัตถุคอลเลกชัน Java ได้โดยไม่ต้องระบุประเภทข้อมูลที่เก็บไว้ แต่ไม่แนะนำList<String> stringList = new ArrayList<>();
ตอนนี้ คุณไม่สามารถจัดเก็บจำนวนเต็มในรายการประเภทสตริงโดยไม่ได้ตั้งใจโดยไม่ทำให้เกิดข้อผิดพลาดในการคอมไพล์ เพื่อให้แน่ใจว่าโปรแกรมของคุณไม่มีข้อผิดพลาดรันไทม์
stringList.add(new Integer(4)); //Compile time Error
จุดประสงค์หลักของการแนะนำชื่อสามัญใน Java คือการหลีกเลี่ยงการเรียกใช้ ClassCastExceptions
ระหว่างรันไทม์
การสร้าง Java Generics
คุณสามารถใช้ generics เพื่อสร้างคลาส Java และเมธอด มาดูตัวอย่างการสร้างชื่อสามัญแต่ละประเภทกัน
คลาสทั่วไป
เมื่อสร้างคลาสทั่วไป พารามิเตอร์ type สำหรับคลาสจะถูกเพิ่มที่ส่วนท้ายของชื่อคลาสภายในมุม <>
วงเล็บ
public class GenericClass<T> {
private T item;
public void setItem(T item) {
this.item = item;
}
public T getItem() {
return this.item;
}
}
ที่นี่ T
เป็นพารามิเตอร์ชนิดข้อมูล T
, N
และ E
คือตัวอักษรบางตัวที่ใช้สำหรับพารามิเตอร์ชนิดข้อมูลตามข้อตกลงของ Java
ในตัวอย่างข้างต้น คุณสามารถส่งผ่านประเภทข้อมูลเฉพาะเมื่อสร้างวัตถุ GenericClass
public static void main(String[] args) {
GenericClass<String> gc1 = new GenericClass<>();
gc1.setItem("hello");
String item1 = gc1.getItem(); // "hello"
gc1.setItem(new Object()); //Error
GenericClass<Integer> gc2 = new GenericClass<>();
gc2.setItem(new Integer(1));
Integer item2 = gc2.getItem(); // 1
gc2.setItem("hello"); //Error
}
คุณไม่สามารถส่งผ่านชนิดข้อมูลดั้งเดิมไปยังพารามิเตอร์ชนิดข้อมูลเมื่อสร้างอ็อบเจ็กต์คลาสทั่วไป เฉพาะประเภทข้อมูลที่ขยายประเภทอ็อบเจ็กต์เท่านั้นที่สามารถส่งผ่านเป็นพารามิเตอร์ประเภทได้
ตัวอย่างเช่น:
GenericClass<float> gc3 = new GenericClass<>(); //Error
วิธีการทั่วไป
การสร้างเมธอดทั่วไปมีรูปแบบที่คล้ายคลึงกันในการสร้างคลาสทั่วไป คุณสามารถใช้เมธอดทั่วไปในคลาสทั่วไปและคลาสที่ไม่ใช่แบบทั่วไปได้
public class GenericMethodClass {
public static <T> void printItems(T[] arr){
for (int i=0; i< arr.length; i++) {
System.out.println(arr[i]);
}
}
public static void main(String[] args) {
String[] arr1 = {"Cat", "Dog", "Mouse"};
Integer[] arr2 = {1, 2, 3};
GenericMethodClass.printItems(arr1); // "Cat", "Dog", "Mouse"
GenericMethodClass.printItems(arr2); // 1, 2, 3
}
}
ที่นี่ คุณสามารถส่งอาร์เรย์ของประเภทเฉพาะเพื่อกำหนดพารามิเตอร์ของเมธอดได้ วิธีการทั่วไป PrintItems()
วนซ้ำผ่านอาร์เรย์ที่ส่งผ่านและพิมพ์รายการที่เก็บไว้เหมือนกับวิธี Java ปกติ
พารามิเตอร์ประเภทที่ถูกผูกไว้
จนถึงตอนนี้ คลาสทั่วไปและเมธอดที่เราสร้างขึ้นข้างต้นสามารถกำหนดพารามิเตอร์เป็นชนิดข้อมูลใดๆ ก็ได้ที่ไม่ใช่ประเภทดั้งเดิม แต่ถ้าเราต้องการจำกัดประเภทข้อมูลที่สามารถส่งต่อไปยังยาสามัญได้ล่ะ นี่คือที่มาของพารามิเตอร์ประเภทที่มีขอบเขต
คุณสามารถผูกประเภทข้อมูลที่ยอมรับโดยคลาสหรือเมธอดทั่วไปโดยระบุว่าควรเป็นคลาสย่อยของประเภทข้อมูลอื่น
ตัวอย่างเช่น:
//accepts only subclasses of List
public class UpperBoundedClass<T extends List>{
//accepts only subclasses of List
public <T extends List> void UpperBoundedMethod(T[] arr) {
}
}
ที่นี่ UpperBoundedClass
และ UpperBoundedMethod
สามารถกำหนดพารามิเตอร์ได้โดยใช้ประเภทย่อยของ List
ชนิดข้อมูล
List
ชนิดข้อมูลทำหน้าที่เป็นขอบเขตบนของพารามิเตอร์ประเภท หากคุณพยายามใช้ประเภทข้อมูลที่ไม่ใช่ประเภทย่อยของ List
มันจะส่งข้อผิดพลาดเวลาคอมไพล์
ขอบเขตไม่ได้จำกัดเฉพาะคลาสเท่านั้น คุณสามารถส่งผ่านอินเทอร์เฟซได้เช่นกัน ในกรณีนี้ การขยายอินเทอร์เฟซหมายถึงการนำอินเทอร์เฟซไปใช้
พารามิเตอร์ยังสามารถมีหลายขอบเขตได้ดังตัวอย่างนี้
//accepts only subclasses of both Mammal and Animal
public class MultipleBoundedClass<T extends Mammal & Animal>{
//accepts only subclasses of both Mammal and Animal
public <T extends Mammal & Animal> void MultipleBoundedMethod(T[] arr){
}
}
ชนิดข้อมูลที่ยอมรับต้องเป็นคลาสย่อยของทั้งสัตว์และสัตว์เลี้ยงลูกด้วยนม หากหนึ่งในขอบเขตเหล่านี้เป็นคลาส ขอบเขตนั้นต้องมาก่อนในการประกาศขอบเขต
ในตัวอย่างข้างต้น หาก Mammal เป็นคลาสและ Animal เป็นส่วนต่อประสาน สัตว์เลี้ยงลูกด้วยนมจะต้องมาก่อนดังที่แสดงด้านบน มิฉะนั้น รหัสจะแสดงข้อผิดพลาดในการคอมไพล์
สัญลักษณ์แทน Java Generics
ไวด์การ์ดใช้เพื่อส่งต่อพารามิเตอร์ของประเภททั่วไปไปยังเมธอด ซึ่งแตกต่างจากวิธีการทั่วไป ในที่นี้ พารามิเตอร์ทั่วไปจะถูกส่งไปยังพารามิเตอร์ที่ยอมรับโดยวิธีการ ซึ่งแตกต่างจากพารามิเตอร์ประเภทข้อมูลที่เรากล่าวถึงข้างต้น สัญลักษณ์แทนถูกแสดงโดย ? สัญลักษณ์
public void printItems(List<?> list) {
for (int i=0; i< list.size(); i++) {
System.out.println(list.get(i));
}
}
printItems()
above ด้านบน method ยอมรับรายการประเภทข้อมูลใด ๆ เป็นพารามิเตอร์ ซึ่งจะป้องกันไม่ให้โปรแกรมเมอร์ต้องทำซ้ำรหัสสำหรับรายการประเภทข้อมูลต่างๆ ซึ่งจะเป็นกรณีนี้หากไม่มีข้อมูลทั่วไป
สัญลักษณ์แทนด้านบน
หากเราต้องการจำกัดประเภทข้อมูลที่จัดเก็บในรายการที่เมธอดยอมรับ เราก็สามารถใช้สัญลักษณ์แทนที่มีขอบเขตได้
ตัวอย่าง:
public void printSubTypes(List<? extends Color> list) {
for (int i=0; i< list.size(); i++) {
System.out.println(list.get(i));
}
}
printSubTypes()
method ยอมรับเฉพาะรายการที่จัดเก็บประเภทย่อยของ Color ยอมรับรายการวัตถุ RedColor หรือ BlueColor แต่ไม่ยอมรับรายการวัตถุ Animal เนื่องจากสัตว์ไม่ใช่ประเภทย่อยของสี นี่คือตัวอย่างของสัญลักษณ์แทนขอบเขตบน
ไวด์การ์ดที่มีขอบเขตล่าง
ในทำนองเดียวกัน ถ้าเรามี:
public void printSuperTypes(List<? super Dog> list) {
for (int i=0; i< list.size(); i++) {
System.out.println(list.get(i));
}
}
จากนั้น printSuperTypes()
method ยอมรับเฉพาะรายการที่จัดเก็บ super types ของ Dog class มันจะยอมรับรายการของ Mammal หรือ Animal object แต่ไม่ใช่รายการของ LabDog object เนื่องจาก LabDog ไม่ใช่ superclass ของ Dog แต่เป็น subclass นี่คือตัวอย่างของไวด์การ์ดที่มีขอบเขตล่าง
บทสรุป
Java Generics ได้กลายเป็นคุณลักษณะที่โปรแกรมเมอร์ขาดไม่ได้ตั้งแต่เปิดตัว
ความนิยมนี้เกิดจากการทำให้ชีวิตของโปรแกรมเมอร์ง่ายขึ้น นอกเหนือจากการป้องกันไม่ให้เกิดข้อผิดพลาดในการเขียนโค้ดแล้ว การใช้ generics ทำให้โค้ดซ้ำกันน้อยลง คุณสังเกตไหมว่ามันทำให้คลาสและวิธีการทั่วไปเพื่อหลีกเลี่ยงไม่ให้ต้องโค้ดซ้ำสำหรับข้อมูลประเภทต่างๆ
การมีความเข้าใจที่ดีเกี่ยวกับยาสามัญเป็นสิ่งสำคัญในการเป็นผู้เชี่ยวชาญในภาษา ดังนั้นการนำสิ่งที่คุณเรียนรู้ในบทช่วยสอนนี้ไปใช้ในโค้ดที่ใช้งานได้จริงจึงเป็นหนทางสู่อนาคต