Java Generics เป็นหนึ่งในฟีเจอร์สำคัญที่สุดของภาษา Java แนวคิดเบื้องหลัง generics ค่อนข้างเรียบง่าย แต่บางครั้งดูซับซ้อนเพราะการเปลี่ยนแปลงจาก syntax ปกติที่คุ้นเคย
จุดประสงค์ของบทช่วยสอนนี้คือแนะนำแนวคิด generals ที่มีประโยชน์นี้อย่างเข้าใจง่าย
แต่ก่อนจะลงลึกใน generics เอง มาหาว่าทำไม Java generics ถึงจำเป็น
จุดประสงค์ของ Java Generics
ก่อนที่ generics จะถูกนำเข้ามาใน Java 5 คุณเขียนและ compile code snippet แบบนี้ได้โดยไม่มี error หรือ warning:
List list = new ArrayList(); list.add("hey"); list.add(new Object());
คุณเพิ่มค่าประเภทไหนก็ได้เข้า list หรือ Java Collection อื่นๆ โดยไม่ต้องประกาศว่าจะเก็บข้อมูลประเภทไหน แต่ตอน retrieve ค่าจาก list คุณต้อง cast ให้เป็นประเภทเฉพาะอย่างชัดเจน
พิจารณา iteration ผ่าน list ข้างต้น
for (int i=0; i< list.size(); i++) { String value = (String) list.get(i); // ClassCastException when i=1 }
การอนุญาตให้สร้าง list โดยไม่ประกาศชนิดข้อมูลที่เก็บก่อน เหมือนเราทำ อาจทำให้โปรแกรมเมอร์ทำผิดพลาดอย่างข้างต้น ซึ่งโยน ClassCastExceptions ระหว่าง runtime
Generics ถูกนำเข้ามาเพื่อป้องกันโปรแกรมเมอร์จากการทำผิดพลาดแบบนี้
ด้วย generics คุณสามารถประกาศชนิดข้อมูลที่จะเก็บอย่างชัดเจนตอนสร้าง Java Collection ตามตัวอย่าง:
หมายเหตุ: คุณยังสร้าง Java collection object ได้โดยไม่ระบุชนิดข้อมูลที่เก็บ แต่ไม่แนะนำ
List stringList = new ArrayList<>();
ตอนนี้ คุณไม่สามารถเก็บ Integer ใน String type list ได้โดยไม่มี compile-time error นี่确保ว่าโปรแกรมของคุณไม่เจอ runtime errors
stringList.add(new Integer(4)); // Compile time Error
จุดประสงค์หลักของการนำ generics เข้า Java คือหลีกเลี่ยง ClassCastExceptions ระหว่าง runtime
การสร้าง Java Generics
คุณสามารถใช้ generics สร้าง Java classes และ methods มาดูตัวอย่างการสร้าง generics แต่ละประเภท
Generic Class
ตอนสร้าง generic class type parameter สำหรับ class จะเพิ่มท้ายชื่อ class ภายใน angle brackets <>
public class GenericClass { private T item; public void setItem(T item) { this.item = item; } public T getItem() { return this.item; } }
ที่นี่ T คือ type parameter T, N, และ E เป็นตัวอักษรบางตัวที่ใช้สำหรับ type parameters ตาม Java conventions
ในตัวอย่างข้างต้น คุณส่งชนิดข้อมูลเฉพาะตอนสร้าง GenericClass object
public static void main(String[] args) { GenericClass gc1 = new GenericClass<>(); gc1.setItem("hello"); String item1 = gc1.getItem(); // "hello" gc1.setItem(new Object()); // Error GenericClass gc2 = new GenericClass<>(); gc2.setItem(new Integer(1)); Integer item2 = gc2.getItem(); // 1 gc2.setItem("hello"); // Error }
คุณไม่สามารถส่ง primitive data type ให้ type parameter ตอนสร้าง generic class object ได้ เฉพาะ data types ที่ extend Object type เท่านั้นที่ส่งเป็น type parameters ได้
ตัวอย่าง:
GenericClass gc3 = new GenericClass<>(); // Error
Generic Methods
การสร้าง generic methods ทำตามรูปแบบคล้ายการสร้าง generic classes คุณสามารถ implement generic method ภายใน generic class และ non-generic class ได้
public class GenericMethodClass { public static 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 } }
ที่นี่ คุณส่ง array ของชนิดเฉพาะไป parameterize method Generic method printItems() iterate ผ่าน array ที่ส่งมาและพิมพ์ items ที่เก็บ เหมือน Java method ปกติ
Bounded Type Parameters
จนตอนนี้ generic classes และ methods ที่เราสร้าง parameterize ด้วย data type ใดก็ได้ ยกเว้น primitive types แต่ถ้าเราอยากจำกัด data types ที่ส่งให้ generics ได้? นี่คือที่ bounded type parameters เข้ามา
คุณสามารถ bound data types ที่ generic class หรือ method ยอมรับ โดยระบุว่าต้องเป็น subclass ของ data type อื่น
ตัวอย่าง:
// รับเฉพาะ subclasses ของ List public class UpperBoundedClass{ // รับเฉพาะ subclasses ของ List public void UpperBoundedMethod(T[] arr) { } }
ที่นี่ UpperBoundedClass และ UpperBoundedMethod สามารถ parameterize ได้เฉพาะ subtypes ของ List data type
List data type ทำหน้าที่เป็น upper bound ของ type parameter หากลองใช้ data type ที่ไม่ใช่ subtype ของ List จะมี compile-time error
Bounds ไม่จำกัดแค่ classes คุณส่ง interfaces ได้ด้วย Extending interface ในกรณีนี้หมายถึง implementing the interface
Parameter สามารถมี multiple bounds ได้เหมือนตัวอย่างนี้:
// รับเฉพาะ subclasses ของทั้ง Mammal และ Animal public class MultipleBoundedClass{ // รับเฉพาะ subclasses ของทั้ง Mammal และ Animal public void MultipleBoundedMethod(T[] arr){ } }
Data type ที่ยอมรับ ต้องเป็น subclass ของทั้ง Animal และ Mammal classes หาก bound หนึ่งเป็น class ต้องมาก่อนใน bound declaration
ในตัวอย่างข้างต้น หาก Mammal เป็น class และ Animal เป็น interface Mammal ต้องมาก่อนเหมือนข้างต้น มิฉะนั้นโค้ดจะโยน compile-time error
Java Generics Wildcards
Wildcards ใช้สำหรับส่ง parameters ของ generic types ไปยัง methods ต่างจาก generic method ที่นี่ generic parameter ส่งไปยัง parameters ที่ method ยอมรับ ซึ่งต่างจาก type parameter ที่อภิปรายข้างต้น Wildcard แทนด้วยสัญลักษณ์ ?
public void printItems(List> list) { for (int i=0; i< list.size(); i++) { System.out.println(list.get(i)); } }
เมธอด printItems() ข้างต้นยอมรับ lists ของ data type ใดก็ได้เป็น parameter นี่ป้องกันโปรแกรมเมอร์จากการทำโค้ดซ้ำสำหรับ lists ของ data types ต่างๆ ซึ่งจะเป็นกรณีโดยไม่มี generics
Upper Bounded Wildcards
หากเราอยากจำกัด data types ที่เก็บใน list ที่ method ยอมรับ เราสามารถใช้ bounded wildcards
ตัวอย่าง:
public void printSubTypes(List extends Color> list) { for (int i=0; i< list.size(); i++) { System.out.println(list.get(i)); } }
เมธอด printSubTypes() ยอมรับเฉพาะ lists ที่เก็บ subtypes ของ Color ยอมรับ list ของ RedColor หรือ BlueColor objects แต่ไม่ยอมรับ list ของ Animal objects เพราะ Animal ไม่ใช่ subtype ของ Color นี่คือตัวอย่าง upper-bounded wildcard
Lower Bounded Wildcards
เช่นเดียวกัน หากเรามี:
public void printSuperTypes(List super Dog> list) { for (int i=0; i< list.size(); i++) { System.out.println(list.get(i)); } }
เมธอด printSuperTypes() จะยอมรับเฉพาะ lists ที่เก็บ super types ของ Dog class จะยอมรับ list ของ Mammal หรือ Animal objects แต่ไม่ยอมรับ list ของ LabDog objects เพราะ LabDog ไม่ใช่ superclass ของ Dog แต่เป็น subclass นี่คือตัวอย่าง lower-bounded wildcard
สรุป
Java Generics กลายเป็นฟีเจอร์ที่โปรแกรมเมอร์ไม่ thểขาดตั้งแต่นำเข้า
ความนิยมนี้มาจากผลกระทบต่อการทำให้ชีวิตโปรแกรมเมอร์ง่ายขึ้น นอกจากป้องกันการทำผิดพลาดในการเขียนโค้ดแล้ว การใช้ generics ทำให้โค้ดน้อยซ้ำลง สังเกตเห็นไหมว่ามัน generalize classes และ methods เพื่อไม่ต้องทำโค้ดซ้ำสำหรับ data types ต่างๆ?
การมีความเข้าใจที่ดีใน generics สำคัญต่อการเป็น expert ในภาษา ดังนั้น การนำไปใช้ในโค้ดจริงคือทางไปข้างหน้า