Nedir Bu Reflection ve Annotation?

İsmail Güngör
5 min readMar 28, 2019

--

Herkese merhaba,

Bu yazıda sizlere çokça karşımıza çıkan ve bence bilgi sahibi olmamız gereken “Reflection” ve “Annotation” konularından bahsedeceğim.

Daha önce ButterKnife, Room, Dagger veya Gson kütüphanelerinden birini kullandınız mı? Eğer kullandıysanız

  • @BindView -butterknife-
  • @Insert -room-
  • @Component -dagger-
  • @SerializedName -gson-

gibi ifadelerle karşılaşmışsınızdır mutlaka. Peki bu ifadelerin arka planda nasıl çalıştığınız hiç merak ettiniz mi? Bu gibi kütüphaneler reflection ve annotation özelliklerini kulanmaktadır.

Hadi gelin inceleyelim!

Java Reflection

Reflection (Yansıma) özelliği nesnelerin sınıf, method, değişken ve diğer özelliklerine ulaşmamıza yardımcı olur. Biz bunu zaten bir değişken tanımlayıp “.” koyduğumuz zaman -idenin tamamlamasıyla- görebiliyoruz diyebilirsiniz!

val student = Student()
student.
(bu kısımda ide bizlere method veya değişken gibi özellikler önerir)

Ancak siz bir method yazdınız ve bu method parametre olarak herhangi bir nesne alabiliyor olsun. Map, Activity, String, View vb. herhangi biri olabilir. Mesela nesnenin sınıf ismine göre işlem yapacaksak eğer, sınıf ismine ulaşmamızı reflection özelliği sağlamaktadır.

Gson kütüphanesini düşünelim. Biz fromJson(String -json-, Type-class-) metodunu çağırırken json objesini çevireceği bir model veriyoruz. Gson ise reflection özelliğini kullanarak bu modelin değişkenlerine erişip değerlerini ayarlamaktadır.

Şimdi örnek bir sınıf ve bir method yazalım, sonra yazdığımız bu methoda parametre olarak oluşturduğumuz sınıfın bir örneğini verelim ve özelliklerine ulaşmaya çalışalım.

Student.class

class Student(
private val name: String,
private val number: Int,
private val hardWorking: Boolean
) {

public fun showName(){
Log.i("student", name)

}

public fun showNumber(){
Log.i("student", "$number")

}

public fun isHardWorking(){
Log.i("student", "Harworking ?: ${if(hardWorking) "Yes" else "No"}")
}


}

lookIntoUnknownClass method

Methodumuz parametre olarak her tipte nesne alabilir. Kod kotlin ile yazıldığı için öncesinde (3. satır) java sınıfına çevirmesini yaptık. Daha sonrasında sınıfımızın bazı özelliklerine erişim sağladık.

  • Name : Sınıf ismini verecektir. Package ile beraber
  • Simple Name: Sadece bizim sınıf oluştururken kullandığımız ismi verecektir.
  • IsArray, IsInterface: Sınıfın bir array veya interface olup olmadığını belirtir.
  • Constructors Count: Sınıfın kaç tane constructura sahip olduğunu belirtir.
  • Constructor Parameter Count: Sınıfın ilgili constructurının kaç tane parametresi olduğunu belirtir.
  • Declared Fields: Bizim tanımladığımız ve varsayılan olarak java sınıfında tanımlı değişkenleri verecektir.
  • Declared Mathods: Bizim tanımladığımız ve varsayılan olarak java sınıfında tanımlı methodları verecektir.

MainActivity.class

val student = Student("John", 23, true)
lookIntoUnknownClass(student)

Uygulamanın Logcat çıktısı aşağıdaki gibi olacaktır.

$change, serialVersionUID, access$super java tarafından varsayılan olarak tanımlıdır.

Ve Başardık!

Annotation

Annotation, Java’da method, sınıf ve değişkenlere özellik katmak için kullanılan yapılardır. Üst bilgi olarak düşünebiliriz.

Aslında baktığımızda biz bu yapılara aşinayız. Çünkü Java varsayılan olarak bazı annotationlara sahiptir. Örneğin: @Override, @Deprecated

Kotlin’de yeni bir annotation sınıfı açmak için “annotation class” anahtar sözcüğü kullanılır.

annotation class KBindTranslateView(val key: String)

Peki oluşturduğumuz annotationların üst bilgi olarak kullanılacağı alanları nasıl belirtiyoruz?

Başlıca Kullanım Alanları:

CLASS - Sınıf başlarında

PROPERTY, FIELD - Özelliklerin başlarında

LOCAL_VARIABLE - Yerel Değişkenlerin başlarında
CONSTRUCTOR - Constructorların başlarında

FUNCTION - Methodların başlarında
VALUE_PARAMETER - Constructor ve methodların parametre başlarında.
.
.

Şimdi oluşturduğumuz özel annotation için kullanım alanı tanımlayalım.

@Target(AnnotationTarget.FIELD,
AnnotationTarget.FUNCTION,
AnnotationTarget.VALUE_PARAMETER)
annotation class KBindTranslateView(
val value: Int,
val key: String
)

Burada KBindTranslateView adlı bir annotation oluşturduk. Kullanım alanlarını FIELD, FUNCTION, VALUE_PARAMETER olacağını belirttik. Parametre olarakta value:Int ve key:String alacağını belirttik.

  • 2. satırda oluşturduğumuz annotationu constructor parametresi için üst bilgi olarak ekledik. Value ve key değerlerini nasıl ayarladığımızı görebilirsiniz.
  • 5. ve 8. satırda değişkenler için üst bilgi olarak ekledik.
  • 16. satırda method için ve 17. satırda ise method parametresi için üst bilgi olarak ekledik. Burada value ve key değerlerini isimlendirme kullanmadan, parametre sırasına göre ayarladığımız fark edebilirsiniz.

Buraya kadar özet geçelim.

— KBindTranslateView adında özel bir annotation oluşturduk.

— Oluşturduğumuz annotationu hangi alanlarda kullanacağımız belirledik.

— Oluşturduğumuz annotation için value ve key adlarında iki parametre ekledik.

— MainActivity sınıfını içinde değişken, method ve parametreler için üst bilgi olarak ekledik.

Peki üst bilgi eklerken value ve key değerlerini ayarlamıştık. Bu değerlere ne zaman erişebiliyoruz?

Annotation bilgilerine erişmek için üç farklı zaman vardır.

  • SOURCE: Bebekken bize takılan takma ad gibi düşünebiliriz. Büyüdüğümüz zaman kimse bu adı hatırlamaz, kendimiz de dahil. Annotation bilgileri (key-value) derleme (build project) zamanına kadar bilinir. Derleme sırasında bu bilgiler kaybolur. Tutulmaz.
  • BINARY: Aile içerisindeki özel takma adımız gibi düşünebiliriz. Sadece ailemiz tarafından bilinir. Başka kimseler bu bilgimize ulaşamaz. Annotation bilgileri kod derlendikten sonra da tutulur. Ancak çalışma zamanında bu bilgilere (key-value) ulaşamayız.
  • RUNTIME: Okuldaki takma adımız gibi düşünebiliriz. Okul zamanında herkes tarafından bilinir ve okul bittikten sonra isteyen okul arkadaşımıza sorarak bizim takma adımızı öğrenebilir. Annotation bilgileri kod derlendikten sonra bile uygulama çalışma zamanında erişim sağlanır.

Annotation bilgilerine ise Reflection kütüphanesi kullanılarak erişilir.

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FIELD,
AnnotationTarget.FUNCTION,
AnnotationTarget.VALUE_PARAMETER)
annotation class KBindTranslateView(
val value: Int,
val key: String
)

Yukarıda kullanacağımız annotation bilgilerine çalışma zamanında erişeceğimiz belirtmiş olduk.

Bu kadar yazı yeterli, artık sizlerle bir örnek yapalım! Ekranda bir Button ve TextView olsun. Bu componentlerdeki yazılar telefonun diline göre değişsin.

İhtiyacımız olan sınıfları düşünelim.

  • Bize belirlediğimiz anahtar sözcüklere İngilizce ve Türkçe anlamlarını verecek olan bir json sınıfı
  • Özel bir annotation sınıfı
  • Reflection kullanarak annotation bilgilerine ulaşıp, arka planda findViewById uygulayacak bir sınıf
  • MainActivity

Hadi başlayalım!

FakeTranslatorJson

Bu sınıf bizlere, anahtar sözcük ve telefonun diline göre İngilizce veya Türkçe olarak ekran gösterilecek mesajları içeren JSONObject sağlayacaktır.

Örneğin biz uygulamada “try” anahtar sözcüğünün mesajını İngilizce olarak butonda göstermek isteyeceğiz.

KBindTranslateView

Bu sınıf bizim oluşturduğumuz özel bir annotation sınıfıdır. Bu annotation sınıfını propertylerde (FIELD) üst bilgi olarak kullanacağız. Value -viewların idleri-, Key -bu viewda göstereceğimiz mesaj- bilgilerini gireceğiz. Annotation sınıfının bilgilerine ise çalışma zamanında (RUNTIME) ulaşabileceğiz.

Yani ben MainActivity de bir view tanımlayacağım örneğin Button. Bu viewa annotation olarak KBindTranslateView ekleyeceğim. Eklerken de bind etmek için value olarak “R.id.btn_try”, İngilizce veya Türkçe mesajı seçmek için key olarak “try” bilgilerini ayarlayacağım.

MainActivity

Bu sınıfta yukarda oluşturduğumuz annotation sınıfını “tvFail” TextView ve “btnTry” Button propertyleri için kullanmış olduk. İlgili kaynak idsini ve anahtar sözcüğünü üst bilgiye ekledik. OnCreate metodunda ise aşağıda açıklamış olduğum KViewTranslator sınıfının bindAndTranslate metodunu çağırdık. Artık reflection kullanarak annotation nesnelerinden bilgileri okuma işlemi bu method içerisinde gerçekleştirilecektir.

KViewTranslator

Bu sınıf bindAndTranslate methoduna sahiptir. Bu method, parametre olarak KBindTranslateView annotationlarını kullandığımız activity objesini alır.

  • 13. satırda öncelikle telefonun dilini öğrenmiş olduk.
  • 14. satırda ise dil bilgisini içeren json nesnesini edinmiş olduk.
  • 16. satırda parametre olarak gönderdiğimiz Activity nesnesinin sahip olduğu, kullanıcı tarafından tanımlanan değerlere ulaşmış olduk. (Reflection ile btnTry ve tvFail nesnelerine ulaşılacaktır.)
  • 17. satırda bize dönen fieldın KBindTranslateView diye bir annotationa sahip olup olmadığını sorguladık. (Reflection ile)
  • Eğer currentAnnotation boş değil ise 19. satırda json nesnesinden annotation objesinin key değerine göre ekranda göstereceğimiz mesajı almış olduk.
  • 23. satırda ise annotation objesinin value değerini kullanarak ilgili viewi bind etmiş olduk.
  • 24. satırda ise ilgili field ın eğer TextView objesi ise ona dönüştürüp ilgili mesajımızı ayarlamış olduk.

Uygulamanın Çıktısı

Bir de bu fake json objesinin kendi apimizden geldiğini düşünürsek!

Anlık olarak uygulamanın yazılarını değiştireceğimiz bir özellik katmış olmaz mıyız!

Yazımı okuduğunuz için teşekkür ederim, sevgiyle kalın!

--

--