จะวนซ้ำแอตทริบิวต์คลาสใน Java ได้อย่างไร?


87

ฉันจะวนซ้ำแอตทริบิวต์คลาสใน java แบบไดนามิกได้อย่างไร

เช่น:

public class MyClass{
    private type1 att1;
    private type2 att2;
    ...

    public void function(){
        for(var in MyClass.Attributes){
            System.out.println(var.class);
        }
    }
}

เป็นไปได้ใน Java หรือไม่?

คำตอบ:


102

ไม่มีการสนับสนุนทางภาษาในการทำสิ่งที่คุณขอ

คุณสามารถเข้าถึงสมาชิกของประเภทในขณะรันไทม์โดยใช้การสะท้อนกลับ (เช่นClass.getDeclaredFields()เพื่อรับอาร์เรย์Field) แต่ขึ้นอยู่กับสิ่งที่คุณพยายามทำนี่อาจไม่ใช่ทางออกที่ดีที่สุด

ดูสิ่งนี้ด้วย

คำถามที่เกี่ยวข้อง


ตัวอย่าง

นี่คือตัวอย่างง่ายๆเพื่อแสดงให้เห็นว่าการสะท้อนกลับสามารถทำได้เพียงบางส่วนเท่านั้น

import java.lang.reflect.*;

public class DumpFields {
    public static void main(String[] args) {
        inspect(String.class);
    }
    static <T> void inspect(Class<T> klazz) {
        Field[] fields = klazz.getDeclaredFields();
        System.out.printf("%d fields:%n", fields.length);
        for (Field field : fields) {
            System.out.printf("%s %s %s%n",
                Modifier.toString(field.getModifiers()),
                field.getType().getSimpleName(),
                field.getName()
            );
        }
    }
}

ตัวอย่างข้อมูลข้างต้นใช้การสะท้อนเพื่อตรวจสอบฟิลด์ที่ประกาศทั้งหมดของclass String; มันสร้างผลลัพธ์ต่อไปนี้:

7 fields:
private final char[] value
private final int offset
private final int count
private int hash
private static final long serialVersionUID
private static final ObjectStreamField[] serialPersistentFields
public static final Comparator CASE_INSENSITIVE_ORDER

Effective Java 2nd Edition, Item 53: ชอบอินเทอร์เฟซสำหรับการสะท้อนแสง

นี่คือข้อความที่ตัดตอนมาจากหนังสือ:

กำหนดClassวัตถุที่คุณสามารถได้รับConstructor, MethodและFieldกรณีที่เป็นตัวแทนของการก่อสร้างงานวิธีการและสาขาของชั้นเรียน [พวกเขา] ช่วยให้คุณจัดการกับคู่หูที่อยู่ภายใต้การไตร่ตรองได้ อย่างไรก็ตามพลังนี้มาในราคา:

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

ตามกฎแล้วไม่ควรเข้าถึงออบเจ็กต์แบบสะท้อนแสงในแอปพลิเคชันปกติที่รันไทม์

มีแอพพลิเคชั่นที่ซับซ้อนบางอย่างที่ต้องการการสะท้อนแสง ตัวอย่าง ได้แก่[... ละไว้โดยมีวัตถุประสงค์ ... ]หากคุณมีข้อสงสัยว่าแอปพลิเคชันของคุณอยู่ในหมวดหมู่ใดประเภทหนึ่งเหล่านี้อาจจะไม่


ตามค่าของเขตข้อมูลฉันต้องการเขียนหรือไม่เขียนค่าของแต่ละเขตข้อมูล?
Zakaria

@ Zakaria: ใช่คุณสามารถทำสิ่งนี้ได้ด้วยการไตร่ตรอง แต่ขึ้นอยู่กับสิ่งที่คุณพยายามทำมีการออกแบบที่ดีกว่ามาก
polygenelubricants

อันที่จริงฉันมีรายงานที่ต้องสร้างจากคลาสและขึ้นอยู่กับค่าของฟิลด์คลาสที่ฉันต้องการใส่หรือไม่ฟิลด์เหล่านั้นการออกแบบที่ดีที่สุดที่ฉันสามารถนำไปใช้ได้คืออะไร?
Zakaria

1
@ Zakaria: ไปข้างหน้าและลองไตร่ตรองดู มีField.getที่คุณสามารถใช้ในการอ่านค่าของเขตข้อมูล reflectively ถ้าเป็นคุณอาจจะสามารถที่จะได้รับรอบว่าprivate setAccessibleใช่การสะท้อนกลับมีประสิทธิภาพมากและช่วยให้คุณทำสิ่งต่างๆเช่นการตรวจสอบprivateฟิลด์การเขียนทับfinalฟิลด์การเรียกตัวprivateสร้าง ฯลฯ หากคุณต้องการความช่วยเหลือเพิ่มเติมเกี่ยวกับด้านการออกแบบคุณควรเขียนคำถามใหม่พร้อมข้อมูลเพิ่มเติม บางทีคุณอาจไม่จำเป็นต้องมีคุณลักษณะมากมายเหล่านี้ในตอนแรก (เช่นใช้enumหรือListฯลฯ )
polygenelubricants

1
ยาชื่อสามัญไม่มีประโยชน์ที่นี่ เพียงแค่ทำstatic void inspect(Class<?> klazz)
user102008

45

การเข้าถึงฟิลด์โดยตรงไม่ใช่รูปแบบที่ดีใน java ฉันขอแนะนำให้สร้างเมธอด getter และ setter สำหรับฟิลด์ของ bean ของคุณจากนั้นใช้คลาส Introspector และ BeanInfo จากแพ็คเกจ java.beans

MyBean bean = new MyBean();
BeanInfo beanInfo = Introspector.getBeanInfo(MyBean.class);
for (PropertyDescriptor propertyDesc : beanInfo.getPropertyDescriptors()) {
    String propertyName = propertyDesc.getName();
    Object value = propertyDesc.getReadMethod().invoke(bean);
}

มีวิธีใดบ้างที่จะได้รับคุณสมบัติของถั่วเท่านั้นไม่ใช่คลาส? เช่นหลีกเลี่ยง MyBean.class ที่ส่งคืนโดย getPropertyDescriptors
hbprotoss

@hbprotoss ถ้าผมเข้าใจว่าคุณถูกต้องคุณสามารถตรวจสอบหรือคุณสามารถระบุระดับการวิเคราะห์หยุดเป็นพารามิเตอร์ที่สองเช่นpropertyDesc.getReadMethod().getDeclaringClass() != Object.class getBeanInfo(MyBean.class, Object.class)
Jörn Horstmann

20

แม้ว่าฉันจะเห็นด้วยกับคำตอบของJörnหากคลาสของคุณเป็นไปตามข้อกำหนด JavaBeabs แต่นี่เป็นทางเลือกที่ดีหากไม่เป็นเช่นนั้นและคุณใช้ Spring

Spring มีคลาสชื่อReflectionUtilsซึ่งมีฟังก์ชันการทำงานที่ทรงพลังมากรวมถึงdoWithFields (คลาสโทรกลับ)ซึ่งเป็นวิธีการแบบผู้เยี่ยมชมที่ให้คุณทำซ้ำในฟิลด์คลาสโดยใช้อ็อบเจ็กต์การเรียกกลับเช่นนี้:

public void analyze(Object obj){
    ReflectionUtils.doWithFields(obj.getClass(), field -> {

        System.out.println("Field name: " + field.getName());
        field.setAccessible(true);
        System.out.println("Field value: "+ field.get(obj));

    });
}

แต่นี่คือคำเตือน: ชั้นเรียนมีข้อความว่า "สำหรับใช้ภายในเท่านั้น" ซึ่งน่าเสียดายถ้าคุณถามฉัน


14

วิธีง่ายๆในการวนซ้ำฟิลด์คลาสและรับค่าจากออบเจ็กต์:

 Class<?> c = obj.getClass();
 Field[] fields = c.getDeclaredFields();
 Map<String, Object> temp = new HashMap<String, Object>();

 for( Field field : fields ){
      try {
           temp.put(field.getName().toString(), field.get(obj));
      } catch (IllegalArgumentException e1) {
      } catch (IllegalAccessException e1) {
      }
 }

บางทีคลาสหรือวัตถุใด ๆ
Rickey

@Rickey มีการกำหนดค่าใหม่ให้กับแอตทริบิวต์โดยการวนซ้ำแต่ละฟิลด์หรือไม่?
hyperfkcb

@hyperfkcb คุณอาจได้รับข้อยกเว้นการแก้ไขเมื่อพยายามเปลี่ยนค่าคุณสมบัติ / ฟิลด์ที่คุณกำลังทำซ้ำ
Rickey

6

Java มี Reflection (java.reflection. *) แต่ฉันขอแนะนำให้ดูในไลบรารีเช่น Apache Beanutils มันจะทำให้กระบวนการยุ่งยากน้อยกว่าการใช้การสะท้อนโดยตรง


0

นี่คือวิธีแก้ปัญหาที่จัดเรียงคุณสมบัติตามตัวอักษรและพิมพ์ทั้งหมดพร้อมกับค่า:

public void logProperties() throws IllegalArgumentException, IllegalAccessException {
  Class<?> aClass = this.getClass();
  Field[] declaredFields = aClass.getDeclaredFields();
  Map<String, String> logEntries = new HashMap<>();

  for (Field field : declaredFields) {
    field.setAccessible(true);

    Object[] arguments = new Object[]{
      field.getName(),
      field.getType().getSimpleName(),
      String.valueOf(field.get(this))
    };

    String template = "- Property: {0} (Type: {1}, Value: {2})";
    String logMessage = System.getProperty("line.separator")
            + MessageFormat.format(template, arguments);

    logEntries.put(field.getName(), logMessage);
  }

  SortedSet<String> sortedLog = new TreeSet<>(logEntries.keySet());

  StringBuilder sb = new StringBuilder("Class properties:");

  Iterator<String> it = sortedLog.iterator();
  while (it.hasNext()) {
    String key = it.next();
    sb.append(logEntries.get(key));
  }

  System.out.println(sb.toString());
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.