Java Reflection: Bagaimana cara mendapatkan nama variabel?


139

Menggunakan Java Reflection, apakah mungkin untuk mendapatkan nama variabel lokal? Misalnya, jika saya punya ini:

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

apakah mungkin untuk mengimplementasikan metode yang dapat menemukan nama-nama variabel tersebut, seperti:

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

EDIT: Pertanyaan ini berbeda dari Apakah ada cara di Jawa untuk menemukan nama variabel yang dilewatkan ke fungsi? dalam hal itu lebih murni mengajukan pertanyaan tentang apakah seseorang dapat menggunakan refleksi untuk menentukan nama variabel lokal, sedangkan pertanyaan lain (termasuk jawaban yang diterima) lebih fokus pada pengujian nilai-nilai variabel.


11
Semua jawaban bagus! Terima kasih kepada semua orang untuk membalas dan berkomentar - ini telah menjadi diskusi yang menarik dan berwawasan luas.
David Koelle


Itu mungkin. Lihat [intisari] saya [1]. Bekerja untuk JDK 1.1 hingga JDK 7. [1]: gist.github.com/2011728
Wendal Chen


3
Bukan duplikat, dan memperbarui pertanyaan saya untuk menjelaskan alasannya. Jika ada, pertanyaan lain itu adalah duplikat (atau kasus khusus) dari pertanyaan ini!
David Koelle

Jawaban:


65

Pada Java 8, beberapa informasi nama variabel lokal tersedia melalui refleksi. Lihat bagian "Perbarui" di bawah ini.

Informasi lengkap sering disimpan dalam file kelas. Salah satu optimasi waktu kompilasi adalah untuk menghapusnya, menghemat ruang (dan menyediakan beberapa obsfuscation). Namun, ketika itu hadir, setiap metode memiliki atribut tabel variabel lokal yang mencantumkan jenis dan nama variabel lokal, dan berbagai instruksi di mana mereka berada dalam ruang lingkup.

Mungkin pustaka rekayasa kode-byte seperti ASM akan memungkinkan Anda untuk memeriksa informasi ini saat runtime. Satu-satunya tempat yang masuk akal yang dapat saya pikirkan untuk membutuhkan informasi ini adalah dalam alat pengembangan, dan karena itu teknik byte-code mungkin berguna untuk keperluan lain juga.


Pembaruan: Dukungan terbatas untuk ini ditambahkan ke Java 8. Nama parameter (kelas khusus variabel lokal) sekarang tersedia melalui refleksi. Di antara tujuan lain, ini dapat membantu mengganti @ParameterNameanotasi yang digunakan oleh wadah injeksi ketergantungan.


49

Itu tidak mungkin sama sekali. Nama variabel tidak dikomunikasikan dalam Java (dan mungkin juga dihapus karena optimisasi kompiler).

EDIT (terkait dengan komentar):

Jika Anda mundur dari gagasan harus menggunakannya sebagai parameter fungsi, berikut ini adalah alternatif (yang tidak akan saya gunakan - lihat di bawah):

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

Akan ada masalah jika a == b, a == r, or b == ratau ada bidang lain yang memiliki referensi yang sama.

EDIT sekarang tidak perlu karena pertanyaan diklarifikasi



1
-1: Saya pikir Anda salah paham. @ David adalah setelah bidang, bukan variabel lokal. Variabel lokal memang tidak tersedia melalui API Refleksi.
Luke Woodward

Saya pikir Pourquoi Litytestdata benar. Jelas bidang tidak dapat dioptimalkan, jadi Marcel J. harus memikirkan variabel lokal.
Michael Myers

3
@ David: Anda perlu mengedit untuk menjelaskan bahwa maksud Anda adalah bidang, bukan variabel lokal. Pertanyaan asli memberikan kode yang menyatakan b, a, dan r sebagai variabel lokal.
Jason S

7
Maksud saya adalah variabel lokal, dan saya telah mengedit pertanyaan untuk mencerminkan hal itu. Saya pikir itu tidak mungkin untuk mendapatkan nama variabel, tapi saya pikir saya akan bertanya SO sebelum mempertimbangkan itu tidak mungkin.
David Koelle

30

( Sunting: dua jawaban sebelumnya dihapus, satu untuk menjawab pertanyaan seperti yang berdiri sebelum pengeditan dan satu untuk menjadi, jika tidak sepenuhnya salah, setidaknya dekat dengan itu. )

Jika Anda mengompilasi dengan informasi debug pada ( javac -g), nama-nama variabel lokal disimpan dalam file .class. Sebagai contoh, ambil kelas sederhana ini:

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

Setelah dikompilasi dengan javac -g:vars TestLocalVarNames.java, nama-nama variabel lokal sekarang di file .class. javap's -lbendera ( 'Cetak nomor baris dan tabel variabel lokal') dapat menunjukkan kepada mereka.

javap -l -c TestLocalVarNames menunjukkan:

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

Spesifikasi VM menjelaskan apa yang kami lihat di sini:

§4.7.9 LocalVariableTableAtribut :

The LocalVariableTableatribut opsional variabel-panjang atribut dari Code(§4.7.3) atribut. Ini dapat digunakan oleh debuggers untuk menentukan nilai variabel lokal yang diberikan selama eksekusi suatu metode.

The LocalVariableTablemenyimpan nama dan jenis variabel dalam setiap slot, sehingga memungkinkan untuk mencocokkan mereka dengan bytecode. Ini adalah bagaimana para pengadu dapat melakukan "Mengevaluasi ekspresi".

Seperti yang dikatakan erickson, tidak ada cara untuk mengakses tabel ini melalui refleksi normal. Jika Anda masih bertekad untuk melakukan ini, saya percaya Java Platform Debugger Architecture (JPDA) akan membantu (tapi saya tidak pernah menggunakannya sendiri).


1
Uh-oh, erickson memposting ketika saya mengedit dan sekarang saya menentangnya. Yang mungkin berarti saya salah.
Michael Myers

Secara default, javacletakkan tabel variabel lokal di kelas untuk setiap metode untuk membantu debugging. Gunakan -lopsi javapuntuk melihat tabel variabel lokal.
erickson

Tidak secara default, tampaknya. Saya harus menggunakannya javac -g:varsuntuk mendapatkannya. (Saya sudah berusaha untuk mengedit jawaban ini selama tiga jam terakhir, tapi seperti yang saya katakan koneksi jaringan saya mengalami masalah, yang membuatnya sulit untuk penelitian.)
Michael Myers

2
Anda benar, maaf soal itu. Ini adalah nomor baris yang "aktif" secara default.
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()dapat digunakan jika Anda ingin nama bidang pribadi juga
coffeMug

10

Anda bisa melakukan ini:

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

Jawaban Anda bekerja dengan baik untuk mendapatkan semua fiead. Untuk mendapatkan hanya satu bidang, saat saya menggunakan: YourClass.class.getDeclaredField ("field1"); Saya mendapatkan NullPointer. Apa masalah dalam menggunakannya? Bagaimana saya harus menggunakan metode getDeclaredField?
Shashi Ranjan

0

Yang perlu Anda lakukan adalah membuat array bidang dan kemudian mengaturnya ke kelas yang Anda inginkan seperti yang ditunjukkan di bawah ini.

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

Sebagai contoh jika Anda melakukannya

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

kamu akan mendapatkan

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

perbarui jawaban @Marcel Jackwerth untuk umum.

dan hanya bekerja dengan atribut kelas, tidak bekerja dengan variabel metode.

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

lihat contoh ini:

PersonneTest pt=new PersonneTest();
System.out.println(pt.getClass().getDeclaredFields().length);
Field[]x=pt.getClass().getDeclaredFields();
System.out.println(x[1].getName());
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.