Java Reflection: วิธีรับชื่อของตัวแปร?


139

การใช้ Java Reflection เป็นไปได้หรือไม่ที่จะได้รับชื่อตัวแปรภายในเครื่อง? ตัวอย่างเช่นถ้าฉันมีสิ่งนี้:

Foo b = new Foo();
Foo a = new Foo();
Foo r = new Foo();

เป็นไปได้หรือไม่ที่จะใช้เมธอดที่สามารถค้นหาชื่อของตัวแปรเหล่านั้นได้เช่น:

public void baz(Foo... foos)
{
    for (Foo foo: foos) {
        // Print the name of each foo - b, a, and r
        System.out.println(***); 
    }
}

แก้ไข: คำถามนี้แตกต่างจากมีวิธีใน Java เพื่อค้นหาชื่อของตัวแปรที่ถูกส่งผ่านไปยังฟังก์ชั่นหรือไม่? ในการที่มันจะถามคำถามอย่างบริสุทธิ์ใจว่าใครสามารถใช้การสะท้อนเพื่อกำหนดชื่อของตัวแปรท้องถิ่นในขณะที่คำถามอื่น ๆ (รวมถึงคำตอบที่ได้รับการยอมรับ) จะเน้นที่การทดสอบค่าของตัวแปรมากกว่า


11
คำตอบที่ยอดเยี่ยมทั้งหมด! ขอบคุณทุกคนสำหรับการตอบกลับและแสดงความคิดเห็น - นี่เป็นการสนทนาที่น่าสนใจและลึกซึ้ง
David Koelle


มันเป็นไปได้. ดู [ส่วนสำคัญ] ของฉัน [1] ใช้งานได้กับ JDK 1.1 ถึง JDK 7 [1]: gist.github.com/2011728
Wendal Chen


3
ไม่ใช่คำถามที่ซ้ำกันและอัปเดตคำถามของฉันเพื่ออธิบายว่าทำไม หากมีสิ่งใดคำถามอื่นนั้นซ้ำ (หรือกรณีพิเศษ) ของคำถามนี้!
David Koelle

คำตอบ:


65

ในฐานะของ Java 8 ข้อมูลชื่อตัวแปรท้องถิ่นบางส่วนสามารถดูได้ผ่านการสะท้อนกลับ ดูส่วน"อัปเดต"ด้านล่าง

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

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


อัปเดต:การสนับสนุนที่ จำกัด สำหรับสิ่งนี้ถูกเพิ่มใน Java 8 ชื่อพารามิเตอร์ (คลาสพิเศษของตัวแปรโลคัล) พร้อมใช้งานผ่านการสะท้อนกลับแล้ว นอกเหนือจากวัตถุประสงค์อื่น ๆ สิ่งนี้สามารถช่วยในการแทนที่@ParameterNameคำอธิบายประกอบที่ใช้โดยถังบรรจุแบบพึ่งพาได้


49

มันเป็นไปไม่ได้เลย ชื่อตัวแปรไม่ถูกสื่อสารภายใน Java (และอาจถูกลบออกเนื่องจากการปรับให้เหมาะสมของคอมไพเลอร์)

แก้ไข (เกี่ยวข้องกับความคิดเห็น):

หากคุณย้อนกลับไปจากแนวคิดที่จะใช้มันเป็นพารามิเตอร์ฟังก์ชั่นนี่เป็นอีกทางเลือกหนึ่ง (ซึ่งฉันจะไม่ใช้ - ดูด้านล่าง):

public void printFieldNames(Object obj, Foo... foos) {
    List<Foo> fooList = Arrays.asList(foos);
    for(Field field : obj.getClass().getFields()) {
         if(fooList.contains(field.get()) {
              System.out.println(field.getName());
         }
    }
}

จะมีปัญหาหากa == b, a == r, or b == rหรือมีเขตข้อมูลอื่นที่มีการอ้างอิงเดียวกัน

ขณะนี้แก้ไขไม่จำเป็นเนื่องจากคำถามได้รับการชี้แจง


แล้วคุณจะอธิบายสิ่งนี้ได้ อย่างไร: java.sun.com/javase/6/docs/api/java/lang/reflect/Field.html
Outlaw Programmer

1
-1: ฉันคิดว่าคุณเข้าใจผิด @David อยู่หลังเขตข้อมูลไม่ใช่ตัวแปรเฉพาะที่ ตัวแปรท้องถิ่นไม่สามารถใช้งานได้ผ่าน Reflection API
ลุควู้ดเวิร์ด

ฉันคิดว่า Pourquoi Litytestdata นั้นถูกต้อง เห็นได้ชัดว่าฟิลด์ไม่สามารถปรับให้เหมาะสมออกไปได้ดังนั้น Marcel J. ต้องคำนึงถึงตัวแปรท้องถิ่น
Michael Myers

3
@David: คุณต้องแก้ไขเพื่อชี้แจงว่าคุณหมายถึงฟิลด์มากกว่าตัวแปรในตัวเครื่อง คำถามเดิมให้รหัสที่ประกาศ b, a และ r เป็นตัวแปรท้องถิ่น
46499 Jason S

7
ฉันหมายถึงตัวแปรท้องถิ่นและฉันได้แก้ไขคำถามเพื่อให้สะท้อนถึงสิ่งนั้น ฉันคิดว่ามันอาจเป็นไปไม่ได้ที่จะได้ชื่อตัวแปร แต่ฉันคิดว่าฉันจะถามก่อนที่จะพิจารณาว่าเป็นไปไม่ได้
David Koelle

30

( แก้ไข: ลบคำตอบก่อนหน้านี้สองคำตอบสำหรับการตอบคำถามเนื่องจากอยู่ตรงหน้าการแก้ไขและอีกคำตอบถ้าไม่ผิดอย่างน้อยก็ใกล้เคียง )

หากคุณคอมไพล์ด้วยข้อมูลการดีบักบน ( javac -g) ชื่อของตัวแปรโลคัลจะถูกเก็บไว้ในไฟล์. class ตัวอย่างเช่นใช้คลาสง่าย ๆ นี้:

class TestLocalVarNames {
    public String aMethod(int arg) {
        String local1 = "a string";
        StringBuilder local2 = new StringBuilder();
        return local2.append(local1).append(arg).toString();
    }
}

หลังจากการคอมไพล์ด้วยjavac -g:vars TestLocalVarNames.javaตอนนี้ชื่อของตัวแปรโลคัลจะอยู่ในไฟล์. class javap's -lธง ( 'พิมพ์หมายเลขบรรทัดและตารางตัวแปรท้องถิ่น') สามารถแสดงให้พวกเขา

javap -l -c TestLocalVarNames แสดงให้เห็นว่า:

class TestLocalVarNames extends java.lang.Object{
TestLocalVarNames();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

  LocalVariableTable:
   Start  Length  Slot  Name   Signature
   0      5      0    this       LTestLocalVarNames;

public java.lang.String aMethod(int);
  Code:
   0:   ldc     #2; //String a string
   2:   astore_2
   3:   new     #3; //class java/lang/StringBuilder
   6:   dup
   7:   invokespecial   #4; //Method java/lang/StringBuilder."<init>":()V
   10:  astore_3
   11:  aload_3
   12:  aload_2
   13:  invokevirtual   #5; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   16:  iload_1
   17:  invokevirtual   #6; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
   20:  invokevirtual   #7; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
   23:  areturn

  LocalVariableTable:
   Start  Length  Slot  Name   Signature
   0      24      0    this       LTestLocalVarNames;
   0      24      1    arg       I
   3      21      2    local1       Ljava/lang/String;
   11      13      3    local2       Ljava/lang/StringBuilder;
}

ข้อมูลจำเพาะ VMอธิบายถึงสิ่งที่เราเห็นที่นี่:

§4.7.9 LocalVariableTableคุณสมบัติ :

LocalVariableTableเป็นแอตทริบิวต์ตัวแปรความยาวตัวเลือกของCodeแอตทริบิวต์ (§4.7.3) มันอาจจะถูกใช้โดย debuggers เพื่อกำหนดค่าของตัวแปรท้องถิ่นที่กำหนดในระหว่างการดำเนินการของวิธีการ

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

อย่างที่ erickson กล่าวแม้ว่าจะไม่มีวิธีการเข้าถึงตารางนี้ผ่านการสะท้อนกลับปกติ หากคุณยังคงตั้งใจที่จะทำสิ่งนี้ฉันเชื่อว่าJava Platform Debugger Architecture (JPDA)จะช่วยได้ (แต่ฉันไม่เคยใช้มันด้วยตนเอง)


1
เอ่อโออิคสันโพสต์ขณะที่ฉันกำลังแก้ไขและตอนนี้ฉันแย้งเขา ซึ่งอาจหมายความว่าฉันผิด
Michael Myers

โดยค่าเริ่มต้นให้javacวางตารางตัวแปรโลคัลในคลาสสำหรับแต่ละวิธีเพื่อช่วยในการดีบัก ใช้-lตัวเลือกเพื่อjavapดูตารางตัวแปรท้องถิ่น
เอริก

ไม่ใช่ตามค่าเริ่มต้นดูเหมือนว่า ฉันต้องใช้javac -g:varsเพื่อรับมัน (ฉันพยายามแก้ไขคำตอบนี้เป็นเวลาสามชั่วโมงที่ผ่านมา แต่เนื่องจากฉันบอกว่าการเชื่อมต่อเครือข่ายของฉันกำลังมีปัญหาซึ่งทำให้ยากต่อการค้นคว้า)
Michael Myers

2
คุณพูดถูกแล้วขอโทษด้วย เป็นหมายเลขบรรทัดที่ "เปิด" ตามค่าเริ่มต้น
erickson

15
import java.lang.reflect.Field;


public class test {

 public int i = 5;

 public Integer test = 5;

 public String omghi = "der";

 public static String testStatic = "THIS IS STATIC";

 public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException {
  test t = new test();
  for(Field f : t.getClass().getFields()) {
   System.out.println(f.getGenericType() +" "+f.getName() + " = " + f.get(t));
  }
 }

}

3
getDeclaredFields()สามารถใช้ในกรณีที่คุณต้องการชื่อฟิลด์ส่วนตัวเช่นกัน
coffeMug

10

คุณสามารถทำสิ่งนี้:

Field[] fields = YourClass.class.getDeclaredFields();
//gives no of fields
System.out.println(fields.length);         
for (Field field : fields) {
    //gives the names of the fields
    System.out.println(field.getName());   
}

คำตอบของคุณทำงานได้ดีเพื่อรับ fieads ทั้งหมด เพื่อให้ได้เพียงหนึ่งช่องเมื่อฉันใช้: YourClass.class.getDeclaredField ("field1"); ฉันได้รับ NullPointer ปัญหาในการใช้งานคืออะไร? ฉันควรใช้วิธี getDeclaredField ได้อย่างไร
Shashi Ranjan

0

สิ่งที่คุณต้องทำคือสร้างอาร์เรย์ของฟิลด์จากนั้นตั้งค่าเป็นคลาสที่คุณต้องการดังแสดงด้านล่าง

Field fld[] = (class name).class.getDeclaredFields();   
for(Field x : fld)
{System.out.println(x);}

ตัวอย่างเช่นถ้าคุณทำ

Field fld[] = Integer.class.getDeclaredFields();
          for(Field x : fld)
          {System.out.println(x);}

คุณจะได้รับ

public static final int java.lang.Integer.MIN_VALUE
public static final int java.lang.Integer.MAX_VALUE
public static final java.lang.Class java.lang.Integer.TYPE
static final char[] java.lang.Integer.digits
static final char[] java.lang.Integer.DigitTens
static final char[] java.lang.Integer.DigitOnes
static final int[] java.lang.Integer.sizeTable
private static java.lang.String java.lang.Integer.integerCacheHighPropValue
private final int java.lang.Integer.value
public static final int java.lang.Integer.SIZE
private static final long java.lang.Integer.serialVersionUID

0

อัปเดตคำตอบของ @Marcel Jackwerth สำหรับทั่วไป

และทำงานเฉพาะกับแอตทริบิวต์ class ไม่ทำงานกับตัวแปร method

    /**
     * get variable name as string
     * only work with class attributes
     * not work with method variable
     *
     * @param headClass variable name space
     * @param vars      object variable
     * @throws IllegalAccessException
     */
    public static void printFieldNames(Object headClass, Object... vars) throws IllegalAccessException {
        List<Object> fooList = Arrays.asList(vars);
        for (Field field : headClass.getClass().getFields()) {
            if (fooList.contains(field.get(headClass))) {
                System.out.println(field.getGenericType() + " " + field.getName() + " = " + field.get(headClass));
            }
        }
    }

-1

ดูตัวอย่างนี้:

PersonneTest pt=new PersonneTest();
System.out.println(pt.getClass().getDeclaredFields().length);
Field[]x=pt.getClass().getDeclaredFields();
System.out.println(x[1].getName());
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.