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동일한 참조를 가진 다른 필드가 있거나있을 경우 문제가 발생합니다 .

질문이 명확 해 졌으므로 지금 수정하지 않아도됩니다.



1
-1 : 오해 한 것 같아요. @David는 지역 변수가 아닌 필드 뒤입니다. 리플렉션 API를 통해 로컬 변수를 사용할 수 없습니다.
Luke Woodward

Pourquoi Litytestdata가 옳다고 생각합니다. 분명히 필드를 최적화 할 수 없으므로 Marcel J.는 지역 변수를 고려해야합니다.
Michael Myers

3
@David : 지역 변수가 아닌 필드를 의미한다는 것을 명확히하기 위해 편집해야합니다. 원래 질문은 b, a 및 r을 지역 변수로 선언하는 코드를 제공합니다.
Jason S

7
나는 지역 변수를 의미했고, 그것을 반영하기 위해 질문을 편집했습니다. 변수 이름을 얻는 것이 불가능하다고 생각했지만 불가능하다고 생각하기 전에 SO에 물을 것이라고 생각했습니다.
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-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) 속성 의 선택적 가변 길이 속성입니다 . 메소드 실행 중에 지정된 로컬 변수의 값을 판별하기 위해 디버거에서 사용할 수 있습니다.

LocalVariableTable상점 이름과 각 슬롯에서 변수의 타입이 있으므로은 바이트 코드들을 일치시킬 수있다. 디버거가 "표현식 평가"를 수행하는 방법입니다.

그러나 에릭슨이 말했듯이 정상적인 반사를 통해이 테이블에 액세스 할 수있는 방법이 없습니다. 그래도이 작업을 수행하기로 결정했다면 Java 플랫폼 디버거 아키텍처 (JPDA) 가 도움이 될 것이라고 생각합니다 (하지만 직접 사용하지는 않았습니다).


1
어, 에릭슨은 내가 편집하는 동안 글을 올렸는데 지금은 그를 모순하고 있습니다. 아마 내가 틀렸다는 것을 의미합니다.
마이클 마이어스

기본적으로 javac디버깅을 지원하기 위해 각 메소드의 로컬 변수 테이블을 클래스에 넣습니다. 로컬 변수 테이블을 보려면 -l옵션을 사용하십시오 javap.
erickson

기본적으로는 아닌 것 같습니다. 나는 javac -g:vars그것을 얻기 위해 사용해야 했다. (지난 3 시간 동안이 답변을 편집하려고했지만 네트워크 연결에 문제가있어서 조사하기가 어렵습니다.)
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의 일반적인 답변을 업데이트하십시오.

메소드 변수로는 작동하지 않고 클래스 속성으로 만 작동합니다.

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