Java'da yansıma yoluyla özel miras alınan alanlara erişim


109

Devralınan üyeler aracılığıyla class.getDeclaredFields(); özel üyelere erişmenin bir yolunu buldum class.getFields() Ama devralınan özel alanlar arıyorum. Bunu nasıl başarabilirim?


28
"özel devralınan alanlar" mevcut değil. Bir alan özelse, miras alınmaz ve yalnızca üst sınıfın kapsamında kalır. Ebeveyn özel alanlarına erişmek için, önce ebeveyn sınıfa erişmeniz gerekir (cf. aioobe'nin yanıtı)
Benoit Courtine

6
Bununla birlikte, korunan alanlar miras alınır, ancak bunları yansıtma yoluyla elde etmek için aynısını yapmanız gerekir.
Bozho

Yanıtlar:


128

Bu, nasıl çözüleceğini göstermelidir:

import java.lang.reflect.Field;

class Super {
    private int i = 5;
}

public class B extends Super {
    public static void main(String[] args) throws Exception {
        B b = new B();
        Field f = b.getClass().getSuperclass().getDeclaredField("i");
        f.setAccessible(true);
        System.out.println(f.get(b));
    }
}

(Veya Class.getDeclaredFieldstüm alanların bir dizisi için.)

Çıktı:

5

Bu, tüm üst sınıfların alanlarını mı yoksa yalnızca doğrudan üst sınıfı mı alıyor?
Yağmur

Doğrudan süper sınıfların alanları. Daha yükseğe çıkmak getSuperclass()istiyorsanız ulaşana kadar tekrarlayabilirsiniz null.
aioobe

Sonraki iki deyimde dizi erişimini neden kullanmıyorsunuz getDeclaredFields()[0]ya da getDeclaredField("i")bunun yerine tekrar etmiyorsunuz [0]?
Holger

Bu özel sorunun formüle edilme biçiminden kaynaklanıyor. Temelde sadece nasıl kullanılacağının bir gösterisiydi getDeclaredFields. Cevap güncellendi.
aioobe

44

Buradaki en iyi yaklaşım , sınıftaki tüm alanları ve tüm süper sınıfları bulmak ve bunlar üzerinde bir geri arama eylemi gerçekleştirmek için Ziyaretçi Modelini kullanmaktır .


uygulama

Spring, tam da bunu yapan güzel bir Utility sınıfına ReflectionUtilssahiptir: bir geri çağırma ile tüm süper sınıfların tüm alanları üzerinde döngü yapmak için bir yöntem tanımlar:ReflectionUtils.doWithFields()

Belgeler:

Belirtilen tüm alanları almak için sınıf hiyerarşisinde yukarı çıkarak, hedef sınıftaki tüm alanlarda verilen geri aramayı çağırın.

Parametreler:
- clazz - analiz edilecek hedef sınıf
- fc - her alan için çağrılacak geri çağrı
- ff - geri çağrının uygulanacağı alanları belirleyen filtre

Basit kod:

ReflectionUtils.doWithFields(RoleUnresolvedList.class,
    new FieldCallback(){

        @Override
        public void doWith(final Field field) throws IllegalArgumentException,
            IllegalAccessException{

            System.out.println("Found field " + field + " in type "
                + field.getDeclaringClass());

        }
    },
    new FieldFilter(){

        @Override
        public boolean matches(final Field field){
            final int modifiers = field.getModifiers();
            // no static fields please
            return !Modifier.isStatic(modifiers);
        }
    });

Çıktı:

Tip sınıfı javax.management.relation.RoleUnresolvedList içinde javax.management.relation.RoleUnresolvedList.typeSafe boole Bulundu alan özel geçici
tipi sınıf javax.management.relation.RoleUnresolvedList içinde javax.management.relation.RoleUnresolvedList.tainted boolean alan özel geçici Bulunan
Bulunan alanda özel geçici java.lang.Object [] java.util.ArrayList tür sınıfında java.util.ArrayList.elementData
Bulunan alan özel int java.util.ArrayList.size, tür sınıfında java.util.ArrayList
Alan korumalı geçici int java bulundu. java.util.AbstractList tür sınıfında util.AbstractList.modCount


3
bu bir "ziyaretçi kalıbı" değildir, ancak kodunuzda Spring virüsü varsa, yine de çok güzel bir araçtır. paylaştığınız için teşekkürler :)
thinlizzy

2
@ jose.diego Bu konuda tartışabileceğinizden oldukça eminim. Bir nesne ağacından ziyade bir sınıf hiyerarşisini ziyaret eder, ancak prensip aynı kalır
Sean Patrick Floyd

Bu yorumun bir yanıt alıp almayacağından emin değilim, ancak bu çözümle aynı anda yalnızca belirli bir alanı ziyaret ediyorsunuz. Aynı anda diğer alanlara bakmam gerekirse - örneğin, başka bir alan NULL ise bu alanı "abc" olarak ayarlayın - nesnenin tamamı benim için kullanılabilir durumda değil.
gen b.

Bu sınıf için JavaDoc'un "yalnızca dahili kullanım için tasarlandığını" belirtmesi çok kötü, bu nedenle bu, onu kullanmak isteyen herkes için olası bir risktir.
spaceman spiff

1
@spacemanspiff teknik olarak haklısınız, ancak bu sınıf yaklaşık 15 yıldır (4 ana sürüm dahil) ve birçok Spring müşterisi tarafından yaygın olarak kullanılıyor. Şimdi geri çekeceklerinden şüpheliyim.
Sean Patrick Floyd

34

Bunu yapacak:

private List<Field> getInheritedPrivateFields(Class<?> type) {
    List<Field> result = new ArrayList<Field>();

    Class<?> i = type;
    while (i != null && i != Object.class) {
        Collections.addAll(result, i.getDeclaredFields());
        i = i.getSuperclass();
    }

    return result;
}

EclEmma gibi bir kod kapsama aracı kullanıyorsanız , dikkat etmeniz gerekir: sınıflarınızın her birine gizli bir alan eklerler. EclEmma durumunda, bu alanlar sentetik olarak işaretlenir ve bunları şu şekilde filtreleyebilirsiniz:

private List<Field> getInheritedPrivateFields(Class<?> type) {
    List<Field> result = new ArrayList<Field>();

    Class<?> i = type;
    while (i != null && i != Object.class) {
        for (Field field : i.getDeclaredFields()) {
            if (!field.isSynthetic()) {
                result.add(field);
            }
        }
        i = i.getSuperclass();
    }

    return result;
}

Sentetik alanlar hakkındaki yorumunuz için teşekkürler, EMMA da aynısını yapıyor.
Anatoliy

bu, argüman sınıfının bildirilen ve miras alınan alanlarını alır, bu nedenle getDeclaredAndInheritedPrivateFields olarak adlandırılmalıdır. mükemmel olsa da teşekkürler!
Peter Hawkins

1
isSynthetic'da güzel yakalayış :)
Lucas Crawford

Mükemmel cevabınız için teşekkürler ~
Raining

19
public static Field getField(Class<?> clazz, String fieldName) {
    Class<?> tmpClass = clazz;
    do {
        try {
            Field f = tmpClass.getDeclaredField(fieldName);
            return f;
        } catch (NoSuchFieldException e) {
            tmpClass = tmpClass.getSuperclass();
        }
    } while (tmpClass != null);

    throw new RuntimeException("Field '" + fieldName
            + "' not found on class " + clazz);
}

( bu cevaba göre)


15

Aslında karmaşık bir tür hiyeraşi kullanıyorum, bu yüzden çözümünüz tam değil. Devralınan tüm özel alanları almak için özyinelemeli bir çağrı yapmam gerekiyor. İşte benim çözümüm

 /**
 * Return the set of fields declared at all level of class hierachy
 */
public Vector<Field> getAllFields(Class clazz) {
    return getAllFieldsRec(clazz, new Vector<Field>());
}

private Vector<Field> getAllFieldsRec(Class clazz, Vector<Field> vector) {
    Class superClazz = clazz.getSuperclass();
    if(superClazz != null){
        getAllFieldsRec(superClazz, vector);
    }
    vector.addAll(toVector(clazz.getDeclaredFields()));
    return vector;
}

Ancak, çözümü sizi doğru yola götürdü, değil mi?
aperkins

1
Vektör kötü eski koddur. Lütfen koleksiyon çerçevesinden güncel bir veri yapısı kullanın (ArrayList çoğu durumda yeterlidir)
Sean Patrick Floyd

@aperkins aioobe cevabı benimki gibi görünüyor, ama buldum ve sonra cevabı gördüm. @seanizer Vector o kadar eski değil ve API koleksiyonunun bir üyesidir
benzen

"Java 2 platformu v1.2'den itibaren, bu sınıf Java'nın koleksiyon çerçevesinin bir parçası olması için List'i uygulamak üzere güçlendirilmiştir." 1.2'de güçlendirildi mi? eğer bu eski değilse o zaman nedir? Kaynak: download.oracle.com/javase/1.4.2/docs/api/java/util/Vector.html
Sean Patrick Floyd

7
Vektörün çok büyük bir ek yükü vardır çünkü her şey senkronize edilmiştir. Ve senkronizasyona ihtiyaç duyduğunuz yerde, java.util.concurrent içinde daha iyi sınıflar vardır. Vector, Hashtable ve StringBuffer çoğu durumda ArrayList, HashMap ve StringBuilder ile değiştirilmelidir
Sean Patrick Floyd

8

Model Citizen'daki planlar için devralınan alanlar için destek eklemem gerekiyordu . Bir Sınıf 'alanlarını + miras alınan alanları almak için biraz daha kısa olan bu yöntemi türettim.

private List<Field> getAllFields(Class clazz) {
    List<Field> fields = new ArrayList<Field>();

    fields.addAll(Arrays.asList(clazz.getDeclaredFields()));

    Class superClazz = clazz.getSuperclass();
    if(superClazz != null){
        fields.addAll(getAllFields(superClazz));
    }

    return fields;
}

7
private static Field getField(Class<?> clazz, String fieldName) {
    Class<?> tmpClass = clazz;
    do {
        for ( Field field : tmpClass.getDeclaredFields() ) {
            String candidateName = field.getName();
            if ( ! candidateName.equals(fieldName) ) {
                continue;
            }
            field.setAccessible(true);
            return field;
        }
        tmpClass = tmpClass.getSuperclass();
    } while ( clazz != null );
    throw new RuntimeException("Field '" + fieldName +
        "' not found on class " + clazz);
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.