Selamlar, genç Padawan. Bu yazıda size, Java programcılarının yalnızca imkansız gibi görünen durumlarda kullandıkları bir güç olan Kuvvet'ten bahsedeceğim. Java'nın karanlık yüzü, Reflection API'sidir. Java'da yansıma, Java Reflection API kullanılarak gerçekleştirilir.
Paket hiyerarşimde , Sınıfım'ın tam adı "reflection.MyClass" olacaktır. Bir sınıfın adını öğrenmenin de basit bir yolu var (sınıfın adını bir String olarak döndür):
Java yansıması nedir?
İnternette kısa, doğru ve popüler bir tanım var. Yansıma ( geç Latin refleksio'dan - geri dönmek için ), çalışırken bir program hakkındaki verileri keşfetme mekanizmasıdır. Yansıma, alanlar, yöntemler ve sınıf kurucuları hakkındaki bilgileri keşfetmenizi sağlar. Yansıma, derleme zamanında mevcut olmayan ancak çalıştırma sırasında kullanılabilir hale gelen türlerle çalışmanıza olanak tanır. Hata bilgisi vermek için yansıtma ve mantıksal olarak tutarlı bir model, doğru dinamik kodun oluşturulmasını mümkün kılar. Başka bir deyişle, Java'da yansımanın nasıl çalıştığını anlamak size bir dizi harika fırsat sunacaktır. Kelimenin tam anlamıyla sınıfları ve bileşenlerini dengeleyebilirsiniz. İşte yansımanın izin verdiği temel bir liste:- Bir nesnenin sınıfını öğrenin/belirleyin;
- Bir sınıfın değiştiricileri, alanları, yöntemleri, sabitleri, yapıcıları ve üst sınıfları hakkında bilgi edinin;
- Uygulanan arabirim(ler)e hangi yöntemlerin ait olduğunu öğrenin;
- Çalışma zamanına kadar sınıf adı bilinmeyen bir sınıf örneği oluşturun;
- Bir nesnenin alanlarının değerlerini ada göre alın ve ayarlayın;
- Bir nesnenin yöntemini ada göre çağırın.
MyClass
:
public class MyClass {
private int number;
private String name = "default";
// public MyClass(int number, String name) {
// this.number = number;
// this.name = name;
// }
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public void setName(String name) {
this.name = name;
}
private void printData(){
System.out.println(number + name);
}
}
Gördüğünüz gibi, bu çok temel bir sınıftır. Parametreleri olan yapıcı kasıtlı olarak yorumlanır. Buna daha sonra geri döneceğiz. Sınıfın içeriğine dikkatlice baktıysanız, muhtemelen ad alanı için bir alıcının olmadığını fark etmişsinizdir . Ad alanının kendisi özel erişim değiştiricisiyle işaretlenmiştir : ona sınıfın dışından erişemeyiz, bu da değerini geri alamayacağımız anlamına gelir . " Öyleyse sorun ne ?" diyorsun. "Bir alıcı ekleyin veya erişim değiştiricisini değiştirin". Ve haklısın, eğerMyClass
derlenmiş bir AAR kitaplığında veya değişiklik yapma yeteneği olmayan başka bir özel modüldeydi. Uygulamada, bu her zaman olur. Ve dikkatsiz bir programcı basitçe bir alıcı yazmayı unutmuş . Yansımayı hatırlamanın tam zamanı! Sınıfın özel isim alanına ulaşmaya çalışalım MyClass
:
public static void main(String[] args) {
MyClass myClass = new MyClass();
int number = myClass.getNumber();
String name = null; // No getter =(
System.out.println(number + name); // Output: 0null
try {
Field field = myClass.getClass().getDeclaredField("name");
field.setAccessible(true);
name = (String) field.get(myClass);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
System.out.println(number + name); // Output: 0default
}
Biraz önce ne olduğunu analiz edelim. Java'da, adında harika bir sınıf var Class
. Yürütülebilir bir Java uygulamasındaki sınıfları ve arabirimleri temsil eder. Bu makalenin konusu bu olmadığı için Class
ve arasındaki ilişkiyi ele almayacağız . ClassLoader
Ardından, bu sınıfın alanlarını almak için yöntemi çağırmanız gerekir getFields()
. Bu yöntem, bu sınıfın tüm erişilebilir alanlarını döndürür. Bu bizim için işe yaramıyor, çünkü alanımız private , bu yüzden yöntemi kullanıyoruz getDeclaredFields()
. Bu yöntem aynı zamanda bir dizi sınıf alanı döndürür, ancak artık özel ve korumalı alanları da içermektedir . Bu durumda ilgilendiğimiz alanın adını biliyoruz, böylece yöntemi kullanabiliriz getDeclaredField(String)
, buradaString
istenen alanın adıdır. Not: getFields()
ve getDeclaredFields()
bir üst sınıfın alanlarını döndürmeyin! Harika. AdımızaField
atıfta bulunan bir nesnemiz var . Alan public olmadığından , onunla çalışmak için erişim izni vermeliyiz. Yöntem daha fazla ilerlememizi sağlar. Artık isim alanı tamamen bizim kontrolümüz altında! Değerini, sınıfımızın bir örneği olan nesnenin yöntemini çağırarak alabilirsiniz . Türü dönüştürüyoruz ve değeri name değişkenimize atıyoruz. Ad alanına yeni bir değer ayarlamak için bir ayarlayıcı bulamazsak , set yöntemini kullanabilirsiniz : setAccessible(true)
Field
get(Object)
Object
MyClass
String
field.set(myClass, (String) "new value");
Tebrikler! Yansımanın temellerinde ustalaştınız ve özel bir alana eriştiniz! try/catch
Bloğa ve işlenen istisna türlerine dikkat edin . IDE size onların varlığının kendi başına gerekli olduğunu söyleyecektir, ancak adlarından neden burada olduklarını açıkça anlayabilirsiniz. Hareketli! Fark etmiş olabileceğiniz gibi, sınıfımızda MyClass
zaten sınıf verileri hakkında bilgi görüntülemek için bir yöntem var:
private void printData(){
System.out.println(number + name);
}
Ama bu programcı burada da parmak izlerini bırakmış. Yöntemin özel bir erişim değiştiricisi vardır ve verileri her seferinde görüntülemek için kendi kodumuzu yazmamız gerekir. Ne dağınıklık. Yansımamız nereye gitti? Aşağıdaki işlevi yazın:
public static void printData(Object myClass){
try {
Method method = myClass.getClass().getDeclaredMethod("printData");
method.setAccessible(true);
method.invoke(myClass);
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
e.printStackTrace();
}
}
Buradaki prosedür, bir alanı almak için kullanılanla hemen hemen aynıdır. İstenen yönteme adıyla erişir ve ona erişim izni veririz. Method
Ve yöntem dediğimiz nesnede , invoke(Object, Args)
burada Object
da sınıfın bir örneğidir MyClass
. Args
yöntemin argümanlarıdır, ancak bizimkinde yoktur. printData
Şimdi bilgiyi görüntülemek için işlevi kullanıyoruz :
public static void main(String[] args) {
MyClass myClass = new MyClass();
int number = myClass.getNumber();
String name = null; //?
printData(myClass); // Output: 0default
try {
Field field = myClass.getClass().getDeclaredField("name");
field.setAccessible(true);
field.set(myClass, (String) "new value");
name = (String) field.get(myClass);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
printData(myClass);// Output: 0new value
}
Yaşasın! Artık sınıfın özel yöntemine erişimimiz var. Ancak, yöntemin argümanları varsa ve neden yapıcı yorumlanırsa? Her şey kendi zamanında. Başlangıçtaki tanımdan, yansımanın çalışma zamanında (program çalışırken) bir sınıfın örneklerini oluşturmanıza izin verdiği açıktır! Sınıfın tam adını kullanarak bir nesne oluşturabiliriz. Sınıfın tam adı, paketinin yolu da dahil olmak üzere sınıf adıdır .

MyClass.class.getName()
Sınıfın bir örneğini oluşturmak için Java yansımasını kullanalım:
public static void main(String[] args) {
MyClass myClass = null;
try {
Class clazz = Class.forName(MyClass.class.getName());
myClass = (MyClass) clazz.newInstance();
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
System.out.println(myClass); // Output: created object reflection.MyClass@60e53b93
}
Bir Java uygulaması başladığında, tüm sınıflar JVM'ye yüklenmez. Kodunuz sınıfa atıfta bulunmuyorsa MyClass
, ClassLoader
sınıfları JVM'ye yüklemekten sorumlu olan , sınıfı asla yüklemez. Bu, onu yüklemeye zorlamanız ClassLoader
ve bir değişken biçiminde bir sınıf açıklaması almanız gerektiği anlamına gelir Class
. Bu nedenle forName(String)
, String
açıklamasına ihtiyacımız olan sınıfın adı olan bir yöntemimiz var. Nesneyi aldıktan sonra Сlass
, yöntemin çağrılması, bu açıklama kullanılarak oluşturulan newInstance()
bir nesneyi döndürür . Object
Geriye kalan tek şey bu nesneyi bizim için tedarik etmek.MyClass
sınıf. Serin! Bu zordu ama anlaşılırdı umarım. Artık kelimenin tam anlamıyla tek bir satırda bir sınıf örneği oluşturabiliriz! Ne yazık ki, açıklanan yaklaşım yalnızca varsayılan oluşturucuyla (parametreler olmadan) çalışacaktır. Yöntemleri ve yapıcıları parametrelerle nasıl çağırırsınız? Yapıcımızın açıklamasını kaldırmanın zamanı geldi. Beklendiği gibi, newInstance()
varsayılan kurucu bulunamıyor ve artık çalışmıyor. Sınıf örneklemesini yeniden yazalım:
public static void main(String[] args) {
MyClass myClass = null;
try {
Class clazz = Class.forName(MyClass.class.getName());
Class[] params = {int.class, String.class};
myClass = (MyClass) clazz.getConstructor(params).newInstance(1, "default2");
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
e.printStackTrace();
}
System.out.println(myClass);// Output: created object reflection.MyClass@60e53b93
}
Yöntem getConstructors()
, sınıf oluşturucuları elde etmek için sınıf tanımında çağrılmalı ve ardından getParameterTypes()
bir oluşturucunun parametrelerini almak için çağrılmalıdır:
Constructor[] constructors = clazz.getConstructors();
for (Constructor constructor : constructors) {
Class[] paramTypes = constructor.getParameterTypes();
for (Class paramType : paramTypes) {
System.out.print(paramType.getName() + " ");
}
System.out.println();
}
Bu bize tüm yapıcıları ve parametrelerini alır. Örneğimde, önceden bilinen belirli parametrelere sahip belirli bir kurucuya atıfta bulunuyorum. newInstance
Ve bu yapıcıyı çağırmak için, bu parametrelerin değerlerini ilettiğimiz yöntemi kullanırız . invoke
Yöntemleri çağırmak için kullanırken aynı olacaktır . Bu şu soruyu akla getiriyor: Oluşturucuları yansıma yoluyla çağırmak ne zaman işe yarar? Başta da belirtildiği gibi, modern Java teknolojileri Java Reflection API olmadan yapamaz. Örneğin, popüler Darer'ı oluşturmak için ek açıklamaları yöntemlerin ve yapıcıların yansımasıyla birleştiren Dependency Injection (DI).Android geliştirme için kütüphane. Bu makaleyi okuduktan sonra, kendinizi güvenle Java Reflection API yöntemleri konusunda eğitimli olarak düşünebilirsiniz. Yansımayı boşuna Java'nın karanlık tarafı olarak adlandırmazlar. OOP paradigmasını tamamen kırar. Java'da kapsülleme, başkalarının belirli program bileşenlerine erişimini gizler ve kısıtlar. Özel değiştiriciyi kullandığımızda, o alana yalnızca bulunduğu sınıfın içinden erişilmesini amaçlıyoruz. Ve programın sonraki mimarisini bu prensibe göre inşa ediyoruz. Bu yazıda, herhangi bir yere gitmek için yansımayı nasıl kullanabileceğinizi gördük. Yaratıcı tasarım deseni Singletonmimari çözüm olarak buna güzel bir örnektir. Temel fikir, bu modeli uygulayan bir sınıfın, tüm programın yürütülmesi sırasında yalnızca bir örneğe sahip olacağıdır. Bu, özel erişim değiştiricisini varsayılan kurucuya ekleyerek gerçekleştirilir. Ve bir programcının bu tür sınıfların daha fazla örneğini oluşturmak için yansımayı kullanması çok kötü olurdu. Bu arada, yakın zamanda bir iş arkadaşımın çok ilginç bir soru sorduğunu duydum: Singleton modelini uygulayan bir sınıf miras alınabilir mi? Bu durumda, yansıma bile güçsüz olabilir mi? Makale hakkındaki görüşlerinizi ve cevabınızı aşağıdaki yorumlara bırakın ve kendi sorularınızı orada sorun!
Daha fazla okuma: |
---|
GO TO FULL VERSION