Belki de sıradan hayatta "yansıma" kavramıyla karşılaşmışsınızdır. Bu kelime genellikle kendini inceleme sürecini ifade eder. Programlamada benzer bir anlama sahiptir - bir program hakkındaki verileri analiz etmek ve hatta program çalışırken programın yapısını ve davranışını değiştirmek için bir mekanizmadır. Yansıma örnekleri - 1 Burada önemli olan, bunu derleme zamanında değil, çalışma zamanında yapıyor olmamızdır. Ancak kodu çalışma zamanında neden inceleyesiniz? Sonuçta, kodu zaten okuyabilirsiniz :/ Yansıma fikrinin hemen netleşmemesinin bir nedeni var: bu noktaya kadar, hangi sınıflarla çalıştığınızı her zaman biliyordunuz. CatÖrneğin, bir sınıf yazabilirsiniz :

package learn.codegym;

public class Cat {

   private String name;
   private int age;

   public Cat(String name, int age) {
       this.name = name;
       this.age = age;
   }

   public void sayMeow() {

       System.out.println("Meow!");
   }

   public void jump() {

       System.out.println("Jump!");
   }

   public String getName() {
       return name;
   }

   public void setName(String name) {
       this.name = name;
   }

   public int getAge() {
       return age;
   }

   public void setAge(int age) {
       this.age = age;
   }

@Override
public String toString() {
   return "Cat{" +
           "name='" + name + '\'' +
           ", age=" + age +
           '}';
}

}
Bununla ilgili her şeyi biliyorsunuz ve sahip olduğu alanları ve yöntemleri görebilirsiniz. Aniden programa başka hayvan sınıfları eklemeniz gerektiğini varsayalım. Kolaylık sağlamak için muhtemelen bir üst sınıfla bir sınıf kalıtım yapısı oluşturabilirsiniz Animal. Daha önce, bir veteriner kliniğini temsil eden, bir nesneyi (ebeveyn sınıfı örneği) iletebileceğimiz bir sınıf bile oluşturduk Animalve program, hayvanı köpek mi kedi mi olduğuna göre uygun şekilde tedavi etti. Bunlar en basit görevler olmasa da, program sınıflar hakkında gerekli tüm bilgileri derleme zamanında öğrenebilir. CatBuna göre, veteriner kliniği sınıfının yöntemlerine bir nesne ilettiğinizde,main()yöntem, program onun bir köpek değil, bir kedi olduğunu zaten biliyor. Şimdi farklı bir görevle karşı karşıya olduğumuzu düşünelim. Amacımız bir kod analizcisi yazmak. CodeAnalyzerTek bir yöntemle bir sınıf oluşturmamız gerekiyor : void analyzeObject(Object o). Bu yöntem:
  • kendisine iletilen nesnenin sınıfını belirleyin ve sınıf adını konsolda görüntüleyin;
  • özel olanlar da dahil olmak üzere, geçirilen sınıfın tüm alanlarının adlarını belirleyin ve bunları konsolda görüntüleyin;
  • özel olanlar da dahil olmak üzere, geçirilen sınıfın tüm yöntemlerinin adlarını belirleyin ve bunları konsolda görüntüleyin.
Bunun gibi bir şey görünecek:

public class CodeAnalyzer {

   public static void analyzeClass(Object o) {
      
       // Print the name of the class of object o
       // Print the names of all variables of this class
       // Print the names of all methods of this class
   }
  
}
Şimdi, bu görevin daha önce çözdüğünüz diğer görevlerden ne kadar farklı olduğunu açıkça görebiliriz. Şu anki hedefimizle ilgili zorluk, ne bizim ne de programın programa tam olarak neyin aktarılacağını bilmememizde yatıyor.analyzeClass()yöntem. Böyle bir program yazarsanız, diğer programcılar onu kullanmaya başlayacak ve bu yönteme her şeyi - herhangi bir standart Java sınıfı veya yazdıkları herhangi bir başka sınıf - iletebilirler. Geçirilen sınıf, herhangi bir sayıda değişkene ve yönteme sahip olabilir. Başka bir deyişle, biz (ve programımız) hangi sınıflarla çalışacağımız hakkında hiçbir fikrimiz yok. Ama yine de bu görevi tamamlamamız gerekiyor. İşte standart Java Reflection API'nin yardımımıza geldiği yer burasıdır. Reflection API, dilin güçlü bir aracıdır. Oracle'ın resmi belgeleri, bu mekanizmanın yalnızca ne yaptığını bilen deneyimli programcılar tarafından kullanılmasını önerir. Neden önceden böyle bir uyarı verdiğimizi kısa sürede anlayacaksınız :) Reflection API ile yapabileceğiniz şeylerin listesi:
  1. Bir nesnenin sınıfını tanımlayın/belirleyin.
  2. Sınıf değiştiriciler, alanlar, yöntemler, sabitler, yapıcılar ve üst sınıflar hakkında bilgi edinin.
  3. Uygulanan bir arabirime/arayüzlere hangi yöntemlerin ait olduğunu bulun.
  4. Program yürütülene kadar sınıf adı bilinmeyen bir sınıf örneği oluşturun.
  5. Bir örnek alanının değerini ada göre alın ve ayarlayın.
  6. Ada göre bir örnek yöntemi çağırın.
Etkileyici liste, ha? :) Not:yansıma mekanizması, kod çözümleyicimize ilettiğimiz nesnenin türünden bağımsız olarak tüm bunları "anında" yapabilir! Bazı örneklere bakarak Reflection API'nin yeteneklerini keşfedelim.

Bir nesnenin sınıfı nasıl belirlenir/belirlenir

Temel bilgilerle başlayalım. Java yansıma motorunun giriş noktası sınıftır Class. Evet, gerçekten komik görünüyor, ama yansıma bu :) Sınıfı kullanarak Class, önce yöntemimize geçirilen herhangi bir nesnenin sınıfını belirliyoruz. Bunu yapmaya çalışalım:

import learn.codegym.Cat;

public class CodeAnalyzer {

   public static void analyzeClass(Object o) {
       Class clazz = o.getClass();
       System.out.println(clazz);
   }

   public static void main(String[] args) {

       analyzeClass(new Cat("Fluffy", 6));
   }
}
Konsol çıktısı:

class learn.codegym.Cat
İki şeye dikkat edin. Catİlk olarak, sınıfı kasıtlı olarak ayrı bir pakete koyduk learn.codegym. getClass()Artık yöntemin sınıfın tam adını döndürdüğünü görebilirsiniz . İkinci olarak, değişkenimizi adlandırdık clazz. Bu biraz garip görünüyor. Buna "sınıf" demek mantıklı olur, ancak "sınıf" Java'da ayrılmış bir kelimedir. Derleyici, değişkenlerin böyle adlandırılmasına izin vermez. Bunu bir şekilde aşmamız gerekiyordu :) Başlangıç ​​için fena değil! Bu yetenekler listesinde başka neler vardı?

Sınıf değiştiriciler, alanlar, yöntemler, sabitler, yapıcılar ve üst sınıflar hakkında nasıl bilgi edinilir.

Şimdi işler daha da ilginçleşiyor! Mevcut sınıfta herhangi bir sabitimiz veya ebeveyn sınıfımız yok. Tam bir resim oluşturmak için onları ekleyelim. En basit Animalüst sınıfı oluşturun:

package learn.codegym;
public class Animal {

   private String name;
   private int age;
}
CatVe sınıfımızın miras almasını sağlayacağız Animalve bir sabit ekleyeceğiz:

package learn.codegym;

public class Cat extends Animal {

   private static final String ANIMAL_FAMILY = "Feline family";

   private String name;
   private int age;

   // ...the rest of the class
}
Şimdi tam resme sahibiz! Yansımanın neler yapabileceğini görelim :)

import learn.codegym.Cat;

import java.util.Arrays;

public class CodeAnalyzer {

   public static void analyzeClass(Object o) {
       Class clazz = o.getClass();
       System.out.println("Class name: " + clazz);
       System.out.println("Class fields: " + Arrays.toString(clazz.getDeclaredFields()));
       System.out.println("Parent class: " + clazz.getSuperclass());
       System.out.println("Class methods: " + Arrays.toString(clazz.getDeclaredMethods()));
       System.out.println("Class constructors: " + Arrays.toString(clazz.getConstructors()));
   }

   public static void main(String[] args) {

       analyzeClass(new Cat("Fluffy", 6));
   }
}
İşte konsolda gördüklerimiz:

Class name:  class learn.codegym.Cat 
Class fields: [private static final java.lang.String learn.codegym.Cat.ANIMAL_FAMILY, private java.lang.String learn.codegym.Cat.name, private int learn.codegym.Cat.age] 
Parent class: class learn.codegym.Animal 
Class methods: [public java.lang.String learn.codegym.Cat.getName(), public void learn.codegym.Cat.setName(java.lang.String), public void learn.codegym.Cat.sayMeow(), public void learn.codegym.Cat.setAge(int), public void learn.codegym.Cat.jump(), public int learn.codegym.Cat.getAge()] 
Class constructors: [public learn.codegym.Cat(java.lang.String, int)]
Alabildiğimiz tüm bu ayrıntılı sınıf bilgilerine bakın! Ve sadece halka açık bilgiler değil, aynı zamanda özel bilgiler de! Not: privatedeğişkenler de listede görüntülenir. analyzeObject()Sınıfla ilgili "analizimiz" esasen tamamlanmış sayılabilir: Yöntemi öğrenebileceğimiz her şeyi öğrenmek için kullanıyoruz . Ancak bu, yansıtma ile yapabileceğimiz her şey değil. Basit gözlemle sınırlı değiliz — harekete geçmeye geçeceğiz! :)

Program çalıştırılana kadar sınıf adı bilinmeyen bir sınıfın örneği nasıl oluşturulur?

Varsayılan kurucu ile başlayalım. Sınıfımızda Cathenüz bir tane yok, o yüzden ekleyelim:

public Cat() {
  
}
Catİşte yansıma ( createCat()yöntemi) kullanarak bir nesne oluşturma kodu :

import learn.codegym.Cat;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Main {

   public static Cat createCat() throws IOException, IllegalAccessException, InstantiationException, ClassNotFoundException {

       BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
       String className = reader.readLine();

       Class clazz = Class.forName(className);
       Cat cat = (Cat) clazz.newInstance();

       return cat;
   }

public static Object createObject() throws Exception {

   BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
   String className = reader.readLine();

   Class clazz = Class.forName(className);
   Object result = clazz.newInstance();

   return result;
}

   public static void main(String[] args) throws IOException, IllegalAccessException, ClassNotFoundException, InstantiationException {
       System.out.println(createCat());
   }
}
Konsol girişi:

learn.codegym.Cat
Konsol çıktısı:

Cat{name='null', age=0}
nameBu bir hata değildir: ve değerleri agekonsolda görüntülenir, çünkü bunları toString()sınıfın yönteminde çıkarmak için kod yazdık Cat. Burada konsoldan nesnesini oluşturacağımız sınıfın adını okuyoruz. Program, nesnesi oluşturulacak sınıfın adını tanır. Yansıma örnekleri - 3Kısa olması adına, örneğin kendisinden daha fazla yer kaplayacak uygun istisna işleme kodunu atladık. Gerçek bir programda, elbette, yanlış girilen isimler vb. durumlarla başa çıkmalısınız. Varsayılan oluşturucu oldukça basittir, bu nedenle, gördüğünüz gibi, sınıfın bir örneğini oluşturmak için onu kullanmak kolaydır :) Yöntemi newInstance()kullanmak , bu sınıftan yeni bir nesne yaratıyoruz. O başka bir mesele, eğerCatyapıcı bağımsız değişkenleri girdi olarak alır. Sınıfın varsayılan yapıcısını kaldıralım ve kodumuzu tekrar çalıştırmayı deneyelim.

null
java.lang.InstantiationException: learn.codegym.Cat 
at java.lang.Class.newInstance(Class.java:427)
Bir şeyler yanlış gitti! Varsayılan oluşturucuyu kullanarak bir nesne oluşturmak için bir yöntem çağırdığımız için bir hata aldık. Ama şu anda böyle bir kurucumuz yok. Bu nedenle newInstance(), yöntem çalıştığında, yansıtma mekanizması eski kurucumuzu iki parametreyle kullanır:

public Cat(String name, int age) {
   this.name = name;
   this.age = age;
}
Ama parametrelerle hiçbir şey yapmadık, sanki onları tamamen unutmuşuz gibi! Yapıcıya argümanları iletmek için yansımayı kullanmak biraz "yaratıcılık" gerektirir:

import learn.codegym.Cat;

import java.lang.reflect.InvocationTargetException;

public class Main {

   public static Cat createCat()  {

       Class clazz = null;
       Cat cat = null;

       try {
           clazz = Class.forName("learn.codegym.Cat");
           Class[] catClassParams = {String.class, int.class};
           cat = (Cat) clazz.getConstructor(catClassParams).newInstance("Fluffy", 6);
       } catch (ClassNotFoundException e) {
           e.printStackTrace();
       } catch (InstantiationException e) {
           e.printStackTrace();
       } catch (IllegalAccessException e) {
           e.printStackTrace();
       } catch (NoSuchMethodException e) {
           e.printStackTrace();
       } catch (InvocationTargetException e) {
           e.printStackTrace();
       }

       return cat;
   }

   public static void main(String[] args) {
       System.out.println(createCat());
   }
}
Konsol çıktısı:

Cat{name='Fluffy', age=6}
Programımızda neler olduğuna daha yakından bakalım. Bir dizi nesne oluşturduk Class.

Class[] catClassParams = {String.class, int.class};
Yapıcımızın parametrelerine karşılık gelirler (sadece Stringve intparametreleri vardır). Onları yönteme aktarıyoruz clazz.getConstructor()ve istenen kurucuya erişim sağlıyoruz. Bundan sonra tek yapmamız gereken, newInstance()gerekli argümanlarla yöntemi çağırmak ve nesneyi açıkça istenen türe dönüştürmeyi unutmayın: Cat.

cat = (Cat) clazz.getConstructor(catClassParams).newInstance("Fluffy", 6);
Artık nesnemiz başarıyla oluşturuldu! Konsol çıktısı:

Cat{name='Fluffy', age=6}
Doğru ilerliyor :)

Ada göre bir örnek alanın değeri nasıl alınır ve ayarlanır.

Başka bir programcı tarafından yazılmış bir sınıfı kullandığınızı hayal edin. Ayrıca, düzenleme olanağınız da yok. Örneğin, bir JAR'da paketlenmiş hazır bir sınıf kitaplığı. Sınıfların kodunu okuyabilirsiniz ama değiştiremezsiniz. Bu kütüphanedeki sınıflardan birini (eski sınıfımız olsun Cat) oluşturan programcının, tasarım tamamlanmadan önceki gece yeterince uyuyamayınca, alan için alıcı ve ayarlayıcıyı kaldırdığını varsayalım age. Şimdi bu sınıf size geldi. CatProgramınızda sadece nesnelere ihtiyacınız olduğu için tüm ihtiyaçlarınızı karşılar . Ama bir alana sahip olmak için onlara ihtiyacın var age! Bu bir sorun: sahaya ulaşamıyoruz çünküprivatedeğiştirici ve alıcı ve ayarlayıcı, sınıfı oluşturan uykusuz geliştirici tarafından silindi :/ Bu durumda yansıma bize yardımcı olabilir! Sınıfın koduna erişimimiz var Cat, böylece en azından hangi alanlara sahip olduğunu ve bunların ne olarak adlandırıldığını öğrenebiliriz. Bu bilgilerle donanmış olarak, sorunumuzu çözebiliriz:

import learn.codegym.Cat;

import java.lang.reflect.Field;

public class Main {

   public static Cat createCat()  {

       Class clazz = null;
       Cat cat = null;
       try {
           clazz = Class.forName("learn.codegym.Cat");
           cat = (Cat) clazz.newInstance();

           // We got lucky with the name field, since it has a setter
           cat.setName("Fluffy");

           Field age = clazz.getDeclaredField("age");
          
           age.setAccessible(true);

           age.set(cat, 6);

       } catch (IllegalAccessException e) {
           e.printStackTrace();
       } catch (InstantiationException e) {
           e.printStackTrace();
       } catch (ClassNotFoundException e) {
           e.printStackTrace();
       } catch (NoSuchFieldException e) {
           e.printStackTrace();
       }

       return cat;
   }

   public static void main(String[] args) {
       System.out.println(createCat());
   }
}
Yorumlarda belirtildiği gibi, namesınıf geliştiricileri bir ayarlayıcı sağladığından, alanla ilgili her şey basittir. Varsayılan kuruculardan nasıl nesne yaratılacağını zaten biliyorsunuz: newInstance()bunun için bizde var. Ama ikinci alanla biraz uğraşmamız gerekecek. Burada neler olduğunu anlayalım :)

Field age = clazz.getDeclaredField("age");
Burada, nesnemizi kullanarak , alana yöntem aracılığıyla Class clazzerişiyoruz . Yaş alanını bir nesne olarak almamızı sağlar . Ancak bu yeterli değildir çünkü alanlara basitçe değer atayamayız . Bunu yapmak için, şu yöntemi kullanarak alanı erişilebilir hale getirmemiz gerekir : agegetDeclaredField()Field ageprivatesetAccessible()

age.setAccessible(true);
Bunu bir alana yaptığımızda, bir değer atayabiliriz:

age.set(cat, 6);
Gördüğünüz gibi, nesnemiz Field agebir int değeri ve alanı atanacak nesneyi ilettiğimiz bir tür içten dışa ayarlayıcıya sahiptir. Metodumuzu çalıştırıyoruz main()ve görüyoruz:

Cat{name='Fluffy', age=6}
Harika! Yaptık! :) Bakalım başka neler yapabiliriz...

Bir örnek yöntemi ada göre nasıl çağrılır?

Bir önceki örnekteki durumu biraz değiştirelim. Diyelim ki Catsınıf geliştirici alıcılar ve ayarlayıcılarla ilgili bir hata yapmadı. Bu konuda her şey yolunda. Şimdi sorun farklı: Kesinlikle ihtiyacımız olan bir yöntem var, ancak geliştirici onu özel yaptı:

private void sayMeow() {

   System.out.println("Meow!");
}
Bu, programımızda nesneler oluşturursak , onlar üzerinde yöntemi Catçağıramayacağımız anlamına gelir. sayMeow()Miyavlamayan kedilerimiz mi olacak? Bu garip :/ Bunu nasıl düzelteceğiz? Reflection API bir kez daha bize yardımcı oluyor! İhtiyacımız olan yöntemin adını biliyoruz. Diğer her şey teknik bir ayrıntıdır:

import learn.codegym.Cat;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Main {

   public static void invokeSayMeowMethod()  {

       Class clazz = null;
       Cat cat = null;
       try {

           cat = new Cat("Fluffy", 6);
          
           clazz = Class.forName(Cat.class.getName());
          
           Method sayMeow = clazz.getDeclaredMethod("sayMeow");
          
           sayMeow.setAccessible(true);
          
           sayMeow.invoke(cat);
          
       } catch (ClassNotFoundException e) {
           e.printStackTrace();
       } catch (NoSuchMethodException e) {
           e.printStackTrace();
       } catch (IllegalAccessException e) {
           e.printStackTrace();
       } catch (InvocationTargetException e) {
           e.printStackTrace();
       }
   }

   public static void main(String[] args) {
       invokeSayMeowMethod();
   }
}
Özel bir alana erişirken yaptığımız şeylerin çoğunu burada yapıyoruz. İlk olarak, ihtiyacımız olan yöntemi alıyoruz. Bir nesnede kapsüllenmiştir Method:

Method sayMeow = clazz.getDeclaredMethod("sayMeow");
Yöntem getDeclaredMethod(), özel yöntemlere ulaşmamızı sağlar. Ardından, yöntemi çağrılabilir hale getiriyoruz:

sayMeow.setAccessible(true);
Son olarak, istenen nesnedeki yöntemi çağırıyoruz:

sayMeow.invoke(cat);
Burada, yöntem çağrımız bir "geri arama" gibi görünüyor: bir nesneyi istenen yönteme ( ) işaret etmek için nokta kullanmaya alışkınız cat.sayMeow(), ancak yansıma ile çalışırken, yönteme çağırmak istediğimiz nesneyi geçiyoruz o yöntem Konsolumuzda neler var?

Meow!
Her şey çalıştı! :) Artık Java'nın yansıma mekanizmasının bize sunduğu engin olasılıkları görebilirsiniz. Zor ve beklenmedik durumlarda (kapalı bir kütüphaneden bir sınıfla örneklerimiz gibi), bize gerçekten çok yardımcı olabilir. Ancak, herhangi bir büyük güçte olduğu gibi, büyük sorumluluk getirir. Yansımanın dezavantajları , Oracle web sitesindeki özel bir bölümde açıklanmaktadır . Üç ana dezavantaj vardır:
  1. Performans daha kötü. Yansıma kullanılarak adlandırılan yöntemler, normal şekilde çağrılan yöntemlerden daha kötü performansa sahiptir.

  2. Güvenlik kısıtlamaları var. Yansıma mekanizması, çalışma zamanında bir programın davranışını değiştirmemize izin verir. Ancak iş yerinizde gerçek bir proje üzerinde çalışırken buna izin vermeyen sınırlamalarla karşılaşabilirsiniz.

  3. Dahili bilgilerin açığa çıkma riski. Yansımanın kapsülleme ilkesinin doğrudan ihlali olduğunu anlamak önemlidir: özel alanlara, yöntemlere vb. erişmemizi sağlar. OOP ilkelerinin doğrudan ve alenen ihlaline başvurulması gerektiğini söylememe gerek yok sanırım yalnızca en aşırı durumlarda, kontrolünüz dışındaki nedenlerle bir sorunu çözmenin başka yolu olmadığında.

Yansımayı akıllıca ve yalnızca kaçınılamayacağı durumlarda kullanın ve eksikliklerini unutmayın. Bununla dersimiz sona ermiştir. Biraz uzun oldu ama bugün çok şey öğrendin :)