REFLECTION
Reflection API, aslında javada bir class’taki methodlara ve fieldlara erişebildiğimiz bir API olarak karşımıza çıkar. Ama aslında sadece bu kadarla sınırlı değil.
Class’larla ve field’larla alakalı her türlü bilgiyi edindiğimiz bir yapı reflectionlar. java.lang.reflect paketi ile reflection api’sini dilediğimiz gibi kullanabiliriz.
public class ReflectionClass {
private int privateField;
public String publicField;
protected long protectedField;
public final int finalField = 10;
public static String STATIC_FIELD;
private static final String STATIC_FINAL_FIELD = "Static and final field";
}
Yukarıda farklı tiplerde fieldlar mevcut. Ve class’ımız da public olarak imlenmiş olsun diyelim. Bu class’ın hem kendisine hem de field’larına rahat rahat ulaşabiliriz ve bu field’larla ve class’larla alakalı özellikleri ekranda görebiliriz.
Class’ımızın adına erişelim.
ReflectionClass reflectionClass = new ReflectionClass();
Class<?> myClass = reflectionClass.getClass();
String className = myClass.getName();
System.out.println("ClassName : " + className);
Yukarıdaki kod blocku ile classımızdan oluşturduğumuz bir instance ile class’ımızın adını görebiliriz.
- Önce ilgili class’ımızdan bir instance oluşturduk.
- Oluşturduğumuz instance üzerinden getClass() methoduyla classımıza eriştik. (Class tipinde myClass)
- myClass değişkenimiz üzerinden de classımızın adına ulaştık.
Yukarıdaki adımlar aslında çok da efektif değil. IDE sonarlint yardımıyla burada “Yalnızca getClass’ı çağırmak amacıyla bir nesne oluşturmak, bellek ve döngü israfıdır. Bunun yerine, sınıfın .class özelliğini kullanmanız yeterlidir.” şeklinde bir uyarı verir.
Class<?> myClass = ReflectionClass.class;
String className = myClass.getName();
System.out.println("ClassName : " + className);
Yukarıdaki yöntem bir önceki yönteme göre daha efektiftir. Burada direkt olarak class’ımız üzerinden istediğimiz değerlere ulaşabiliyoruz.
Bir başka yöntem de aşağıdaki gibi olabilir.
Class<?> myClass = Class.forName("reflection.ReflectionClass");
System.out.println(myClass.getName());
Yukarıdaki kod blockunda erişmek istediğimiz sınıfı paketi ile verirsek, aynı şekilde istediğimiz bilgilere erişebiliriz. Bu kod throws ile bir istisna fırlatmaya yönelik methodun yanına ClassNotFoundException ekler.
Yukarıdaki üç kodun da çıktısı aslında aynı olacaktır.
ClassName : reflection.ReflectionClass
ClassName : reflection.ReflectionClass
ClassName : reflection.ReflectionClass
NOT: Class<?> ifadesi ile Class<Object> ifadesi aslında birbirleriyle yakın anlam ifade eder. ? generic bir kullanım olduğunu, buraya herhangi bir class’ın gelebileceğini ifade eder. Ve Object kullanımına göre daha esnek bir yapıya sahiptir.
Class’ımızın constructor’larına erişelim.
Biz biliyoruz ki, javada bir class oluşturulduğu zaman biz yazsak da yazmasak da aslında boş bir tane constructor arka tarafta oluşturulur. Bunu reflection ile kanıtlayalım.
Java’da reflect paketinin altında Constructor adında bir class vardır. Bu class yardımıyla kanıtımızı yapabiliriz.
Yukarıda yazının ilk başında oluşturduğumuz ReflectionClass’ı baz alacak olursak, şu anki haliyle herhangi bir constructor’a sahip değil. Ama biz biliyoruz ki, bir tane parametre almayan constructor’u kendi içerisinde oluşturdu.
Constructor<?>[] constructors = myClass.getConstructors();
Arrays.stream(constructors).forEach(System.out::println);
getConstructros() methodu Constructor tipinde bir array döner. Yukarıda, bu array’i bir stream’e alıp yazdırırsak,
public reflection.ReflectionClass()
yukarıdaki outputu görürüz. public ve method parametre almamış. ((:
Peki class’ımıza bir kaç farklı constructor ekleyelim.
public class ReflectionClass {
private int privateField;
public String publicField;
protected long protectedField;
public final int finalField = 10;
public static String STATIC_FIELD;
private static final String STATIC_FINAL_FIELD = "Static and final field";
public ReflectionClass() {
}
public ReflectionClass(int privateField, String publicField, long protectedField) {
this.privateField = privateField;
this.publicField = publicField;
this.protectedField = protectedField;
}
public ReflectionClass(int privateField) {
this.privateField = privateField;
}
public ReflectionClass(String publicField) {
this.publicField = publicField;
}
public ReflectionClass(int privateField, String publicField) {
this.privateField = privateField;
this.publicField = publicField;
}
}
Class’ımıza üç farklı constructor ekledik. Peki bu constructor’ları ekranda Stream yardımıyla yazdırmayı denesek nasıl bir çıktı alırız.?
public reflection.ReflectionClass(int,java.lang.String)
public reflection.ReflectionClass(java.lang.String)
public reflection.ReflectionClass(int)
public reflection.ReflectionClass(int,java.lang.String,long)
Yukarıdaki output bizlere aldıkları parametrelere göre constructor’ların hepsini geri döndü.
Class’ımıza iki parametre alan bir constructor daha ekleyelim.
public ReflectionClass(int privateField, long protectedField) {
this.privateField = privateField;
this.protectedField = protectedField;
}
Yani son olarak bizim class’ımız aslında şu şekli almış olacak.
public class ReflectionClass {
private int privateField;
public String publicField;
protected long protectedField;
public final int finalField = 10;
public static String STATIC_FIELD;
private static final String STATIC_FINAL_FIELD = "Static and final field";
public ReflectionClass() {
}
public ReflectionClass(int privateField, String publicField, long protectedField) {
this.privateField = privateField;
this.publicField = publicField;
this.protectedField = protectedField;
}
public ReflectionClass(int privateField) {
this.privateField = privateField;
}
public ReflectionClass(String publicField) {
this.publicField = publicField;
}
public ReflectionClass(int privateFinal, String publicField) {
this.privateFinal = privateFinal;
this.publicField = publicField;
}
public ReflectionClass(int privateFinal, long protectedField) {
this.privateFinal = privateFinal;
this.protectedField = protectedField;
}
}
Peki biz bu constructor’lar arasından iki parametre alan constructor’ları filtrelemek istersek ne yapabiliriz.?
Arrays
.stream(constructors)
.filter(item -> item.getParameterCount() == 2)
.forEach(System.out::println);
constructors değişkenimizi Stream içerisine alıp filter ile parameterCount() methodu yardımıyla parameterCount() sayısı 2 olanı filtrelersek elimizde sadece iki tane parametre alan constructor’lar kalmış olur.
Ya da yukarıda yaptığımız örneğin bir benzerini yapmaya çalışalım. Örneğin, tek parametre alan constructorların aldığı parametrelerin tiplerini ekrana yazdıralım. Yine stream’leri kullanalım.
Arrays
.stream(constructors)
.filter(item -> item.getParameterCount() == 1)
.forEach(c -> {
Arrays
.stream(c.getParameterTypes())
.forEach(System.out::println);
});
Yukarıdaki kod blockunda önce stream ile tüm constructor’ları stream’e aldık. Sonrasında filter yardımıyla tek parametre alan constructor’ları filtreledik. Aslında filtreden geçen constructor’lar geriye yine bir constructor arrayi döndü. Ve bu dönen array üzerinden parameterType’larını alıp forEach methoduyla ekrana bastırdık. Çıktı aşağıdaki gibi olacaktır.
class java.lang.String
int
Class’ımızın field’larına erişelim.
Class’ımızda 6 tane field bulunmakta.
publicField’a erişmeye çalışalım.
Bunun için yine reflect paketinin altında bulunan Field classını kullanmamız gerekir.
Field myPublicField = myClass.getField("publicField");
System.out.println(myPublicField.getName());
Yukarıda oluşturduğumuz myClass değişkeni üzerinden getField() methodu yardımıyla istediğimiz field’a ulaştık. Dikkat edilmesi gereken nokta getField() methoduna ulaşmak istediğimiz field’ın adını vermemiz gerekir. Field’ımıza ulaştıktan sonra (myPublicField) bu field’ın adını ekranda publicField olarak görebiliriz. Bir diğer husus da getField() methodunu kullanmak istediğimizde NoSuchFieldException istisnasını throws olarak methodumuza ekler.
Java’da reflect paketinin altında bir de getDeclaredField() adında başka bir method daha vardır.
Field myPublicField = myMainClass.getField("publicField");
System.out.println(myPublicField.getName());
Field myPublicField2 = myMainClass.getDeclaredField("publicField");
System.out.println(myPublicField2.getName());
Yukarıdaki kod blocku aynı çıktıyı verecektir. output olarak,
publicField
publicField
outputunu görürüz. İlk bakışta aynı gibi gözükselerde aslında bu methodlar birbirlerinden farklıdırlar.
getField() methodu sadece public field’lar için kullanılırken, getDeclaredField() methodu class’taki tüm field’lar için kullanılabilir.
Bunu bir örnekle açıklamak gerekirse,
Field myPrivateField1 = myMainClass.getField("privateField");
System.out.println(myPrivateField1.getName());
Yukarıdaki kod blocku hata verecektir, çünkü getField() yordamıyla private bir alana ulaşmaya çalıştık.
Fakat, aynı private field’a getDeclaredField() yöntemi ile ulaşmaya çalışırsak eğer herhangi bir hata almadan private field’ımıza ulaşabiliriz.
Field’ımızın tipine de çok rahat bir şekilde ulaşabiliriz.
Field myPublicField = myMainClass.getDeclaredField("publicField");
System.out.println(myPublicField.getType().getName());
myPublicField üzerinden, getType() methoduna ulaşıp getName() yardımıyla class’ımızda bulunan publicField field’ımızın tipine ulaşabiliriz.
diğer field’lara da aynı şekilde ulaşabiliriz.
Peki, field tanımında bir değere ulaşmak istersek bunu nasıl yapabiliriz.?
ReflectionClass clazz = new ReflectionClass();
Field myPublicAndFinalField = clazz.getClass().getDeclaredField("finalField");
var result = myPublicAndFinalField.get(clazz);
System.out.println(result);
Yukarıda konunun ilk başında gördüğümüz yöntemlerle ilerleyecek olursak,
- Önce ilgili classımızdan bir instance oluşturduk.
- Sonra olışturduğumuz bu instance üzerinden getClass() methodunun getDeclaredField() yardımıyla istediğimiz public field’a eriştik.(Burada erişmek istediğimiz field public olduğu için getField() methodunu da kullanabilirdik.) Ve eriştiğimiz field’ı Field tipinde bir değişken olarak tanımladık.
- var keywordü yardımıyla bu field’ımızı aldık ve get() methodunun içerisine ilk adımda oluşturduğumuz instance’ı parametre olarak verdik. Ve elde ettiğimiz değişkeni result olarak adlandırdık.
- Akabinde System.out.println() içerisine result değerimizi verdik. Sonuç ise tabiiki 10 olarak karşımıza çıktı.
Peki, şu an public bir field’a ulaştık. Ya private bir field’a ulaşmaya çalışsaydık.?
Biz biliyoruz ki, get() ve set() methodları olmadan classın dışından private field’lara ulaşamayız.
Ama reflection ile bu da mümkün. Bunun için ReflectionClass adlı classımıza gidip
private String privateAndInitializedField = "Turkey Java Community";
şeklinde bir field tanımlayalım. Ve bu field’ın değerine erişmeye çalışalım. Bunun için aşağıdaki kod blockunu kullanabiliriz.
Class<?> myMainClass = Class.forName("reflection.ReflectionClass");
Object myNewInstance = myMainClass.newInstance();
Field privateAndInitializedField = myNewInstance
.getClass()
.getDeclaredField("privateAndInitializedField");
privateAndInitializedField.setAccessible(true);
var res = privateAndInitializedField.get(newInstance);
System.out.println(res);
- Classımıza Class.forName() methoduyla ulaştık ve bunu myMainClass adlı Class tipinde bir değişkene atadık.
- myMainClass adlı değişkenimiz üzerinden newInstance() methodu yardımıyla yeni bir instance ürettik. (newInstance() methodu ilgili class’tan bir tane constructor üretmemize yardımcı olur.) Ve ürettiğimiz bu instance’ı myNewInstance adlı değişkene atadık.
- myNewInstance üzerinden önce getClass() diyerek classımıza, akabinde ise getDeclaredField() methoduna erişmek istediğimiz field’ın adını yazdık. (Unutmayalım ki, erişmek istediğimiz field private ve bu yüzden getField() yerine getDeclaredField() yapısını kullandık.) Fieldımıza eriştik ve eriştiğimiz bu field’ı Field tipinde bir değişkene (privateAndInitializedField) atadık.
- privateAndInitializedField değişkeninde, bizim private bir field’ımız var. Reflection ile field’ımıza bir şekilde ulaştık ama, bu field hala private. Bunun accessible flag’ini true’ya çekmemiz lazım. Bu yüzden setAccessible() methodu yardımıyla field’ımızın erişilebilirliğini true yaptık.
- Artık private field’ımıza erişebiliyoruz ve bu field’ın değerini okuyabiliriz. get() methodu yardımıyla var keyword’üne ilgili değerimizi atadık.
- System.out.println(res) ‘ de ise tabiiki “Turkiye Java Community” değerini göreceğiz.
Peki, field’ımızın değerini değiştirebilir miyiz.? Elbette.
privateAndInitializedField.set(newInstance, "Furkan Sahin Kulaksiz");
res = privateAndInitializedField.get(newInstance);
System.out.println(res);
Buradaki tek husus, eriştiğimiz privateAndInitializedField değişkenine set() methodu yardımıyla istediğimiz değeri atamak. Sonuç ise elbette “Furkan Sahin Kulaksiz”
Field’larımızın başka hangi özelliklerine erişebiliriz.?
Aslında field’larımızla alakalı bir çok bilgiyi edinebiliriz.
Örn;
Field staticAndFinalField = myMainClass.getDeclaredField("STATIC_FINAL_FIELD");
System.out.println(Modifier.isFinal(staticAndFinalField.getModifiers()));
Yukarıdaki kodda önce STATIC_FINAL_FIELD adındaki field’ımıza ulaştık ve bu field’ımız üzerinden Modifier class’ı yardımıyla final olup olmadığını bulan kodu yazdık.
Modifier.isFinal() -> bu field final ‘ mi.?
şeklinde düşünebiliriz.
Bir ipucu olarak, JDK’da is ile başlayan methodlar boolean değer dönderirler. Örn: isFinal(), isEmpty(), isNotEmpty() … gibi. Bu methodları “final mi, empty mi, empty değil mi” şeklinde yorumlayabiliriz. (En azından ben farklı bir durumla karşılaşmadım.) Bu soruların cevapları da true — false olarak karşımıza çıkar. Bu yüzden Modifier.isFinal() sorusunun cevabı bize true olarak dönecektir.
Aşağıdkai kod blockunu dikkatli bir şekilde inceleyecek olursak,
Field[] myAllFields = ReflectionClass.class.getDeclaredFields();
for (Field item : myAllFields) {
System.out.printf("""
Field Name : %s
Field Type : %s
isFinal : %s
isStatic : %s
isPublic : %s
isPrivate : %s
isProtected : %s
isNative : %s
%n""",
item.getName(),
item.getAnnotatedType().getType(),
Modifier.isFinal(item.getModifiers()),
Modifier.isStatic(item.getModifiers()),
Modifier.isPublic(item.getModifiers()),
Modifier.isPrivate(item.getModifiers()),
Modifier.isProtected(item.getModifiers()),
Modifier.isNative(item.getModifiers())
);
}
Yukarıdaki kod blockunda önce Field tipinde bir Field Array tanımladık. class üzerinden erişebildiğimiz getDeclaredFields() methodu geriye bir Field array dönderir. Elimizdeki myAllFields arrayini basitçe bir forEach döngüsü yardımıyla döndük. Ve burada daha temiz bir kod yapısı için java 15 ile hayatımıza giren text blockları yardımıyla tüm field’larımızın özelliklerini ekrana bastırdık.
Çıktı ise aşağıdaki gibi olacaktır.
Field Name : privateField
Field Type : int
isFinal : false
isStatic : false
isPublic : false
isPrivate : true
isProtected : false
isNative : false
Field Name : publicField
Field Type : class java.lang.String
isFinal : false
isStatic : false
isPublic : true
isPrivate : false
isProtected : false
isNative : false
Field Name : protectedField
Field Type : long
isFinal : false
isStatic : false
isPublic : false
isPrivate : false
isProtected : true
isNative : false
Field Name : finalField
Field Type : int
isFinal : true
isStatic : false
isPublic : true
isPrivate : false
isProtected : false
isNative : false
Field Name : STATIC_FIELD
Field Type : class java.lang.String
isFinal : false
isStatic : true
isPublic : true
isPrivate : false
isProtected : false
isNative : false
Field Name : STATIC_FINAL_FIELD
Field Type : class java.lang.String
isFinal : true
isStatic : true
isPublic : false
isPrivate : true
isProtected : false
isNative : false
Classımızın methodlarına erişelim.
Reflection ile class’ımızdaki methodlara da erişim mümkündür.
Örneğin ReflectionClass adlı class’ımıza bir tane void method ekleyelim.
public void helloReflection() {
System.out.println("Hello Reflection");
}
Yukarıda çok basit bir method var, peki biz bu methoda erişmek istersek nasıl bir yol izlemeliyiz.?
Method myPublicAndVoidMethod = ReflectionClass
.class
.getMethod("helloReflection");
System.out.println("Method Name : " + myPublicAndVoidMethod.getName());
Yukarıdaki kodda önce ReflectionClass adlı classımızın üzerinden .class yardımıyla getMethod() adlı method ile methodumuza ulaştık. getMethod() adlı methodumuza ise parametre olarak methodumuzun adı olan helloReflection adını parametre olarak verdik. Ve bunu Method classı tipinde myPublicAndVoidMethod adlı değişkene atadık. Akabinde System.out.println içerisinde myPublicAndVoidMethod değişkenimiz üzerinden methodumuzun adına getName() methoduyla ulaşık. Burada dikkat etmemiz gereken husus ise getMethod() adlı method kontrollü bir istisna olarak NoSuchMethodException fırlatabileceği için bu exceptionu throws ile ekledik.
Peki methodumuzu nasıl çalştıracağız.?
Method myPublicAndVoidMethod = ReflectionClass
.class
.getMethod("helloReflection");
ReflectionClass reflectionClassInstance = ReflectionClass.class.newInstance();
myPublicAndVoidMethod.invoke(reflectionClassInstance);
- Önce classımıza, sonrasında methodumuza ulaştık.
- Ulaştığımız class’tan bir instance üretmemiz gerekir ki bunu da yine ReflectionClass adlı class’ımız üzerinden newInstance() methodu yardımıyla bunu gerçekleştirdik.
- Eriştiğimiz method üzerinden invoke() methodunu çağırdık ve bu invoke() methoduna da parametre olarak ürettiğimiz instance’ı verdik. Sonuç olarak ise ekranda Hello Reflection yazısını gördük.
Peki methodumuz parametre alıyor olsaydı ne yapacaktık.?
Method myMethod = ReflectionClass
.class
.getMethod("helloReflection",int.class,int.class,String.class);
Object[] myParameters = {2, 3, "Toplama"};
ReflectionClass reflectionClassInstance = ReflectionClass.class.newInstance();
myMethod.invoke(reflectionClassInstance,myParameters);
- Önce methodumuza erişiyoruz. Ama buradaki farklılık şu, getMethod() methodu 2 farklı parametre alır. Birinci parametre olarak classın ismini alır, ikinci parametre olarak varargs ile methodun aldığı parametrelerin tipini alır. Bizim erişmek istediğimiz method iki tane int tipinde bir tane de String tipinde parametre almakta. Bu tipleri .class diyerek parametre olarak verdik. Ki burada da aslında şunu anlıyoruz ki; bizim methodumuz ister primitive tipte bir parametre alsın ister JDK’da tanımlı bir classı parametre alsın isterse de kendi oluşturduğumuz bir class tipinde parametre alsın bizim tek yapmamız gereken parametre aldığı tipi .class uzantısıyla birlikte getMethod() methoduna parametre olarak vermek.
- Methodumuzun aldığı parametreler tipinde bir Object dizisi oluşturarak istediğimiz parametreleri bu arraye verdik.
- Class’ımızdan bir instance ürettik. (Örneği iyi bir şekilde anlatmak açısından burada yeniden bir instance oluşturdum. Instance’ı bir kere oluşturmak yeterli olacaktır. Diğer işlemlerimizi ilgili instance üzerinden yapabiliriz.)
- En sonunda eriştiğimiz ve myMethod değişkenine attığımız methodumuzu invoke() methodu ile çağırıp, parametre olarak instance’ımızı ve parametrelerimizi verdik.
Sonuç ise Toplama : 5 olarak karşımıza çıktı.
Peki methodumuz geriye değer döndürseydi.??
Reflection class’ımızın içerisine parametre alan ve geriye değer döndüren bir method tanımı yapalım.
public String myReturnMethod(String name) {
return name + " is co-founder of Turkey Java Community";
}
Bu methodumuza erişmek için gerekli olan adımlar ise, bir önceki yöntemle tamamen aynı.
Dikkat etmeniz gereken nokta;
Kodun içerisinde Methodlara Erişim adlı bir başlık açtım ve bu başlığın altında generalInstance tanımladım. Methodlara erişim başlığı altında yapacağım bütün örneklerde bu instance’ı kullanacağım.
Method myReturnMethod = ReflectionClass
.class
.getMethod("myReturnMethod", String.class);
var resultReturnMethod = myReturnMethod.invoke(generalInstance, "Furkan");
System.out.println(resultReturnMethod);
Yukarıdaki kodda, bir önceki örnekten farklı gelebilecek sadece 2 tane yapı var.
- Bir önceki örnekte parametrelerimizi Object tipinde bir diziye verip bu diziyi invoke() methodunda kullanmıştık. Bu örnekte ise kullanmak istediğimiz parametreyi direkt olarak invoke() methoduna geçtik.
- İkinci olarak ise methodumuz geriye bir değer döndüreceği için bu methodu var keywordü ile bir değişkene parametre olarak atadık ve bu değişken üzerinden çıktımıza eriştik.
Peki methodumuz private olsaydı.?
ReflectionClass adlı classımızın içerisine aşağıdaki gibi private olan ve geriye değer döndüren bir method tanımlayalım.
private int myPrivateMethod(int a, int b) {
return a + b;
}
Aslında durum önceki örneklerle tamamen aynı. Sadece iki tane farklılık var.
Method myPrivateMethod = ReflectionClass
.class
.getDeclaredMethod("myPrivateMethod", int.class, int.class);
myPrivateMethod.setAccessible(true);
var resultPrivateMethod = myPrivateMethod.invoke(generalInstance, 7,6);
System.out.println("Result : " + resultPrivateMethod);
- Önce methodumuza getDeclaredMethod() adlı method ile eriştik. Çünkü bu method private bir method ve getMethod() sadece public methodlara erişebilirken getDeclaredMethod() bütün methodlara erişebilir. Akabinde, aldığı parametreleri int.class, int.class şeklinde geçtik.
- Methodumuz private olduğu için setAccessible özelliğine true ekledik.
Peki bir method, başka bir class’tan override edilmişse.?
Örneğin javadaki tüm class’lar Object class’ından türetilmiştir ve toString() methodu da Object class’ının bir methodudur.
@Override
public String toString() {
return "This method is override method";
}
ReflectionClass adlı classımız toString() methodunu override etmiş olsun.
Method myOverrideMethod = ReflectionClass.class.getDeclaredMethod("toString");
Class<?> subClass = myOverrideMethod.getDeclaringClass();
Class<?> superClass = subClass.getSuperclass();
System.out.println(superClass.getName());
- Önce methodumuza ulaştık.
- Methodumuz üzernden class’ımıza eriştik.
- Eriştiğimiz class üzerinden getSuperClass() methodu yardımıyla da üst classımıza eriştik.
Yukarıdaki adımlardan sonra java.lang.Object olarak çıktıyı görürüz.
ReflectionClass adlı class’ımıza daha farklı methodlar ekleyip bu sefer bütün methodların yapılarına bakalım.
public void method1() {
System.out.println("Public Void Method1");
}
public static void method2() {
System.out.println("Public Static Void method2");
}
public static final String method3() {
return "Public Static Final Method";
}
private final int method4() {
return 0;
}
protected static long method5() {
return 0L;
}
Yukarıdaki methodlarımızı ReflectionClass adlı classımıza ekledik.
Şimdi bu methodların özelliklerine genel hatlarıyla bir bakalım.
Method[] allMethods = ReflectionClass.class.getDeclaredMethods();
for (Method method : allMethods) {
System.out.printf("""
Method Name : %s
Declaring Class : %s
Parameter Counts : %s
Parameter Types : %s
Return Type : %s
isNative : %s
isFinal : %s
isStatic : %s
isPublic : %s
isPrivate : %s
isProtected : %s
%n%n""",
method.getName(),
method.getDeclaringClass(),
method.getParameterCount(),
Arrays.stream(method.getParameterTypes()).toList(),
method.getReturnType(),
Modifier.isNative(method.getModifiers()),
Modifier.isFinal(method.getModifiers()),
Modifier.isStatic(method.getModifiers()),
Modifier.isPublic(method.getModifiers()),
Modifier.isPrivate(method.getModifiers()),
Modifier.isProtected(method.getModifiers()));
}
Önce Method tipinde bir array oluşturduk ve getDeclaredMethods() yardımıyla class’ımızdaki bütün methodları arrayimizin içerisine atadık. Akabinde text blockları yardımıyla bütün methodlarımızın özelliklerine tek bir yerden baktık. Çıktı aşağıdaki gibi olacaktır.
Method Name : toString
Declaring Class : class reflection.ReflectionClass
Parameter Counts : 0
Parameter Types : []
Return Type : class java.lang.String
isNative : false
isFinal : false
isStatic : false
isPublic : true
isPrivate : false
isProtected : false
Method Name : helloReflection
Declaring Class : class reflection.ReflectionClass
Parameter Counts : 3
Parameter Types : [int, int, class java.lang.String]
Return Type : void
isNative : false
isFinal : false
isStatic : false
isPublic : true
isPrivate : false
isProtected : false
Method Name : helloReflection
Declaring Class : class reflection.ReflectionClass
Parameter Counts : 0
Parameter Types : []
Return Type : void
isNative : false
isFinal : false
isStatic : false
isPublic : true
isPrivate : false
isProtected : false
Method Name : myReturnMethod
Declaring Class : class reflection.ReflectionClass
Parameter Counts : 1
Parameter Types : [class java.lang.String]
Return Type : class java.lang.String
isNative : false
isFinal : false
isStatic : false
isPublic : true
isPrivate : false
isProtected : false
Method Name : myPrivateMethod
Declaring Class : class reflection.ReflectionClass
Parameter Counts : 2
Parameter Types : [int, int]
Return Type : int
isNative : false
isFinal : false
isStatic : false
isPublic : false
isPrivate : true
isProtected : false
Method Name : method1
Declaring Class : class reflection.ReflectionClass
Parameter Counts : 0
Parameter Types : []
Return Type : void
isNative : false
isFinal : false
isStatic : false
isPublic : true
isPrivate : false
isProtected : false
Method Name : method2
Declaring Class : class reflection.ReflectionClass
Parameter Counts : 0
Parameter Types : []
Return Type : void
isNative : false
isFinal : false
isStatic : true
isPublic : true
isPrivate : false
isProtected : false
Method Name : method3
Declaring Class : class reflection.ReflectionClass
Parameter Counts : 0
Parameter Types : []
Return Type : class java.lang.String
isNative : false
isFinal : true
isStatic : true
isPublic : true
isPrivate : false
isProtected : false
Method Name : method4
Declaring Class : class reflection.ReflectionClass
Parameter Counts : 0
Parameter Types : []
Return Type : int
isNative : false
isFinal : true
isStatic : false
isPublic : false
isPrivate : true
isProtected : false
Method Name : method5
Declaring Class : class reflection.ReflectionClass
Parameter Counts : 0
Parameter Types : []
Return Type : long
isNative : false
isFinal : false
isStatic : true
isPublic : false
isPrivate : false
isProtected : true
Interface’lere Eriselim
Javada reflection ile interface’lere erişim class’larla aynı. Tek farkı .getClass() methodunu kullanmıyoruz.
Class<?> myInterface = ReflectionInterface.class;
System.out.println("Interface Name : " + myInterface.getName());
Yukarıdaki kod blocku aslında bize ilgili interface’imizin adını verecektir. Çıktı olarak ReflectionInterface görürüz. Peki neden bu durum Class’larla aynı.?
Aslında bu sorunun cevabı projemizdeki out klasöründe gizli. Java’da ne kodu yazarsak yazalım, ne çalıştırırsak çalıştıralım compiler işleminden sonra java .class uzantılı bir dosya üretir. Bu yüzden elimizdeki her şeye .class diyerek erişebiliriz.
İnterface’lerde aslında durumlar biraz değişiyor. Burada Proxy Class’ı devreye giriyor. Aynı zamanda interface’ler ile işlem yapacağımız zaman ClassLoader yapısı da kullanılıyor.
ClassLoader, Java Class’larının runtime’da dinamik bir şekilde JVM’e yüklenmesinden sorumludur. ClassLoader sayesinde JVM’in java programlarını çalıştırmak için temeldeki dosyalar veya dosya sistemleri hakkında bilgi sahibi olması gerekmez. Ayrıca Java class’ları belleğe bir kerede değil, uygulama istendiğinde yüklenir. ClassLoader’lar belleğe class’ları yüklemekten de sorumludurlar. Daha detaylı bilgi için; https://www.baeldung.com/java-classloaders#classloader-intro
Aslında interface’ler için Reflection ile bu kadar farklı yapıda olması anlaşılabilir bir durum. Çünkü, bir interface birden fazla Class’tan implemente edilebilir.
public interface ReflectionInterface {
void plus(int a, int b);
int minus(int a, int b);
long multiply(long a, long b);
default void defaultMethod1() {
System.out.println("This is default method 1");
}
default void defaultMethod2(int a, int b) {
System.out.println("This is default method 2 and result : " a + b);
}
private void privateMethod1() {
System.out.println("This is private method");
}
}
Yukarıdaki örnekte basitçe bir interface’i doldurduk. Ve örneğin Default methodlarımıza erişim sağlayalım.
ReflectionInterface interfaceInstance = (ReflectionInterface) Proxy.newProxyInstance(
ReflectionInterface.class.getClassLoader(),
new Class[]{ReflectionInterface.class},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
switch (method.getName()) {
case "defaultMethod1":
method.invoke(new InterfaceImplementation());
break;
case "defaultMethod2":
int num1 = 0;
int num2 = 0;
if (args != null && args.length == 2) {
num1 = (int) args[0];
num2 = (int) args[1];
}
Integer resultInteger = (Integer) method.invoke(new InterfaceImplementation(), num1, num2);
int result = resultInteger.intValue();
System.out.println("Proxy: defaultMethod2() is called with parameter: " + result);
break;
default:
break;
}
return null;
}
});
var result2 = interfaceInstance.defaultMethod2(3,6);
System.out.println(result2);
Bir interface içerisindeki methodları çağırmak için kullanılan kodu bir inceleyelim.
- Önce Proxy class’ının newProxyInstance methodunu çağırdık. Bu method 3 tane farklı parametre alır. Birinci parametre; ClassLoader, İkinci parametre Interface’imizin kendisi, ama dikkat edelim ki bu parametre Class array tipinde. (Çünkü bir interface’i birden fazla class implemente etmiş olabilir.) Son parametremiz ise InvocationHandler Class’ı tipinde bir parametre, ki bu da aslında anonymous inner class’a bir örnek olarak karşımıza çıktı. InvocationHandler ise kısaca Java’da bir interface’dir ve Java Reflection API’si tarafından sağlanır. Bu interface, Java nesneleri üzerindeki method çağrılarını ele almak için kullanılır ve dinamik olarak method çağrılarını işlemek için kullanılır.
- Switch — case yapısı kullanılarak default methodların çağrılması gerçekleşmiştir. Buradaki olay aslında tam olarak şu. Interface’imizin içerisinde birden fazla default method bulunabilir ve hangi methodun çağrılacağını ya da hangi methoda erişeceğimizi söylememiz lazım. Örneğin bizim örneğimizde elimizde 2 farklı default method var ve bu default methodlardan birisi parametre alıyor iken diğeri parametre almıyor. Aynı Proxy instance’ı içerisinde bunu switch-case ile ayırt edebiliriz.
- defaultMethod2 adlı methodum 2 farklı parametre alıyor ve args’a bu 2 farklı parametreyi geçiyoruz.
Reflection ile yapılabilecek çok fazla şey var. Spring Boot tarafında da kullanılan bir yapı. ClassLoader ve DynamicProxy yapısı çok önemli.
Not:
Bir interface kaç farklı sınıftan implemente edilmiştir.? Sorusuna çok fazla cevap aradım ve aşağıdaki gibi bir kod blockuna denk geldim, ama mantığı kafamda çok oturmadığı için detaylandırmadım.
Set<Class<?>> classes = new HashSet<>();
String packageName = "reflection";
try {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
assert classLoader != null;
String path = packageName.replace('.', '/');
Enumeration<URL> resources = classLoader.getResources(path);
while (resources.hasMoreElements()) {
URL resource = resources.nextElement();
File file = new File(resource.getFile());
File[] files = file.listFiles();
for (File classFile : files) {
if (classFile.getName().endsWith(".class")) {
String className1 = packageName + '.' + classFile.getName().substring(0, classFile.getName().length() - 6);
Class<?> clazz1 = Class.forName(className1);
if (ReflectionInterface.class.isAssignableFrom(clazz1)) {
classes.add(clazz1);
}
}
}
}
System.out.println(classes);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println(classes.size());
Son olarak
Reflection çok geniş bir konu. Ama recordlarda ya da abstract class’larda durum farklı. Sadece inner class’larda durumlar biraz değişebilir. Ama mantık hepsinde aynı. Ve aynı zamanda JDK’daki class’lara da Reflection ile erişip kullanabilirsiniz.
Yazının kodlarına
https://bitbucket.org/furkandev/reflection/src/master/
buradan ulaşabilirsiniz.
ÖNEMLİ NOT: Normalde asla yazılarımı bir başkası için yazmam, yazılarımı tamamen kendim için yazarım. Ama bu yazım baya çok uzun oldu ve bazı arkadaşlarımız yazımı belki sonuna kadar okuyacaktır. Bu yazıyı sonuna kadar okuyup anlayan 5 arkadaşıma Ankara’da kahve ısmarlamak istiyorum. İspat edilebilir bir şey olmadığı için, lütfen bu doğrultuda hareket edelim. (:
Bana iletişim kanallarından ulaşan ilk 5 arkadaşa kahve ısmarlayacağım. (:
Keyifli okumalar. ((:
İletişim, İşbirlikleri ve Teklifler için,
github: https://github.com/fsk
bitbucket: https://bitbucket.org/furkandev
twitter: https://twitter.com/0xfsk
mail: furkansahinkulaksiz@gmail.com