Reflection & Annotation

บทนำ: เมื่อ Code ต้องตรวจสอบตัวเอง

ในการเขียนโปรแกรมทั่วไป เรามักรู้ว่า class, method, field มีอะไรบ้างตั้งแต่เขียนโค้ด แต่มีบางสถานการณ์ที่ program ต้องตรวจสอบโครงสร้างของ class และ object ระหว่าง runtime เพื่อตัดสินใจว่าจะทำอะไร

ตัวอย่างเช่น:

  • Framework เช่น Spring ต้องรู้ว่า class มี constructor, method, field อะไรบ้าง เพื่อ instantiate object
  • Library ที่ทำ JSON serialization ต้องดูว่า object มี field ชื่ออะไร เพื่อ convert เป็น JSON
  • Testing framework เช่น JUnit ต้องหา method ที่มี @Test annotation เพื่อรู้ว่า method ไหนที่ต้อง run
  • ORM (Object-Relational Mapping) library ต้องรู้ว่า class field ไหนเป็น primary key, foreign key เป็นต้น

นี่คือที่ที่ Reflection และ Annotation เข้ามาบทบาท

Reflection คือเทคนิค ที่ให้ program ตรวจสอบและ manipulate ตัวของมันเอง เช่น ดูว่า class มี method อะไร, field อะไร, พร้อมเข้าถึงข้อมูลเกี่ยวกับมันได้

Annotation คือเครื่องหมายพิเศษที่เราติด (ลักษณะ @Something) เพื่อให้ข้อมูลเพิ่มเติมแก่ program เมื่อ runtime program สามารถ check ว่า class, method, field มี annotation อะไร แล้วทำอย่างไรตามนั้น


Reflection: ตรวจสอบและ Manipulate Objects

ความเข้าใจพื้นฐาน

เมื่อเราสร้าง object จากการนิยาม class ผ่านไปแล้ว Java ยังคง จำข้อมูลที่เกี่ยวกับ class นั้น (metadata) ไว้ ข้อมูลนี้เก็บใน object พิเศษเรียกว่า Class object ที่เราสามารถเข้าถึงผ่าน .getClass() method

ตัวอย่างเบื้องต้น:

javapublic class Student {
    private String name;
    private int age;
    
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    public String getName() {
        return name;
    }
    
    public int getAge() {
        return age;
    }
}

// ใช้ Reflection
public class ReflectionBasics {
    public static void main(String[] args) {
        Student student = new Student("Alice", 20);
        
        // ได้ Class object
        Class<?> clazz = student.getClass();
        
        // ตรวจสอบข้อมูลของ class
        System.out.println("Class name: " + clazz.getName());
        System.out.println("Simple name: " + clazz.getSimpleName());
        
        // ดูว่า class มี method อะไร
        java.lang.reflect.Method[] methods = clazz.getDeclaredMethods();
        System.out.println("Methods:");
        for (java.lang.reflect.Method method : methods) {
            System.out.println("  - " + method.getName());
        }
        
        // ดูว่า class มี field อะไร
        java.lang.reflect.Field[] fields = clazz.getDeclaredFields();
        System.out.println("Fields:");
        for (java.lang.reflect.Field field : fields) {
            System.out.println("  - " + field.getName() + " (" + field.getType().getSimpleName() + ")");
        }
    }
}

// OUTPUT:
// Class name: Student
// Simple name: Student
// Methods:
//   - getName
//   - getAge
// Fields:
//   - name (String)
//   - age (int)

ในตัวอย่างนี้ program ตรวจสอบตัวมันเอง ดู ว่า Student class มี method และ field อะไรบ้าง โดยไม่ต้องดู source code ของ Student


ตัวอย่างที่ 1: Dynamic Method Invocation

เรียก Method แบบ Dynamic

ตัวอย่างที่น่าสนใจกว่า คือ invoke (เรียก) method แบบ dynamic หมายความว่า ตัดสินใจว่าจะเรียก method ไหนระหว่าง runtime:

javapublic class Calculator {
    
    public int add(int a, int b) {
        return a + b;
    }
    
    public int subtract(int a, int b) {
        return a - b;
    }
    
    public int multiply(int a, int b) {
        return a * b;
    }
}

// Dynamic method invocation
public class DynamicInvocation {
    
    public static void main(String[] args) throws Exception {
        Calculator calculator = new Calculator();
        Class<?> clazz = calculator.getClass();
        
        // สมมติว่า operator มาจาก user input
        String operator = "multiply";
        
        // ได้ method object โดยใช้ชื่อ
        java.lang.reflect.Method method = 
            clazz.getMethod(operator, int.class, int.class);
        
        // เรียก method ด้วย invoke()
        Object result = method.invoke(calculator, 10, 5);
        
        System.out.println(operator + "(10, 5) = " + result);  // multiply(10, 5) = 50
    }
}

ในตัวอย่างนี้ program:

  1. ได้ชื่อ method จาก string
  2. หา method object ด้วย getMethod()
  3. เรียก method ด้วย invoke()

ข้อดี: ถ้า user ป้อน “add”, “subtract” หรือ “multiply” program สามารถเรียก method ที่ถูกต้องได้โดยไม่ต้องใช้ if-else ยาวๆ


ตัวอย่างที่ 2: Inspecting Field Values

ตรวจสอบและเปลี่ยนแปลง Field Values

Reflection ยังให้ความสามารถอื่น คือ เปลี่ยนแปลงค่า field แม้ว่า field นั้นเป็น private:

javapublic class Person {
    private String name;
    private int age;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    @Override
    public String toString() {
        return "Person{" + "name='" + name + "', age=" + age + "}";
    }
}

// ใช้ Reflection เพื่อเปลี่ยนแปลง private fields
public class FieldManipulation {
    
    public static void main(String[] args) throws Exception {
        Person person = new Person("Bob", 25);
        System.out.println("Before: " + person);
        
        Class<?> clazz = Person.class;
        
        // เปลี่ยนแปลง name field
        java.lang.reflect.Field nameField = clazz.getDeclaredField("name");
        nameField.setAccessible(true);  // ← อนุญาตให้เข้าถึง private field
        nameField.set(person, "Charlie");
        
        // เปลี่ยนแปลง age field
        java.lang.reflect.Field ageField = clazz.getDeclaredField("age");
        ageField.setAccessible(true);
        ageField.set(person, 30);
        
        System.out.println("After: " + person);
    }
}

// OUTPUT:
// Before: Person{name='Bob', age=25}
// After: Person{name='Charlie', age=30}

นี่อาจดูเหมือน “วิปโยค” private access modifier แต่ในความเป็นจริง framework อย่าง Spring, Hibernate ใช้เทคนิคนี้เพื่อ set field values โดยตรง แทนที่จะเรียก setter method


Annotation: ติดป้ายบอกข้อมูล

Custom Annotation

Annotation คือการติดป้ายเพิ่มเติมบน class, method, field เพื่อให้ข้อมูล metadata สมมติเราต้องสร้าง unit test framework ที่ต้องรู้ว่า method ไหนควร run เราสามารถสร้าง custom annotation:

java// สร้าง Custom Annotation
import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)  // ← เก็บไว้จนถึง runtime
@Target(ElementType.METHOD)           // ← ใช้ได้กับ method เท่านั้น
public @interface Test {
    // annotation definition
}

ตอนนี้เราสามารถใช้ annotation นี้:

javapublic class TestClass {
    
    @Test
    public void testAddition() {
        assert 2 + 2 == 4;
        System.out.println("✓ Addition test passed");
    }
    
    @Test
    public void testSubtraction() {
        assert 5 - 3 == 2;
        System.out.println("✓ Subtraction test passed");
    }
    
    public void helperMethod() {
        // วิธีนี้ไม่มี @Test ดังนั้น ไม่ควร run
        System.out.println("Helper method");
    }
}

จากนั้นเราสามารถเขียน test runner ที่ใช้ Reflection เพื่อหา method ที่มี @Test annotation และรัน:

javapublic class SimpleTestRunner {
    
    public static void main(String[] args) throws Exception {
        TestClass testObj = new TestClass();
        Class<?> clazz = TestClass.class;
        
        // หา method ทั้งหมด
        java.lang.reflect.Method[] methods = clazz.getDeclaredMethods();
        
        for (java.lang.reflect.Method method : methods) {
            // ตรวจสอบว่า method มี @Test annotation ไหม
            if (method.isAnnotationPresent(Test.class)) {
                System.out.println("Running: " + method.getName());
                method.invoke(testObj);  // ← เรียก method
            }
        }
    }
}

// OUTPUT:
// Running: testAddition
// ✓ Addition test passed
// Running: testSubtraction
// ✓ Subtraction test passed

ข้อดี: Test runner ไม่ต้องรู้ว่า test method ชื่ออะไร มันเพียงแค่หา method ที่มี @Test annotation แล้ว run


Built-in Annotations

Annotation ที่ Java มีให้แล้ว

Java มี annotation ที่เป็นมาตรฐานแล้ว ที่นักพัฒนามักใช้:

@Override – บ่งชี้ว่า method override method จากผู้ปกครอง Compiler จะ check เพื่อหลีกเลี่ยงข้อผิดพลาด:

javapublic class Animal {
    public void sound() {
        System.out.println("Generic sound");
    }
}

public class Dog extends Animal {
    @Override  // ← บ่งชี้ว่า override method จากผู้ปกครอง
    public void sound() {
        System.out.println("Bark");
    }
}

@Deprecated – บ่งชี้ว่า method หรือ class ไม่ควรใช้แล้ว Compiler จะ warn:

javapublic class OldAPI {
    
    @Deprecated  // ← ไม่ควรใช้
    public void oldMethod() {
        System.out.println("Old way");
    }
    
    public void newMethod() {
        System.out.println("New way");
    }
}

@FunctionalInterface – บ่งชี้ว่า interface นี้ควรมี single abstract method เพื่อใช้กับ lambda:

java@FunctionalInterface
public interface Calculator {
    int calculate(int a, int b);
}

Practical Example: Building a Simple Serializer

ตัวอย่างจริง: JSON Serializer

มาดูตัวอย่างจริงว่า Reflection และ Annotation ทำงานร่วมกันอย่างไร เราจะสร้าง simple JSON serializer ที่ convert object เป็น JSON string:

ก่อนอื่น สร้าง annotation เพื่อทำเครื่องหมาย field ที่ต้องการ include ใน JSON:

javaimport java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface JsonField {
    String name() default "";  // ← ชื่อใน JSON
}

จากนั้น สร้าง class ที่มี field ติด annotation:

javapublic class Product {
    
    @JsonField(name = "id")
    private int id;
    
    @JsonField(name = "title")
    private String title;
    
    @JsonField(name = "price")
    private double price;
    
    public Product(int id, String title, double price) {
        this.id = id;
        this.title = title;
        this.price = price;
    }
}

สุดท้ายสร้าง JSON serializer ที่ใช้ Reflection:

javapublic class JsonSerializer {
    
    public static String toJson(Object obj) throws IllegalAccessException {
        Class<?> clazz = obj.getClass();
        java.lang.reflect.Field[] fields = clazz.getDeclaredFields();
        
        StringBuilder json = new StringBuilder("{");
        boolean first = true;
        
        for (java.lang.reflect.Field field : fields) {
            // ตรวจสอบว่า field มี @JsonField annotation
            if (field.isAnnotationPresent(JsonField.class)) {
                JsonField annotation = field.getAnnotation(JsonField.class);
                String fieldName = annotation.name();
                
                field.setAccessible(true);
                Object value = field.get(obj);
                
                if (!first) json.append(", ");
                
                // เพิ่มเข้า JSON
                if (value instanceof String) {
                    json.append("\"").append(fieldName).append("\": \"").append(value).append("\"");
                } else {
                    json.append("\"").append(fieldName).append("\": ").append(value);
                }
                
                first = false;
            }
        }
        
        json.append("}");
        return json.toString();
    }
}

// ใช้งาน
public class Main {
    public static void main(String[] args) throws IllegalAccessException {
        Product product = new Product(101, "Laptop", 1200.00);
        String json = JsonSerializer.toJson(product);
        System.out.println(json);
    }
}

// OUTPUT:
// {"id": 101, "title": "Laptop", "price": 1200.0}

ในตัวอย่างนี้:

  1. เรามี annotation @JsonField เพื่อบ่งชี้ field ไหนควร include ใน JSON
  2. JSON serializer ใช้ Reflection เพื่อ:
    • หา fields ทั้งหมดใน class
    • ตรวจสอบว่า field ไหนมี @JsonField
    • เอาค่า field มาสร้าง JSON string

นี่คือหลักการเดียวกัน ที่ library เช่น Jackson, Gson ใช้ เพื่อ convert object เป็น JSON


Best Practices & Cautions

ประโยชน์ของ Reflection & Annotation

ประโยชน์:

  • Framework สามารถ auto-configure code ได้ (เช่น Spring)
  • Library สามารถ generic handling multiple types (เช่น JSON serialization)
  • Testing framework สามารถ auto-discover tests
  • ORM สามารถ map object properties ไปยัง database columns

ข้อควรระวัง:

Reflection มีประสิทธิภาพน้อยกว่า direct method calls เพราะ JVM ไม่สามารถ optimize ได้ดี ดังนั้นใช้ Reflection เฉพาะเมื่อจำเป็น

Reflection สามารถ bypass access modifiers (private, protected) ทำให้ encapsulation ไม่มีความหมาย ควรใช้อย่างระมัดระวัง

Annotation มี metadata เท่านั้น จะไม่ทำอะไรเองถ้าไม่มี code ที่ read และ process annotation


สรุป

Reflection & Annotation เป็นเครื่องมือที่ทรงพลังในการสร้าง flexible frameworks และ libraries:

Reflection ให้ความสามารถ:

  • ตรวจสอบโครงสร้างของ class, method, field ที่ runtime
  • เรียก method, เข้าถึง field แบบ dynamic
  • สร้าง object และ manipulate ได้อย่างอิสระ

Annotation ให้ความสามารถ:

  • ติดป้ายข้อมูล metadata บน code
  • Framework ตรวจสอบ annotation และทำการตามนั้น
  • ทำให้ code เป็น declarative แทนที่ imperative

ตัวอย่างการใช้จริง:

  • Spring Framework ใช้ Annotation (@Component@Autowired) และ Reflection เพื่อ dependency injection
  • JUnit ใช้ @Test annotation เพื่อหา test methods
  • Jackson/Gson ใช้ Reflection เพื่อ serialize/deserialize objects

Reflection และ Annotation อาจดูทำให้ code ซับซ้อน แต่เมื่อใช้อย่างถูกต้อง มัน enable framework developers เพื่อให้ developer ที่ใช้ framework สามารถเขียน code ที่สั้น และชัดเจนได้