Java Yansıması: Bir değişkenin adı nasıl alınır?


139

Java Reflection kullanarak yerel bir değişkenin adını almak mümkün müdür? Örneğin, bu varsa:

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

bu değişkenlerin adlarını bulabilecek bir yöntem uygulamak mümkündür, örneğin:

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

EDIT: Bu sorudan farklıdır Java'da bir işleve geçirilen değişkenin adını bulmak için bir yolu var mı? daha tamamen yerel bir değişkenin adını belirlemek için yansıma kullanıp kullanamayacağı sorusunu sorurken, diğer soru (kabul edilen cevap dahil) değişkenlerin değerlerini test etmeye daha fazla odaklanmıştır.


11
Tüm harika cevaplar! Yanıtladığı ve yorum yaptığı için herkese teşekkürler - bu ilginç ve anlayışlı bir tartışma oldu.
David Koelle


Bu mümkün. Bkz. [Gist] [1]. JDK 1.1 - JDK 7 için çalışır. [1]: gist.github.com/2011728
Wendal Chen


3
Yinelenen değil ve nedenini açıklamak için sorum güncellenmiştir. Bir şey varsa, bu diğer soru bunun bir kopyası (veya özel bir durum)!
David Koelle

Yanıtlar:


65

Java 8'den itibaren bazı yerel değişken adı bilgileri yansıma yoluyla edinilebilir. Aşağıdaki "Güncelle" bölümüne bakın.

Tam bilgi genellikle sınıf dosyalarında saklanır. Derleme zamanı optimizasyonlarından biri, yerden tasarruf etmek (ve biraz karışıklık sağlamak). Ancak, mevcut olduğunda, her yöntemin yerel değişkenlerin türünü ve adını ve kapsamda oldukları talimat aralığını listeleyen bir yerel değişken tablosu özniteliği vardır.

Belki de ASM gibi bir bayt-kod mühendislik kütüphanesi bu bilgiyi çalışma zamanında incelemenize izin verir. Bu bilgiye ihtiyaç duymak için düşünebileceğim tek makul yer bir geliştirme aracında ve bu nedenle bayt kodu mühendisliğinin başka amaçlar için de yararlı olması muhtemeldir.


Güncelleme: Bunun için sınırlı destek Java 8'e eklenmiştir. Parametre (özel bir yerel değişken sınıfı) adları yansıma yoluyla kullanılabilir. Diğer amaçların yanı sıra, bu, @ParameterNamebağımlılık enjeksiyon kapları tarafından kullanılan ek açıklamaların değiştirilmesine yardımcı olabilir .


49

Hiç mümkün değil . Değişken adları Java içinde iletilmez (derleyici optimizasyonları nedeniyle de kaldırılabilir).

EDIT (yorumlarla ilgili):

Fonksiyon parametreleri olarak kullanmak zorunda kalmak fikrinden geri adım atarsanız, işte bir alternatif (ben kullanmazdım - aşağıya bakın):

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 == rAynı referanslara sahip başka alanlar varsa veya varsa sorunlar olacaktır .

Soru açıklığa kavuştuğundan artık EDIT gerekli değil



1
-1: Sanırım yanlış anladın. @David yerel değişkenlerden değil, alanlardan sonra. Yerel değişkenler gerçekten de Yansıma API'sı aracılığıyla kullanılamaz.
Luke Woodward

Bence Pourquoi Litytestdata haklı. Açıkçası alanlar optimize edilemez, bu yüzden Marcel J. yerel değişkenleri düşünüyor olmalıdır.
Michael Myers

3
@David: Yerel değişkenler yerine alanlar demek istediğinizi açıklığa kavuşturmak için düzenlemelisiniz. Orijinal soru, yerel değişkenler olarak b, a ve r'yi bildiren bir kod verir.
Jason S

7
Yerel değişkenleri kastetmiştim ve bunu yansıtmak için soruyu düzenledim. Değişken isimleri almanın mümkün olmayabileceğini düşündüm, ancak imkansız olduğunu düşünmeden önce SO'ya soracağımı düşündüm.
David Koelle

30

( Düzenleme: Önceki iki cevaplar o düzenlemeleri önüne çıkıp olduğu için bir olarak kesinlikle yanlış değilse kendisine en kapanışında, soruya cevap için, bir tane kaldırıldı. )

( javac -g) Üzerindeki hata ayıklama bilgileri ile derlerseniz , yerel değişkenlerin adları .class dosyasında tutulur. Örneğin, şu basit sınıfı alın:

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İle derlendikten sonra , yerel değişkenlerin adları şimdi .class dosyasındadır. javap'nin -lbayrağı ("Baskı satırı numarası ve yerel değişken tabloları") bunları gösterebilir.

javap -l -c TestLocalVarNames gösterileri:

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 Spec burada gördüğümüz şey açıklıyor:

§4.7.9 LocalVariableTableÖzellik :

LocalVariableTableÖzelliği, bir isteğe bağlı bir değişken uzunluklu özniteliktir Code(§4.7.3) öznitelik. Hata ayıklayıcılar tarafından, bir yöntemin yürütülmesi sırasında belirli bir yerel değişkenin değerini belirlemek için kullanılabilir.

LocalVariableTableMağaza isimleri ve her bir yuvaya değişkenlerin tipleri, nedenle bayt ile onları maç mümkündür. Hata ayıklayıcılar "İfadeyi değerlendir" i bu şekilde yapabilir.

Ericson'un dediği gibi, bu tabloya normal yansıma ile erişmenin bir yolu yoktur. Hala bunu yapmaya kararlıysanız, Java Platform Hata Ayıklayıcı Mimarisinin (JPDA) yardımcı olacağına inanıyorum (ancak bunu hiç kullanmadım).


1
Uh-oh, erickson ben düzenleme yaparken yayınladı ve şimdi onunla çelişiyorum. Bu muhtemelen yanıldığım anlamına geliyor.
Michael Myers

Varsayılan olarak, javachata ayıklamaya yardımcı olması için her yöntem için sınıfa yerel bir değişken tablosu koyar. Yerel değişken tablosunu görmek için -lseçeneği kullanın javap.
erickson

Varsayılan olarak değil, öyle görünüyor. Bunu javac -g:varselde etmek için kullanmak zorunda kaldım. (Son üç saattir bu cevabı düzenlemeye çalışıyorum, ancak dediğim gibi ağ bağlantım problemler yaşıyor, bu da araştırmayı zorlaştırıyor.)
Michael Myers

2
Haklısın, bunun için üzgünüm. Varsayılan olarak "açık" satır numaralarıdır.
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()özel alan adlarını da istemeniz durumunda kullanılabilir
coffeMug

10

Bunu şöyle yapabilirsiniz:

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

Cevabınız tüm sonuçları elde etmek için iyi çalışıyor. Yalnızca bir alan elde etmek için şunu kullandığımda: YourClass.class.getDeclaredField ("field1"); NullPointer alıyorum. Kullanmanın problemi nedir? GetDeclaredField yöntemini nasıl kullanmalıyım?
Shashi Ranjan

0

Tek yapmanız gereken bir dizi alan oluşturmak ve daha sonra aşağıda gösterildiği gibi istediğiniz sınıfa ayarlamaktır.

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

Örneğin,

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

alacaksın

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'in cevabını genel olarak güncelleyin.

ve yalnızca sınıf özniteliğiyle çalışma, yöntem değişkeni ile çalışma.

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

bu örneğe bakın:

PersonneTest pt=new PersonneTest();
System.out.println(pt.getClass().getDeclaredFields().length);
Field[]x=pt.getClass().getDeclaredFields();
System.out.println(x[1].getName());
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.