Java Reflection API
Normal kod yazarken hangi sınıfı kullanacağını, hangi metodu çağıracağını önceden bilirsin — her şey derleme zamanında bellidir. Ama bazen program çalışırken, hangi sınıfla karşılaşacağını bilemezsin. Bir framework yazdığını düşün: kullanıcı hangi sınıfı verecek? Hangi alanları olacak? Bunu önceden bilemezsin ama yine de o sınıfın alanlarına erişmen, metodlarını çağırman gerekir.
İşte Reflection API tam olarak bu ihtiyaca cevap verir: bir Java programının çalışma zamanında kendi yapısını incelemesi ve değiştirmesi.
1. Reflection Nedir?
Reflection kelimesi "yansıma" demektir. Tıpkı bir aynanın karşısına geçip kendine bakman gibi, bir Java programı da Reflection ile kendi yapısına bakabilir.
🎯 Analoji — Ayna:
>
Bir insanın aynaya bakmasını düşün. Ayna olmadan saçının dağınık olduğunu, gömleğinin ters olduğunu göremezsin. Reflection, Java programı için bu ayna işlevini görür: program kendi sınıflarını, alanlarını, metodlarını "görebilir" ve hatta değiştirebilir. Normal kod yazarken her şey önceden bellidir (derleme zamanı) — ama Reflection ile program çalışırken kendini keşfeder. Tıpkı aynaya bakıp "hmm, bu alan private'mış ama ben yine de erişebilirim" demek gibi.
Reflection iki temel yetenek sağlar:
Introspection (İç gözlem): Sınıfın yapısını inceleme — hangi alanları var, hangi metodları var, hangi annotation'ları taşıyor?
Manipulation (Müdahale): Alanların değerlerini okuma/yazma, metodları çağırma, yeni nesneler oluşturma — hatta
privateolanlara bile erişme.
Ne Zaman Kullanılır?
Framework geliştirme: Spring, Hibernate, Jackson gibi framework'ler kullanıcı sınıflarını önceden bilmez — reflection ile keşfeder
Test araçları: JUnit test metodlarını
@Testannotation'ına bakarak bulurSerialization: Jackson, JSON'a dönüştüreceği sınıfın alanlarını reflection ile okur
Dependency Injection: Spring, constructor parametrelerini reflection ile inceleyip uygun bean'leri enjekte eder
2. Class Sınıfı — Her Şeyin Başlangıcı
Reflection'ın merkezi java.lang.Class<T> sınıfıdır. JVM'e yüklenen her sınıf için bir Class nesnesi oluşturulur. Bu nesne o sınıfın "meta bilgisi"ni — adını, alanlarını, metodlarını, constructor'larını — içerir.
Class Nesnesine Erişim (3 Yol)
// Yol 1: .class literal — Derleme zamanında tip biliniyorsa
Class<String> clazz1 = String.class;
// Yol 2: .getClass() — Elimizde nesne varsa
String text = "Merhaba";
Class<?> clazz2 = text.getClass();
// Yol 3: Class.forName() — Sınıf adı String olarak geliyorsa
Class<?> clazz3 = Class.forName("java.lang.String");
System.out.println(clazz1 == clazz2); // true
System.out.println(clazz2 == clazz3); // true — hepsi aynı Class nesnesiHer üç yol da aynı Class nesnesini döndürür — çünkü JVM her sınıf için tek bir Class nesnesi tutar.
Class'tan Temel Bilgiler
Class<?> clazz = ArrayList.class;
System.out.println(clazz.getName()); // java.util.ArrayList
System.out.println(clazz.getSimpleName()); // ArrayList
System.out.println(clazz.getPackageName()); // java.util
System.out.println(clazz.getSuperclass()); // class java.util.AbstractList
System.out.println(clazz.isInterface()); // false
System.out.println(clazz.isEnum()); // false
// Implement ettiği interface'ler
for (Class<?> iface : clazz.getInterfaces()) {
System.out.println("Interface: " + iface.getSimpleName());
}
// Interface: List
// Interface: RandomAccess
// Interface: Cloneable
// Interface: SerializableClass.forName() en güçlü yoldur çünkü sınıf adı runtime'da belirlenebilir — konfigürasyon dosyasından, veritabanından veya kullanıcı girdisinden okunabilir.
3. Field Erişimi — Alanları Okuma ve Yazma
Reflection ile bir sınıfın alanlarına — hatta private olanlara bile — erişebilirsiniz.
Alanları Keşfetme
class User {
private String name;
private int age;
public String email;
User(String name, int age, String email) {
this.name = name;
this.age = age;
this.email = email;
}
}Class<?> clazz = User.class;
// Sadece public alanlar (miras dahil)
Field[] publicFields = clazz.getFields();
// Tüm alanlar — private dahil (sadece bu sınıfta tanımlananlar)
Field[] allFields = clazz.getDeclaredFields();
for (Field field : allFields) {
System.out.printf("%-10s %-8s %s%n",
field.getName(),
field.getType().getSimpleName(),
Modifier.isPrivate(field.getModifiers()) ? "(private)" : "(public)");
}
// name String (private)
// age int (private)
// email String (public)getFields() ve getDeclaredFields() farkını iyi anlamak gerekir. getFields() sadece public alanları döndürür ama miras zincirindeki tüm sınıfları tarar. getDeclaredFields() ise o sınıfta tanımlanan tüm alanları (private dahil) döndürür ama miras alınanları göstermez.
Değer Okuma ve Yazma
User user = new User("Ahmet", 25, "ahmet@mail.com");
Class<?> clazz = user.getClass();
// Private alana erişim
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true); // private erişim engelini kaldır
// Okuma
String name = (String) nameField.get(user);
System.out.println("Mevcut isim: " + name); // Ahmet
// Yazma
nameField.set(user, "Mehmet");
String newName = (String) nameField.get(user);
System.out.println("Yeni isim: " + newName); // Mehmet⚠️ Dikkat: setAccessible(true) çağrısı Java'nın erişim kontrolünü devre dışı bırakır. Bu güçlü bir silahtır — framework'ler için gereklidir ama uygulama kodunda kullanmaktan kaçının. Modüler projede (JPMS), setAccessible(true) çalışması için ilgili paketin opens ile açılmış olması gerekir.
4. Method Erişimi — Metodları Çağırma
Reflection ile bir nesnenin metodlarını runtime'da keşfedip çağırabilirsiniz.
Metodları Keşfetme
class Calculator {
public int add(int a, int b) {
return a + b;
}
private int multiply(int a, int b) {
return a * b;
}
public String format(double value) {
return String.format("%.2f", value);
}
}Class<?> clazz = Calculator.class;
for (Method method : clazz.getDeclaredMethods()) {
String params = Arrays.stream(method.getParameterTypes())
.map(Class::getSimpleName)
.collect(Collectors.joining(", "));
System.out.printf("%s %s(%s)%n",
method.getReturnType().getSimpleName(),
method.getName(),
params);
}
// int add(int, int)
// int multiply(int, int)
// String format(double)Metod Çağırma (invoke)
Calculator calc = new Calculator();
Class<?> clazz = calc.getClass();
// Public metod çağırma
Method addMethod = clazz.getDeclaredMethod("add", int.class, int.class);
int result = (int) addMethod.invoke(calc, 10, 20);
System.out.println("10 + 20 = " + result); // 30
// Private metod çağırma
Method multiplyMethod = clazz.getDeclaredMethod("multiply", int.class, int.class);
multiplyMethod.setAccessible(true);
int product = (int) multiplyMethod.invoke(calc, 5, 6);
System.out.println("5 * 6 = " + product); // 30getDeclaredMethod() iki parametre alır: metod adı ve parametre tipleri. Parametre tipleri metod overloading durumunda hangi metodu kastettiğinizi belirler. invoke() ise ilk parametre olarak hedef nesneyi, sonrasında metodun parametrelerini alır.
Static Metod Çağırma
Static metodları çağırırken invoke()'un ilk parametresi null olur — çünkü static metodlar bir nesneye bağlı değildir.
Method valueOfMethod = Integer.class.getDeclaredMethod("valueOf", int.class);
Integer wrapped = (Integer) valueOfMethod.invoke(null, 42);
System.out.println(wrapped); // 425. Constructor Erişimi — Nesne Oluşturma
Reflection ile bir sınıfın constructor'larını keşfedip yeni nesneler oluşturabilirsiniz.
class Product {
private String name;
private double price;
public Product() {
this.name = "Bilinmiyor";
this.price = 0;
}
public Product(String name, double price) {
this.name = name;
this.price = price;
}
@Override
public String toString() {
return name + " - " + price + " TL";
}
}Class<?> clazz = Product.class;
// No-arg constructor ile nesne oluşturma
Constructor<?> noArgCtor = clazz.getDeclaredConstructor();
Product p1 = (Product) noArgCtor.newInstance();
System.out.println(p1); // Bilinmiyor - 0.0 TL
// Parametreli constructor ile nesne oluşturma
Constructor<?> paramCtor = clazz.getDeclaredConstructor(String.class, double.class);
Product p2 = (Product) paramCtor.newInstance("Laptop", 25000.0);
System.out.println(p2); // Laptop - 25000.0 TLConstructor'lar da getDeclaredConstructors() ile listelenebilir ve private constructor'lara setAccessible(true) ile erişilebilir. Singleton pattern'i test ederken veya framework'lerde bu yaygındır.
// Tüm constructor'ları listeleme
for (Constructor<?> ctor : clazz.getDeclaredConstructors()) {
String params = Arrays.stream(ctor.getParameterTypes())
.map(Class::getSimpleName)
.collect(Collectors.joining(", "));
System.out.println("Constructor(" + params + ")");
}
// Constructor()
// Constructor(String, double)6. Annotation Okuma
Reflection'ın en yaygın kullanım alanlarından biri annotation'ları okumaktır. Framework'ler genellikle sınıflara, alanlara veya metodlara annotation ekler ve runtime'da bunları reflection ile okur.
Custom Annotation Tanımlama
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME) // Runtime'da erişilebilir olsun
@Target(ElementType.FIELD) // Sadece alanlara uygulanabilir
@interface JsonField {
String name() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE) // Sadece sınıflara uygulanabilir
@interface Entity {
String table();
}@Retention(RetentionPolicy.RUNTIME) kritik önemdedir. Bu olmadan annotation derleme sonrası silinir ve reflection ile okunamaz.
Annotation Okuma
@Entity(table = "users")
class User {
@JsonField(name = "user_name")
private String name;
@JsonField
private int age;
private String internalId; // Bu alanda annotation yok
User(String name, int age) {
this.name = name;
this.age = age;
this.internalId = "INT-" + System.nanoTime();
}
}Class<?> clazz = User.class;
// Sınıf seviyesinde annotation kontrolü
if (clazz.isAnnotationPresent(Entity.class)) {
Entity entity = clazz.getAnnotation(Entity.class);
System.out.println("Tablo adı: " + entity.table()); // users
}
// Alan seviyesinde annotation okuma
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(JsonField.class)) {
JsonField jsonField = field.getAnnotation(JsonField.class);
String jsonName = jsonField.name().isEmpty()
? field.getName() // annotation'da isim yoksa alan adını kullan
: jsonField.name();
System.out.printf("Alan: %s → JSON: %s%n", field.getName(), jsonName);
}
}
// Alan: name → JSON: user_name
// Alan: age → JSON: ageBu pattern, Jackson'ın @JsonProperty veya JPA'nın @Column annotation'larının çalışma prensibinin temelidir. Framework sınıfın alanlarını tarar, annotation'ları okur ve ona göre davranır.
7. Mini Framework Örneği: Basit JSON Serializer
Şimdiye kadar öğrendiklerimizi birleştirerek basit bir JSON serializer yazalım. Bu örnek, Jackson veya Gson gibi kütüphanelerin perde arkasında neler yaptığının küçük bir demosu.
import java.lang.annotation.*;
import java.lang.reflect.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface JsonField {
String name() default "";
}
class SimpleJsonSerializer {
static String toJson(Object obj) throws Exception {
Class<?> clazz = obj.getClass();
StringBuilder json = new StringBuilder("{");
boolean first = true;
for (Field field : clazz.getDeclaredFields()) {
if (!field.isAnnotationPresent(JsonField.class)) continue;
field.setAccessible(true);
JsonField annotation = field.getAnnotation(JsonField.class);
String key = annotation.name().isEmpty()
? field.getName()
: annotation.name();
Object value = field.get(obj);
if (!first) json.append(", ");
first = false;
json.append("\"").append(key).append("\": ");
if (value instanceof String) {
json.append("\"").append(value).append("\"");
} else {
json.append(value);
}
}
json.append("}");
return json.toString();
}
}class Employee {
@JsonField(name = "full_name")
private String name;
@JsonField
private int age;
@JsonField(name = "department")
private String dept;
private double secretSalary; // annotation yok — serialize edilmeyecek
Employee(String name, int age, String dept, double salary) {
this.name = name;
this.age = age;
this.dept = dept;
this.secretSalary = salary;
}
}
// Kullanım
Employee emp = new Employee("Ayşe Yılmaz", 30, "Mühendislik", 45000);
String json = SimpleJsonSerializer.toJson(emp);
System.out.println(json);
// {"full_name": "Ayşe Yılmaz", "age": 30, "department": "Mühendislik"}secretSalary alanı @JsonField annotation'ına sahip olmadığı için JSON çıktısında yer almaz. İşte gerçek JSON kütüphaneleri de temelde bu mantıkla çalışır — tabii çok daha fazla edge case, performans optimizasyonu ve tip desteğiyle.
8. Framework'ler Neden Reflection Kullanır?
Spring, Hibernate, Jackson, JUnit — Java ekosisteminin neredeyse tüm büyük framework'leri reflection'a dayanır. Bunun temel sebebi framework'ün kullanıcı kodunu önceden bilmemesidir.
Spring — Dependency Injection
@Service
class UserService {
private final UserRepository repository;
@Autowired
UserService(UserRepository repository) { // Spring bunu reflection ile bulur
this.repository = repository;
}
}Spring başlatıldığında şunları yapar:
Classpath'i tarar,
@Service,@Componentgibi annotation'lı sınıfları bulurHer sınıfın constructor'ını reflection ile inceler
Constructor parametrelerinin tiplerini belirler
Uygun bean'leri bulup
newInstance()ile nesne oluşturur
JPA/Hibernate — ORM Mapping
@Entity
@Table(name = "products")
class Product {
@Id
@GeneratedValue
private Long id;
@Column(name = "product_name")
private String name;
}Hibernate, Product sınıfını reflection ile inceler: @Entity annotation'ını görür, alanları tarar, @Column annotation'larını okur ve SQL sorguları oluşturur. Private alanları setAccessible(true) ile okur/yazar.
Jackson — JSON Serialization
class ApiResponse {
@JsonProperty("status_code")
private int statusCode;
@JsonIgnore
private String internalToken;
}Jackson, sınıfın alanlarını reflection ile tarar, annotation'ları okur ve JSON dönüşümünü buna göre yapar. @JsonIgnore olan alanı atlar, @JsonProperty ile isim değişikliği uygular.
9. Performans ve Güvenlik
Reflection güçlüdür ama bedavaya gelmez.
Performans Maliyeti
Reflection ile yapılan işlemler doğrudan kod çağrısından önemli ölçüde yavaştır. Bunun birkaç sebebi var:
JVM'in erişim kontrolü yapması ve devre dışı bırakması gerekir
Metod çağrıları JIT derleyici tarafından optimize edilemez
Parametre tipleri runtime'da kontrol edilir (boxing/unboxing dahil)
// Direkt çağrı — JIT optimize eder, nanosaniyeler
calculator.add(10, 20);
// Reflection — her çağrıda tip kontrolü, erişim kontrolü
Method m = Calculator.class.getDeclaredMethod("add", int.class, int.class);
m.invoke(calculator, 10, 20); // ~10-50x daha yavaşBu yüzden framework'ler genellikle reflection'ı başlangıçta bir kez kullanır: sınıf yapısını inceler, meta bilgiyi cache'ler ve sonraki işlemlerde cache'ten okur. Spring'in başlangıç süresinin uzun olmasının bir sebebi de budur — ama bir kez başladıktan sonra reflection maliyeti minimize edilir.
Güvenlik Endişeleri
setAccessible(true) Java'nın temel güvenlik mekanizmalarından olan erişim kontrolünü devre dışı bırakır. Bu, kötü amaçlı kodun private alanlara erişmesine olanak tanır.
Java 9'dan itibaren JPMS bu sorunu kısmen çözer: modüler projede setAccessible(true) sadece opens ile açılmış paketlerde çalışır. Bu ek güvenlik katmanı, reflection'ın kontrolsüz kullanımını engeller.
// JPMS ile — modül opens yapmamışsa
field.setAccessible(true);
// InaccessibleObjectException: module X does not opens package Y💡 İpucu: Uygulama kodunda reflection'dan mümkün olduğunca kaçının. Interface, abstract class ve polimorfizm çoğu durumda reflection'a ihtiyaç duymadan aynı esnekliği sağlar. Reflection, framework ve kütüphane geliştiricileri için bir araçtır.
10. Dynamic Proxy — Çalışma Zamanında Interface Implementasyonu
java.lang.reflect.Proxy sınıfı, çalışma zamanında bir interface'i implemente eden yeni bir sınıf oluşturmanıza olanak tanır. Bu, AOP (Aspect-Oriented Programming), loglama, caching ve transaction yönetimi gibi cross-cutting concern'ler için kullanılır.
Temel Kullanım
import java.lang.reflect.*;
interface Greeter {
String greet(String name);
String farewell(String name);
}// InvocationHandler — tüm metod çağrılarını yakalar
InvocationHandler handler = (proxy, method, args) -> {
System.out.println("[LOG] " + method.getName() + " çağrıldı");
return switch (method.getName()) {
case "greet" -> "Merhaba, " + args[0] + "!";
case "farewell" -> "Güle güle, " + args[0] + "!";
default -> throw new UnsupportedOperationException();
};
};
// Proxy oluştur
Greeter greeter = (Greeter) Proxy.newProxyInstance(
Greeter.class.getClassLoader(),
new Class<?>[] { Greeter.class },
handler
);
System.out.println(greeter.greet("Ayşe"));
System.out.println(greeter.farewell("Mehmet"));Çıktı:
[LOG] greet çağrıldı
Merhaba, Ayşe!
[LOG] farewell çağrıldı
Güle güle, Mehmet!Gerçekçi Örnek: Loglama Proxy'si
Pratikte dynamic proxy genellikle mevcut bir implementasyonu sarmalar (wrap eder):
interface UserService {
String findUser(int id);
void deleteUser(int id);
}
class UserServiceImpl implements UserService {
public String findUser(int id) {
return "User-" + id;
}
public void deleteUser(int id) {
System.out.println("Kullanıcı " + id + " silindi");
}
}class LoggingProxy implements InvocationHandler {
private final Object target;
LoggingProxy(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long start = System.nanoTime();
System.out.printf("[BEFORE] %s(%s)%n", method.getName(), Arrays.toString(args));
Object result = method.invoke(target, args); // Gerçek metodu çağır
long elapsed = (System.nanoTime() - start) / 1_000_000;
System.out.printf("[AFTER] %s → %dms%n", method.getName(), elapsed);
return result;
}
@SuppressWarnings("unchecked")
static <T> T create(T target, Class<T> iface) {
return (T) Proxy.newProxyInstance(
iface.getClassLoader(),
new Class<?>[] { iface },
new LoggingProxy(target)
);
}
}UserService service = LoggingProxy.create(new UserServiceImpl(), UserService.class);
service.findUser(42);
// [BEFORE] findUser([42])
// [AFTER] findUser → 0ms
service.deleteUser(7);
// [BEFORE] deleteUser([7])
// Kullanıcı 7 silindi
// [AFTER] deleteUser → 0msBu pattern'in gücü, loglama kodunun UserServiceImpl içinde olmamasıdır. Aynı LoggingProxy'yi herhangi bir interface implementasyonuna uygulayabilirsiniz — kod tekrarı sıfır. Spring AOP'nin @Transactional, @Cacheable gibi annotation'ları perde arkasında benzer proxy mekanizmaları kullanır.
⚠️ Dikkat: java.lang.reflect.Proxy sadece interface'ler için çalışır, concrete sınıflar için değil. Sınıf bazlı proxy'ler için CGLIB veya ByteBuddy gibi kütüphaneler kullanılır — Spring bunları tercih eder.
11. Reflection ile Yapılan Yaygın Hatalar
Hata 1: NoSuchMethodException
// Yanlış — parametre tipleri eşleşmiyor
Method m = clazz.getDeclaredMethod("add", Integer.class, Integer.class);
// NoSuchMethodException! Metod int alıyor, Integer değil.
// Doğru — primitive tipler için .class kullan
Method m = clazz.getDeclaredMethod("add", int.class, int.class);Primitive tipler (int, double, boolean) ile wrapper tipler (Integer, Double, Boolean) reflection'da farklı tiplerdir. Metod int alıyorsa int.class kullanmalısınız.
Hata 2: setAccessible Unutma
Field field = clazz.getDeclaredField("name");
// field.setAccessible(true); ← Bunu unuttun
String value = (String) field.get(obj);
// IllegalAccessException: class Main cannot access private fieldHata 3: Static ve Instance Karışıklığı
// Static metod — invoke'un ilk parametresi null
Method staticMethod = Math.class.getMethod("abs", int.class);
staticMethod.invoke(null, -5); // 5
// Instance metod — invoke'un ilk parametresi nesne
Method instanceMethod = String.class.getMethod("length");
instanceMethod.invoke("hello"); // 5
instanceMethod.invoke(null); // NullPointerException!Özet
Reflection bir Java programının çalışma zamanında kendi yapısını incelemesi ve değiştirmesidir —
Class,Field,Method,Constructorsınıfları temel araçlarıdır.`Class` nesnesine
.class,.getClass()veyaClass.forName()ile erişilir — her sınıf için JVM'de tek bir Class nesnesi bulunur.`setAccessible(true)` private üyelere erişimi açar ama güvenlik risklidir — JPMS'te
opensdirektifi olmadan çalışmaz.Annotation okuma reflection'ın en yaygın kullanım alanıdır — framework'ler (
@Autowired,@Entity,@JsonProperty) bu sayede çalışır.Dynamic Proxy ile runtime'da interface implementasyonları oluşturulur — AOP, loglama ve transaction yönetimi için temeldir.
Performans maliyeti yüksektir (10-50x yavaş) — framework'ler bunu başlangıçta bir kez yapıp cache'leyerek minimize eder; uygulama kodunda reflection'dan kaçının.
AI Asistan
Sorularını yanıtlamaya hazır