Generic Type 

บทนำ: ปัญหาของ “ไม่รู้ประเภท”

เมื่อคุณใช้ 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 คืออะไร?

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 ที่เก็บ String
  • List<Integer> = List ที่เก็บ Integer
  • Set<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 ด้วยประเภลจริง เช่น StringIntegerProduct
  • เหมือน “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 นี้เป็น generic
  • printArray(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> ชัดกว่า raw List

Generic ทำให้ code ของคุณ:

  • ปลอดภัยกว่า – ไม่มี ClassCastException ขึ้นมาอย่างน้อยเท่าที่เป็นไปได้
  • ยืดหยุ่นกว่า – สามารถใช้หลายประเภลได้
  • อ่านเข้าใจง่ายกว่า – intention ของ code ชัดเจน

Generic เป็นหนึ่งในคุณลักษณะที่สำคัญของ Java ที่ทำให้ code นั้น safe, clean, และ professional