เข้าถึงเขตข้อมูลที่สืบทอดส่วนตัวผ่านการสะท้อนใน Java


109

ฉันพบวิธีรับสมาชิกที่สืบทอดผ่าน class.getDeclaredFields(); และเข้าถึงสมาชิกส่วนตัวผ่านclass.getFields() แต่ฉันกำลังมองหาเขตข้อมูลที่สืบทอดแบบส่วนตัว ฉันจะบรรลุเป้าหมายนี้ได้อย่างไร?


28
ไม่มี "เขตข้อมูลส่วนตัวที่สืบทอดมา" หากเขตข้อมูลเป็นแบบส่วนตัวจะไม่มีการสืบทอดและจะอยู่ในขอบเขตของคลาสแม่เท่านั้น ในการเข้าถึงฟิลด์ส่วนตัวของผู้ปกครองคุณต้องเข้าถึงคลาสหลักก่อน (อ้างอิงคำตอบของ aioobe)
Benoit Courtine

6
ที่กล่าวว่าเขตข้อมูลที่ได้รับการป้องกันจะได้รับการสืบทอด แต่คุณต้องทำเช่นเดียวกันเพื่อให้ได้มาโดยการไตร่ตรอง
Bozho

คำตอบ:


128

สิ่งนี้ควรสาธิตวิธีแก้ปัญหา:

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));
    }
}

(หรือClass.getDeclaredFieldsสำหรับอาร์เรย์ของฟิลด์ทั้งหมด)

เอาท์พุต:

5

สิ่งนี้ได้รับเขตข้อมูลของ superclasses ทั้งหมดหรือเพียงแค่ superclass โดยตรง?
ฝนตก

ตรงช่องซุปเปอร์คลาส คุณสามารถเรียกคืนได้getSuperclass()จนกว่าคุณจะไปถึงnullถ้าคุณต้องการไปให้สูง
aioobe

ทำไมคุณไม่ใช้getDeclaredFields()[0]หรือgetDeclaredField("i")แต่เป็นการ[0]เข้าถึงอาร์เรย์ซ้ำในสองคำสั่งถัดไป
Holger

เนื่องจากวิธีการกำหนดคำถามนี้โดยเฉพาะ getDeclaredFieldsมันก็เป็นเพียงการสาธิตวิธีการใช้ อัปเดตคำตอบแล้ว
aioobe

44

แนวทางที่ดีที่สุดคือการใช้รูปแบบผู้เยี่ยมชมค้นหาฟิลด์ทั้งหมดในคลาสและซูเปอร์คลาสทั้งหมดและดำเนินการเรียกกลับ


การนำไปใช้

Spring มีคลาสยูทิลิตี้ที่ดีReflectionUtilsที่ทำเช่นนั้น: กำหนดวิธีการวนซ้ำในทุกฟิลด์ของคลาสซุปเปอร์ทั้งหมดด้วยการเรียกกลับ:ReflectionUtils.doWithFields()

เอกสารประกอบ:

เรียกใช้การเรียกกลับที่กำหนดบนฟิลด์ทั้งหมดในคลาสเป้าหมายขึ้นไปตามลำดับชั้นของคลาสเพื่อรับฟิลด์ที่ประกาศทั้งหมด

พารามิเตอร์:
- clazz - คลาสเป้าหมายที่จะวิเคราะห์
- fc - การเรียกกลับเพื่อเรียกใช้สำหรับแต่ละฟิลด์
- ff - ตัวกรองที่กำหนดฟิลด์ที่จะใช้การโทรกลับไป

โค้ดตัวอย่าง:

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);
        }
    });

เอาท์พุต:

พบฟิลด์ไพรเวตบูลีนชั่วคราว javax.management.relation.RoleUnresolvedList.typeSafe ในคลาสประเภท javax.management.relation.RoleUnresolvedList
พบฟิลด์บูลีนชั่วคราวชั่วคราว javax.management.relation.RoleUnresolvedList.tainted ในประเภทคลาส javax.management.relation.Role
ฟิลด์FoundUnresolved ส่วนตัวชั่วคราว java.lang.Object [] java.util.ArrayList.elementData ในประเภทคลาส java.util.ArrayList
พบฟิลด์ private int java.util.ArrayList.size ในคลาสประเภท java.util.ArrayList
พบฟิลด์ป้องกันชั่วคราว int java util.AbstractList.modCount ในคลาสประเภท java.util.AbstractList


3
นั่นไม่ใช่ "รูปแบบผู้เยี่ยมชม" แต่ก็ยังเป็นเครื่องมือที่ดีมากหากคุณมีไวรัส Spring อยู่ในโค้ดของคุณ ขอบคุณสำหรับการแบ่งปัน :)
thinlizzy

2
@ jose.diego ฉันค่อนข้างมั่นใจว่าคุณสามารถโต้แย้งได้ มันเข้าชมลำดับชั้นของคลาสมากกว่าโครงสร้างวัตถุ แต่หลักการยังคงเหมือนเดิม
ฌอนแพทริคฟลอยด์

ไม่แน่ใจว่าความคิดเห็นนี้จะได้รับการตอบกลับหรือไม่ แต่คุณจะไปที่ช่องเฉพาะในแต่ละครั้งด้วยวิธีแก้ปัญหานี้ ถ้าฉันต้องการดูช่องอื่นในเวลาเดียวกัน - เช่นตั้งค่าฟิลด์นี้เป็น "abc" ถ้าฟิลด์อื่นเป็น NULL - ฉันไม่มีวัตถุทั้งหมดสำหรับฉัน
ยีนข.

แย่เกินไปที่ JavaDoc สำหรับคลาสนี้ระบุว่า "มีไว้สำหรับการใช้งานภายในเท่านั้น" ดังนั้นนี่จึงเป็นความเสี่ยงที่อาจเกิดขึ้นกับทุกคนที่ต้องการใช้งาน
นักบินอวกาศ spiff

1
@spacemanspiff คุณถูกต้องในทางเทคนิค แต่คลาสนี้มีมาประมาณ 15 ปีแล้ว (รวมถึงรุ่นหลัก 4 รุ่น) และลูกค้า Spring หลายคนใช้กันอย่างแพร่หลาย ฉันสงสัยว่าพวกเขาจะดึงมันกลับมาแล้ว
Sean Patrick Floyd

34

สิ่งนี้จะทำ:

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คุณต้องระวัง: พวกเขาเพิ่มฟิลด์ที่ซ่อนอยู่ในแต่ละชั้นเรียนของคุณ ในกรณีของ EclEmma ช่องเหล่านี้ถูกทำเครื่องหมายว่าสังเคราะห์และคุณสามารถกรองออกได้ดังนี้:

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;
}

ขอบคุณสำหรับความคิดเห็นของคุณเกี่ยวกับฟิลด์สังเคราะห์ EMMA ก็ทำเช่นเดียวกัน
Anatoliy

สิ่งนี้ได้รับการประกาศและสืบทอดฟิลด์ของคลาสอาร์กิวเมนต์ดังนั้นควรตั้งชื่อว่า getDeclaredAndInheritedPrivateFields สมบูรณ์แบบแม้ว่าขอบคุณ!
Peter Hawkins

1
จับใจความ isSynthetic :)
Lucas Crawford

ขอบคุณสำหรับคำตอบที่ยอดเยี่ยม ~
ฝนตก

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);
}

(ตามคำตอบนี้ )


15

ในความเป็นจริงฉันใช้ hierachy ประเภทที่ซับซ้อนดังนั้นการแก้ปัญหาของคุณจึงไม่สมบูรณ์ ฉันต้องการโทรซ้ำเพื่อรับเขตข้อมูลส่วนตัวที่สืบทอดมาทั้งหมด นี่คือทางออกของฉัน

 /**
 * 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;
}

อย่างไรก็ตามการแก้ปัญหาของเขาทำให้คุณมาถูกทางใช่หรือไม่?
aperkins

1
เวกเตอร์เป็นรหัสเก่าที่ไม่ดี โปรดใช้โครงสร้างข้อมูลปัจจุบันจากคอลเลกชันเฟรมเวิร์ก (ArrayList เพียงพอในกรณีส่วนใหญ่)
Sean Patrick Floyd

@aperkins คำตอบของ aioobe ดูเหมือนของฉัน แต่ฉันพบแล้วฉันก็เห็นคำตอบ @seanizer Vector ไม่ได้เก่าขนาดนั้นและเป็นสมาชิกของคอลเล็กชัน API
benzen

"ในแพลตฟอร์ม Java 2 v1.2 คลาสนี้ได้รับการติดตั้งใหม่เพื่อใช้งาน List เพื่อให้กลายเป็นส่วนหนึ่งของคอลเลกชันเฟรมเวิร์กของ Java" ติดตั้งเพิ่มใน 1.2? ถ้ายังไม่แก่คืออะไร? ที่มา: download.oracle.com/javase/1.4.2/docs/api/java/util/Vector.html
Sean Patrick Floyd

7
เวกเตอร์มีค่าใช้จ่ายสูงมากเนื่องจากทุกอย่างตรงกัน และที่ที่คุณต้องการซิงโครไนซ์มีคลาสที่ดีกว่าใน java.util.concurrent Vector, Hashtable และ StringBuffer ในกรณีส่วนใหญ่ควรถูกแทนที่ด้วย ArrayList, HashMap และ StringBuilder
Sean Patrick Floyd

8

ฉันต้องการเพิ่มการสนับสนุนสำหรับฟิลด์ที่สืบทอดมาสำหรับพิมพ์เขียวใน รุ่นประชาชน ฉันได้รับวิธีนี้ซึ่งกระชับขึ้นเล็กน้อยสำหรับการดึงฟิลด์คลาส + ฟิลด์ที่สืบทอดมา

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);
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.