Reflection API ne işe yarar?

Java'nın yansıma mekanizması, bir geliştiricinin adlarını bilmeden çalışma zamanında sınıflar, arabirimler, alanlar ve yöntemler hakkında değişiklik yapmasına ve bilgi almasına olanak tanır.

Reflection API ayrıca yeni nesneler oluşturmanıza, yöntemleri çağırmanıza ve alan değerleri almanıza veya ayarlamanıza olanak tanır.

Yansımayı kullanarak yapabileceğiniz her şeyin bir listesini yapalım:

  • Bir nesnenin sınıfını tanımlayın/belirleyin
  • Sınıf değiştiriciler, alanlar, yöntemler, sabitler, yapıcılar ve üst sınıflar hakkında bilgi alın
  • Uygulanan arabirim(ler)e hangi yöntemlerin ait olduğunu bulun
  • Program yürütülene kadar sınıf adı bilinmeyen bir sınıfın örneğini oluşturun
  • Bir örnek alanının değerini ada göre alın ve ayarlayın
  • Ada göre bir örnek yöntemi çağırın

Hemen hemen tüm modern Java teknolojileri yansıma kullanır. Günümüzün Java / Java EE çerçevelerinin ve kitaplıklarının çoğunun temelini oluşturur, örneğin:

  • Web uygulamaları oluşturmak için yay çerçeveleri
  • JUnit test çerçevesi

Bu mekanizmalarla daha önce hiç karşılaşmadıysanız, muhtemelen tüm bunlar neden gerekli diye soruyorsunuzdur. Yanıt oldukça basit ama aynı zamanda çok belirsiz: yansıma, esnekliği ve uygulamamızı ve kodumuzu özelleştirme yeteneğimizi önemli ölçüde artırır.

Ama her zaman artıları ve eksileri vardır. O halde birkaç eksiden bahsedelim:

  • Uygulama güvenliği ihlalleri. Yansıma, yapmamamız gereken koda erişmemizi sağlar (kapsülleme ihlali).
  • Güvenlik kısıtlamaları. Yansıma, bir güvenlik yöneticisi çalıştıran sistemlerde bulunmayan çalışma zamanı izinleri gerektirir.
  • Düşük performans. Java'daki yansıma, yüklenecek sınıfı bulmak için sınıf yolunu tarayarak türleri dinamik olarak belirler. Bu, programın performansını düşürür.
  • Bakımı zor. Yansıma kullanan kodun okunması ve hata ayıklaması zordur. Daha az esnektir ve bakımı daha zordur.

Reflection API kullanarak sınıflarla çalışma

Tüm yansıma işlemleri bir java.lang.Class nesnesiyle başlar. Her nesne türü için değişmez bir java.lang.Class örneği oluşturulur. Nesne özelliklerini almak, yeni nesneler oluşturmak ve yöntemleri çağırmak için yöntemler sağlar.

Java.lang.Class ile çalışmak için temel yöntemlerin listesine bir göz atalım :

Yöntem Aksiyon
String getName(); Sınıfın adını döndürür
int getModifiers(); Erişim değiştiricilerini döndürür
Paket getPackage(); Bir paket hakkında bilgi döndürür
Sınıf getSuperclass(); Bir üst sınıf hakkında bilgi verir
Sınıf[] getInterfaces(); Bir arayüz dizisi döndürür
Yapıcı[] getConstructors(); Sınıf kurucuları hakkında bilgi döndürür
Alanlar[] getFields(); Bir sınıfın alanlarını döndürür
Alan getField(Dize alanAdı); Ada göre bir sınıfın belirli bir alanını döndürür
Yöntem[] getMethods(); Bir dizi yöntem döndürür

Bunlar, sınıflar, arayüzler, alanlar ve yöntemler hakkında veri elde etmek için en önemli yöntemlerdir. Alan değerlerini almanıza veya ayarlamanıza ve özel alanlara erişmenize izin veren yöntemler de vardır . Onlara biraz sonra bakacağız.

Şimdi java.lang.Class'ın kendisini almaktan bahsedeceğiz . Bunu yapmak için üç yolumuz var.

1. Class.forName Kullanımı

Çalışan bir uygulamada, bir sınıf almak için forName(String className) yöntemini kullanmanız gerekir.

Bu kod, yansımayı kullanarak sınıfları nasıl oluşturabileceğimizi gösterir. Birlikte çalışabileceğimiz bir Person sınıfı oluşturalım :


package com.company;

public class Person {
    private int age;
    private String name;

    public int getAge() {
        return age;
    }

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

    public String getName() {
        return name;
    }

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

Örneğimizin ikinci kısmı ise yansımayı kullanan kod:


public class TestReflection {
    public static void main(String[] args) {
        try {
            Class<?> aClass = Class.forName("com.company.Person");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

Bu yaklaşım, sınıfın tam adı biliniyorsa mümkündür. Ardından statik Class.forName() yöntemini kullanarak karşılık gelen sınıfı alabilirsiniz . Bu yöntem ilkel türler için kullanılamaz.

2. .class'ı kullanma

Bir tür mevcutsa ancak örneği yoksa, tür adına .class ekleyerek sınıfı alabilirsiniz . Bu, ilkel bir türün sınıfını almanın en kolay yoludur.


Class aClass = Person.class;

3. .getClass()'ı kullanma

Bir nesne mevcutsa, sınıf almanın en kolay yolu object.getClass() öğesini çağırmaktır .


Person person = new Person();
Class aClass = person.getClass();

Son iki yaklaşım arasındaki fark nedir?

Kodlama zamanında hangi sınıf nesnesiyle ilgilendiğinizi biliyorsanız, A.class'ı kullanın . Örnek yoksa, .class kullanmalısınız .

Bir sınıfın yöntemlerini alma

Sınıfımızın yöntemlerini döndüren yöntemlere bakalım: getDeclaredMethods() ve getMethods() .

getDeclaredMethods(), genel, özel, varsayılan ve korumalı yöntemler dahil, ancak miras alınan yöntemler hariç, Class nesnesi tarafından temsil edilen sınıfın veya arabirimin tüm bildirilen yöntemleri için Yöntem nesneleri içeren bir dizi döndürür .

getMethods(), Class nesnesi tarafından temsil edilen sınıfın veya arabirimin tüm genel yöntemleri için (sınıf veya arabirim tarafından bildirilenler ve ayrıca üst sınıflardan ve süper arabirimlerden miras alınanlar) Method nesnelerini içeren bir dizi döndürür .

Her birinin nasıl çalıştığına bir göz atalım.

getDeclaredMethods() ile başlayalım . İki yöntem arasındaki farkı anlamamıza yine yardımcı olması için aşağıda soyut Numbers sınıfıyla çalışacağız . Method dizimizi List<String> dizisine dönüştürecek statik bir yöntem yazalım :


import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class TestReflection {
    public static void main(String[] args) {
        final Method[] declaredMethods = Number.class.getDeclaredMethods();
        List<String> actualMethodNames = getMethodNames(declaredMethods);
        actualMethodNames.forEach(System.out::println);
    }

    private static List<String> getMethodNames(Method[] methods) {
        return Arrays.stream(methods)
                .map(Method::getName)
                .collect(Collectors.toList());
    }
}

İşte bu kodu çalıştırmanın sonucu:

byteValue
kısaDeğer
intValue
uzunDeğer
float floatValue;
çift ​​değer

Bunlar, Number sınıfı içinde bildirilen yöntemlerdir . getMethods() ne döndürür? Örnekteki iki satırı değiştirelim:


final Method[] methods = Number.class.getMethods();
List<String> actualMethodNames = getMethodNames(methods);

Bunu yaparken, aşağıdaki yöntem kümesini göreceğiz:

byteValue
kısaDeğer
intValue
uzunDeğer
float floatValue;
doubleValue
wait
wait
wait
eşittir
toString
hashCode
getClass
notify
notifyAll

Tüm sınıflar Object öğesini devraldığından , yöntemimiz ayrıca Object sınıfının genel yöntemlerini de döndürür.

Bir sınıfın alanlarını alma

Bir sınıfın alanlarını almak için getFields ve getDeclaredFields yöntemleri kullanılır. Örnek olarak LocalDateTime sınıfına bakalım . Kodumuzu yeniden yazacağız:


import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class TestReflection {
    public static void main(String[] args) {
        final Field[] declaredFields = LocalDateTime.class.getDeclaredFields();
        List<String> actualFieldNames = getFieldNames(declaredFields);
        actualFieldNames.forEach(System.out::println);
    }

    private static List<String> getFieldNames(Field[] fields) {
        return Arrays.stream(fields)
                .map(Field::getName)
                .collect(Collectors.toList());
    }
}

Bu kodu çalıştırmanın bir sonucu olarak, LocalDateTime sınıfında bulunan alan kümesini elde ederiz.

MIN
MAX
serialVersionUID
tarih
saat

Önceki yöntem incelememize benzeterek, kodu biraz değiştirirsek ne olacağını görelim:


final Field[] fields = LocalDateTime.class.getFields();
List<String> actualFieldNames = getFieldNames(fields);

Çıktı:

MİN
MAKS

Şimdi bu yöntemler arasındaki farkı bulalım.

getDeclaredFields yöntemi , bununla temsil edilen sınıf veya arabirim tarafından bildirilen tüm alanlar için bir Field nesneleri dizisi döndürür .Sınıfnesne.

getFields yöntemi , sınıfın veya arayüzün tüm ortak alanları için bir Alan nesneleri dizisi döndürür.Sınıfnesne.

Şimdi LocalDateTime içine bakalım .

sınıfınDAKVeMAKS.alanlar herkese açıktır, yani getFields yöntemi aracılığıyla görünür olacaklardır . Buna karşılık,tarih,zaman,seriVersiyonUIDyöntemlerin özel değiştiricisi vardır, yani getFields yöntemi aracılığıyla görünmeyeceklerdir , ancak onları getDeclaredFields kullanarak alabiliriz . Özel alanlar için Field nesnelerine bu şekilde erişebiliriz .

Diğer yöntemlerin açıklamaları

Şimdi, Class sınıfının bazı metotlarından bahsetme zamanı , yani:

Yöntem Aksiyon
getModifier'lar Sınıfımız için değiştiricileri alma
paket al Sınıfımızı içeren paketi almak
süper sınıf al Ebeveyn sınıfını alma
getInterfaces Bir sınıf tarafından uygulanan bir dizi arayüz alma
getName Tam nitelikli sınıf adını alma
getSimpleName Bir sınıfın adını almak

getModifiers()

Değiştiricilere bir kullanılarak erişilebilirSınıfnesne.

Değiştiriciler, public , static , interface vb. anahtar sözcüklerdir . Değiştiricileri getModifiers() yöntemini kullanarak elde ederiz :


Class<Person> personClass = Person.class;
int classModifiers = personClass.getModifiers();

Bu kod, birintbir bit alanı olan değişken. Her erişim değiştirici, karşılık gelen biti ayarlayarak veya temizleyerek açılabilir veya kapatılabilir. Java.lang.reflect.Modifier sınıfındaki yöntemleri kullanarak değiştiricileri kontrol edebiliriz :


import com.company.Person;
import java.lang.reflect.Modifier;

public class TestReflection {
    public static void main(String[] args) {
        Class<Person> personClass = Person.class;
        int classModifiers = personClass.getModifiers();

        boolean isPublic = Modifier.isPublic(classModifiers);
        boolean isStatic = Modifier.isStatic(classModifiers);
        boolean isFinal = Modifier.isFinal(classModifiers);
        boolean isAbstract = Modifier.isAbstract(classModifiers);
        boolean isInterface = Modifier.isInterface(classModifiers);

        System.out.printf("Class modifiers: %d%n", classModifiers);
        System.out.printf("Is public: %b%n", isPublic);
        System.out.printf("Is static: %b%n", isStatic);
        System.out.printf("Is final: %b%n", isFinal);
        System.out.printf("Is abstract: %b%n", isAbstract);
        System.out.printf("Is interface: %b%n", isInterface);
    }
}

Kişimizin beyanının nasıl göründüğünü hatırlayın:


public class Person {
   …
}

Aşağıdaki çıktıyı alıyoruz:

Sınıf değiştiriciler: 1
Geneldir: doğrudur
Statiktir: yanlıştır
Finaldir: yanlışdır
Soyuttur: yanlışdır
Arayüzdür: yanlıştır

Sınıfımızı soyut yaparsak, şunu elde ederiz:


public abstract class Person { … }

ve bu çıktı:

Sınıf değiştiricileri: 1025
Geneldir: doğrudur
Statiktir: yanlıştır
Sondur: yanlışdır
Soyuttur: doğrudur
Arayüzdür: yanlıştır

Erişim değiştiricisini değiştirdik, bu, Modifier sınıfının statik yöntemleri aracılığıyla döndürülen verileri de değiştirdiğimiz anlamına gelir.

getPackage()

Yalnızca bir sınıfı bilerek, paketi hakkında bilgi alabiliriz:


Class<Person> personClass = Person.class;
final Package aPackage = personClass.getPackage();
System.out.println(aPackage.getName());

getSuperclass()

Bir Class nesnemiz varsa, onun üst sınıfına erişebiliriz:


public static void main(String[] args) {
    Class<Person> personClass = Person.class;
    final Class<? super Person> superclass = personClass.getSuperclass();
    System.out.println(superclass);
}

İyi bilinen Object sınıfını alıyoruz :


class java.lang.Object

Ancak sınıfımızın başka bir ebeveyn sınıfı varsa, onun yerine onu göreceğiz:


package com.company;

class Human {
    // Some info
}

public class Person extends Human {
    private int age;
    private String name;

    // Some info
}

İşte ebeveyn sınıfımızı alıyoruz:


class com.company.Human

getInterfaces()

Sınıf tarafından uygulanan arayüzlerin listesini şu şekilde alabiliriz:


public static void main(String[] args) {
    Class<Person> personClass = Person.class;
    final Class<?>[] interfaces = personClass.getInterfaces();
    System.out.println(Arrays.toString(interfaces));
}

Ve Person sınıfımızı da değiştirmeyi unutmayalım :


public class Person implements Serializable { … }

Çıktı:

[java.io.Serileştirilebilir arayüz]

Bir sınıf birçok arayüzü uygulayabilir. Bu yüzden bir dizi elde ediyoruzSınıfnesneler. Java Reflection API'de arayüzler ayrıca şu şekilde temsil edilir:Sınıfnesneler.

Lütfen dikkat: Yöntem, üst sınıfını değil, yalnızca belirtilen sınıf tarafından uygulanan arabirimleri döndürür. Sınıf tarafından uygulanan arayüzlerin tam bir listesini almak için, hem mevcut sınıfa hem de kalıtım zincirindeki tüm atalarına başvurmanız gerekir.

getName() & getSimpleName() & getCanonicalName()

İlkel, iç içe sınıf, anonim sınıf ve String sınıfını içeren bir örnek yazalım :


public class TestReflection {
    public static void main(String[] args) {
        printNamesForClass(int.class, "int class (primitive)");
        printNamesForClass(String.class, "String.class (ordinary class)");
        printNamesForClass(java.util.HashMap.SimpleEntry.class,
                "java.util.HashMap.SimpleEntry.class (nested class)");
        printNamesForClass(new java.io.Serializable() {
                }.getClass(),
                "new java.io.Serializable(){}.getClass() (anonymous inner class)");
    }

    private static void printNamesForClass(final Class<?> clazz, final String label) {
        System.out.printf("%s:%n", label);
        System.out.printf("\tgetName()):\t%s%n", clazz.getName());
        System.out.printf("\tgetCanonicalName()):\t%s%n", clazz.getCanonicalName());
        System.out.printf("\tgetSimpleName()):\t%s%n", clazz.getSimpleName());
        System.out.printf("\tgetTypeName():\t%s%n%n", clazz.getTypeName());
    }
}

Programımızın sonucu:

int sınıf (ilkel):
getName()): int
getCanonicalName()): int
getSimpleName()): int
getTypeName(): int

String.class (sıradan sınıf):
getName()): java.lang.String
getCanonicalName() ): java.lang.String
getSimpleName()): String
getTypeName(): java.lang.String

java.util.HashMap.SimpleEntry.class (iç içe sınıf):
getName()): java.util.AbstractMap$SimpleEntry
getCanonicalName( )): java.util.AbstractMap.SimpleEntry
getSimpleName()): SimpleEntry
getTypeName(): java.util.AbstractMap$SimpleEntry

new java.io.Serializable(){}.getClass() (anonim iç sınıf):
getName() ): TestReflection$1
getCanonicalName()): null
getSimpleName()):
getTypeName(): TestReflection$1

Şimdi programımızın çıktısını inceleyelim:

  • getName() varlığın adını döndürür.

  • getCanonicalName() , Java Dil Belirtimi tarafından tanımlandığı şekliyle temel sınıfın kurallı adını döndürür. Temel sınıfın bir kanonik adı yoksa (yani, yerel veya anonim bir sınıfsa veya öğe türü kanonik bir isme sahip olmayan bir diziyse) null değerini döndürür.

  • getSimpleName(), temel sınıfın kaynak kodunda belirtilen basit adını döndürür. Temel sınıf anonim ise boş bir dize döndürür.

  • getTypeName(), bu türün adı için bilgilendirici bir dize döndürür.