บทนำ: ปัญหาของ “ไม่รู้ประเภท”
เมื่อคุณใช้ Collections เช่น ArrayList หรือ HashMap คุณต้องระบุว่า “นี่เก็บข้อมูลประเภท อะไร?”
ถ้าไม่ระบุ Java จะ “ยินยอม” ให้เก็บข้อมูล ประเภทใดก็ได้ ซึ่งเป็นปัญหา:
java// ❌ ไม่ดี: ไม่ระบุประเภท (raw type)
ArrayList list = new ArrayList();
list.add("สมชาย");
list.add(123);
list.add(3.14);
// เมื่อได้ข้อมูลออกมา ไม่รู้ว่าเป็นอะไร
String name = (String) list.get(0); // ← ต้อง cast
Integer age = (Integer) list.get(1); // ← ต้อง cast
Double score = (Double) list.get(2); // ← ต้อง cast
// ถ้า cast ผิด → RuntimeException!
Generic Types (<T>) คือคำตอบ – มันให้คุณ “บอก Java ว่าจะเก็บข้อมูลประเภทอะไร” ตั้งแต่สร้าง
- บทนำ: ปัญหาของ "ไม่รู้ประเภท"
- Generic Types คืออะไร?
- ตัวอย่างพื้นฐาน
- ตัวอย่างที่ 1: Generic ขั้นพื้นฐาน
- ตัวอย่างที่ 2: Generic กับ Map
- Generic กับ Custom Class
- ตัวอย่างที่ 3: List
- การสร้าง Generic Class เอง
- ตัวอย่างที่ 4: Generic Container
- Generic Method
- ตัวอย่างที่ 5: Method ที่ Generic
- Bounded Generic Type
- ตัวอย่างที่ 6: Bounded
- Type Erasure (ข้อควรรู้)
- ความรู้เพิ่มเติม
- ตัวอย่างรวม: Generic Data Structure
- สรุป
Generic Types คืออะไร?
Generic Type = ประเภทที่ “ตัวแปร” (variable type) ได้ ให้คุณเขียน “แบบ template” ที่สามารถใช้ได้กับหลายประเภท
textสัญกรณ์:
ClassName<T>
↓
ประเภท ที่คุณเลือก
ตัวอย่างพื้นฐาน
java// ✓ ดี: ระบุประเภท (Generic)
ArrayList<String> names = new ArrayList<String>();
names.add("สมชาย");
names.add("สมหญิง");
ArrayList<Integer> ages = new ArrayList<Integer>();
ages.add(25);
ages.add(30);
// ไม่ต้อง cast เพราะ Java รู้ประเภล
String name = names.get(0); // ไม่ต้อง cast
Integer age = ages.get(0); // ไม่ต้อง cast
// ❌ Compiler จะ warn ถ้าพยายาม add ประเภทผิด
// names.add(123); // ERROR!
ตัวอย่างที่ 1: Generic ขั้นพื้นฐาน
javaimport java.util.*;
public class Main {
public static void main(String[] args) {
// ==== List<String> ====
System.out.println("=== List<String> ===");
List<String> fruits = new ArrayList<String>();
fruits.add("แอปเปิล");
fruits.add("กล้วย");
fruits.add("ส้ม");
// ไม่ต้อง cast
for (String fruit : fruits) {
System.out.println("- " + fruit);
}
// ==== List<Integer> ====
System.out.println("\n=== List<Integer> ===");
List<Integer> scores = new ArrayList<Integer>();
scores.add(85);
scores.add(90);
scores.add(78);
// ไม่ต้อง cast
double average = 0;
for (Integer score : scores) {
average += score;
}
average /= scores.size();
System.out.println("คะแนนเฉลี่ย: " + average);
// ==== Set<Double> ====
System.out.println("\n=== Set<Double> ===");
Set<Double> prices = new HashSet<Double>();
prices.add(99.99);
prices.add(49.99);
prices.add(29.99);
prices.add(99.99); // ซ้ำ - จะถูก ignore
double total = 0;
for (Double price : prices) {
total += price;
}
System.out.println("ราคารวม: " + total);
}
}
Output:
text=== List<String> ===
- แอปเปิล
- กล้วย
- ส้ม
=== List<Integer> ===
คะแนนเฉลี่ย: 84.33333333333333
=== Set<Double> ===
ราคารวม: 179.97
คำอธิบาย:
ArrayList<String>= ArrayList ที่เก็บ StringList<Integer>= List ที่เก็บ IntegerSet<Double>= Set ที่เก็บ Double- ไม่ต้อง type cast เพราะ compiler รู้ประเภล
ตัวอย่างที่ 2: Generic กับ Map
javaimport java.util.*;
public class Main {
public static void main(String[] args) {
// ==== Map<String, Integer> ====
System.out.println("=== Map<String, Integer> ===");
Map<String, Integer> inventory = new HashMap<String, Integer>();
inventory.put("Notebook", 50);
inventory.put("Pen", 100);
inventory.put("Pencil", 200);
// ไม่ต้อง cast
for (String product : inventory.keySet()) {
Integer quantity = inventory.get(product);
System.out.println(product + ": " + quantity + " ชิ้น");
}
// ==== Map<String, Double> ====
System.out.println("\n=== Map<String, Double> ===");
Map<String, Double> prices = new HashMap<String, Double>();
prices.put("Apple", 50.0);
prices.put("Banana", 30.0);
prices.put("Orange", 40.0);
double total = 0;
for (Double price : prices.values()) {
total += price;
}
System.out.println("ราคารวม: " + total);
// ==== Map<Integer, String> ====
System.out.println("\n=== Map<Integer, String> ===");
Map<Integer, String> students = new HashMap<Integer, String>();
students.put(1, "สมชาย");
students.put(2, "สมหญิง");
students.put(3, "สมศรี");
for (Map.Entry<Integer, String> entry : students.entrySet()) {
System.out.println("เลขที่ " + entry.getKey() + ": " + entry.getValue());
}
}
}
Output:
text=== Map<String, Integer> ===
Notebook: 50 ชิ้น
Pen: 100 ชิ้น
Pencil: 200 ชิ้น
=== Map<String, Double> ===
ราคารวม: 120.0
=== Map<Integer, String> ===
เลขที่ 1: สมชาย
เลขที่ 2: สมหญิง
เลขที่ 3: สมศรี
Generic กับ Custom Class
ตัวอย่างที่ 3: List<CustomClass>
javaimport java.util.*;
// ==== Custom Class ====
public class Product {
private String name;
private double price;
private int quantity;
public Product(String name, double price, int quantity) {
this.name = name;
this.price = price;
this.quantity = quantity;
}
public String getName() { return name; }
public double getPrice() { return price; }
public int getQuantity() { return quantity; }
public double getTotalValue() { return price * quantity; }
@Override
public String toString() {
return name + " (" + quantity + " ชิ้น @ " + price + " บาท)";
}
}
// ==== การใช้งาน ====
public class Main {
public static void main(String[] args) {
// สร้าง List<Product>
List<Product> products = new ArrayList<Product>();
products.add(new Product("Notebook", 50, 10));
products.add(new Product("Pen", 10, 50));
products.add(new Product("Pencil", 5, 100));
// ไม่ต้อง cast
System.out.println("=== สินค้า ===");
double totalValue = 0;
for (Product product : products) {
System.out.println("- " + product);
totalValue += product.getTotalValue();
}
System.out.println("\nมูลค่ารวม: " + totalValue + " บาท");
}
}
Output:
text=== สินค้า ===
- Notebook (10 ชิ้น @ 50.0 บาท)
- Pen (50 ชิ้น @ 10.0 บาท)
- Pencil (100 ชิ้น @ 5.0 บาท)
มูลค่ารวม: 1500.0 บาท
คำอธิบาย:
List<Product>= List ที่เก็บ Product objects- แต่ละ element เป็น Product ที่สมบูรณ์ ไม่ต้อง cast
การสร้าง Generic Class เอง
ตัวอย่างที่ 4: Generic Container
บางครั้ง คุณอาจต้องสร้าง class ของตัวเอง ที่ใช้ generic
java// ==== Generic Class ====
public class Container<T> {
private T item;
public void put(T item) {
this.item = item;
}
public T get() {
return item;
}
public boolean isEmpty() {
return item == null;
}
}
// ==== การใช้งาน ====
public class Main {
public static void main(String[] args) {
// Container<String>
System.out.println("=== Container<String> ===");
Container<String> stringContainer = new Container<String>();
stringContainer.put("Hello");
System.out.println("ได้: " + stringContainer.get());
// Container<Integer>
System.out.println("\n=== Container<Integer> ===");
Container<Integer> intContainer = new Container<Integer>();
intContainer.put(42);
System.out.println("ได้: " + intContainer.get());
// Container<Product>
System.out.println("\n=== Container<Product> ===");
Container<Product> productContainer = new Container<Product>();
Product p = new Product("Laptop", 30000, 1);
productContainer.put(p);
System.out.println("ได้: " + productContainer.get());
}
}
Output:
text=== Container<String> ===
ได้: Hello
=== Container<Integer> ===
ได้: 42
=== Container<Product> ===
ได้: Notebook (10 ชิ้น @ 50.0 บาท)
คำอธิบาย:
Container<T>=Tคือ “placeholder” สำหรับประเภลใดๆTจะถูก replace ด้วยประเภลจริง เช่นString,Integer,Product- เหมือน “template” ที่สามารถใช้ได้หลายประเภล
Generic Method
ตัวอย่างที่ 5: Method ที่ Generic
บางครั้ง คุณต้อง method ที่ “เลือกประเภลได้”
javapublic class Utilities {
// ==== Generic Method ====
public static <T> void printArray(T[] array) {
System.out.print("[ ");
for (T item : array) {
System.out.print(item + " ");
}
System.out.println("]");
}
// ==== Generic Method ที่ return ====
public static <T> T getFirst(T[] array) {
if (array.length > 0) {
return array[0];
}
return null;
}
// ==== Generic Method ที่ค้นหา ====
public static <T> boolean contains(T[] array, T element) {
for (T item : array) {
if (item.equals(element)) {
return true;
}
}
return false;
}
}
// ==== การใช้งาน ====
public class Main {
public static void main(String[] args) {
// ใช้กับ String[]
String[] names = {"สมชาย", "สมหญิง", "สมศรี"};
System.out.println("=== String Array ===");
Utilities.printArray(names);
System.out.println("First: " + Utilities.getFirst(names));
System.out.println("มี 'สมหญิง'? " + Utilities.contains(names, "สมหญิง"));
// ใช้กับ Integer[]
Integer[] scores = {85, 90, 78, 95};
System.out.println("\n=== Integer Array ===");
Utilities.printArray(scores);
System.out.println("First: " + Utilities.getFirst(scores));
System.out.println("มี 90? " + Utilities.contains(scores, 90));
// ใช้กับ Double[]
Double[] prices = {50.0, 30.0, 99.99};
System.out.println("\n=== Double Array ===");
Utilities.printArray(prices);
System.out.println("First: " + Utilities.getFirst(prices));
}
}
Output:
text=== String Array ===
[ สมชาย สมหญิง สมศรี ]
First: สมชาย
มี 'สมหญิง'? true
=== Integer Array ===
[ 85 90 78 95 ]
First: 85
มี 90? true
=== Double Array ===
[ 50.0 30.0 99.99 ]
First: 50.0
คำอธิบาย:
<T>ในวงเล็บ method = บอก Java ว่า method นี้เป็น genericprintArray(T[] array)= array สามารถเป็น String[], Integer[], Double[] ฯลฯ- Method เดียว แต่ใช้ได้หลายประเภท
Bounded Generic Type
ตัวอย่างที่ 6: Bounded <T extends Class>
บางครั้ง คุณต้อง T แต่จำกัดว่า “ต้องเป็นประเภลนี้ หรือ subclass ของนี้”
java// ==== Number hierarchy ====
// Number
// ├─ Integer
// ├─ Double
// ├─ Float
// └─ Long
// ==== Bounded Generic ====
public class NumberUtilities {
// T ต้องเป็น Number หรือ subclass
public static <T extends Number> double getAverage(T[] numbers) {
double sum = 0;
for (T num : numbers) {
sum += num.doubleValue(); // Number มี method นี้
}
return sum / numbers.length;
}
public static <T extends Comparable<T>> T getMax(T[] array) {
T max = array[0];
for (T item : array) {
if (item.compareTo(max) > 0) {
max = item;
}
}
return max;
}
}
// ==== การใช้งาน ====
public class Main {
public static void main(String[] args) {
// Integer[] - ใช้ได้ (Integer extends Number)
Integer[] intArray = {10, 20, 30, 40};
System.out.println("Integer average: " +
NumberUtilities.getAverage(intArray));
// Double[] - ใช้ได้ (Double extends Number)
Double[] doubleArray = {1.5, 2.5, 3.5};
System.out.println("Double average: " +
NumberUtilities.getAverage(doubleArray));
// String[] - ใช้ได้ (String extends Comparable)
String[] strArray = {"apple", "zebra", "banana", "dog"};
System.out.println("Max string: " +
NumberUtilities.getMax(strArray));
// Integer[] - ใช้ได้ (Integer extends Comparable)
System.out.println("Max integer: " +
NumberUtilities.getMax(intArray));
}
}
Output:
textInteger average: 25.0
Double average: 2.5
Max string: zebra
Max integer: 40
คำอธิบาย:
<T extends Number>= T ต้องเป็น Number หรือ subclass- ทำให้เรา guarantee ว่า T มี method
.doubleValue() <T extends Comparable<T>>= T ต้อง implement Comparable interface
Type Erasure (ข้อควรรู้)
ความรู้เพิ่มเติม
Java’s generics ใช้ “type erasure” – ที่ compile time generic type ถูก “erase” ไป
java// เขียน
List<String> list = new ArrayList<String>();
// Compile ไปเป็น
List list = new ArrayList(); // Type info ถูกลบออก
// Runtime จึง:
// ❌ ไม่สามารถใช้ generics เพื่อ runtime checks
// ❌ ไม่สามารถ new T[] ได้โดยตรง
// ✓ แต่ compile-time มี type safety
ตัวอย่างรวม: Generic Data Structure
javaimport java.util.*;
// ==== Generic Pair Class ====
public class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() { return key; }
public V getValue() { return value; }
@Override
public String toString() {
return key + " → " + value;
}
}
// ==== การใช้งาน ====
public class Main {
public static void main(String[] args) {
// ==== Pair<String, Integer> ====
System.out.println("=== Pair<String, Integer> ===");
List<Pair<String, Integer>> scores = new ArrayList<>();
scores.add(new Pair<>("สมชาย", 85));
scores.add(new Pair<>("สมหญิง", 90));
scores.add(new Pair<>("สมศรี", 78));
for (Pair<String, Integer> pair : scores) {
System.out.println(pair);
}
// ==== Pair<String, String> ====
System.out.println("\n=== Pair<String, String> ===");
Map<String, Pair<String, String>> users = new HashMap<>();
users.put("user1", new Pair<>("name", "สมชาย"));
users.put("user2", new Pair<>("name", "สมหญิง"));
for (Pair<String, String> pair : users.values()) {
System.out.println(pair);
}
// ==== Pair<Integer, String> ====
System.out.println("\n=== Pair<Integer, String> ===");
List<Pair<Integer, String>> inventory = new ArrayList<>();
inventory.add(new Pair<>(1, "Notebook"));
inventory.add(new Pair<>(2, "Pen"));
inventory.add(new Pair<>(3, "Pencil"));
for (Pair<Integer, String> pair : inventory) {
System.out.println(pair);
}
}
}
Output:
text=== Pair<String, Integer> ===
สมชาย → 85
สมหญิง → 90
สมศรี → 78
=== Pair<String, String> ===
name → สมชาย
name → สมหญิง
=== Pair<Integer, String> ===
1 → Notebook
2 → Pen
3 → Pencil
สรุป
Generic Types (<T>) คือกลไกของ Java ที่ให้คุณเขียน “code ที่ยืดหยุ่น แต่ยังมี type safety”:
- ไม่ต้อง type cast – Java รู้ว่าประเภลคืออะไร
- Compile-time safety – หาข้อผิดพลาดตั้งแต่เขียน code
- Code reuse – method เดียว ใช้ได้หลายประเภท
- สะอาด (readable) –
List<String>ชัดกว่า rawList
Generic ทำให้ code ของคุณ:
- ปลอดภัยกว่า – ไม่มี ClassCastException ขึ้นมาอย่างน้อยเท่าที่เป็นไปได้
- ยืดหยุ่นกว่า – สามารถใช้หลายประเภลได้
- อ่านเข้าใจง่ายกว่า – intention ของ code ชัดเจน
Generic เป็นหนึ่งในคุณลักษณะที่สำคัญของ Java ที่ทำให้ code นั้น safe, clean, และ professional
