170

Using Java Reflection, is it possible to get the name of a local variable? For example, if I have this:

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

is it possible to implement a method that can find the names of those variables, like so:

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

EDIT: This question is different from Is there a way in Java to find the name of the variable that was passed to a function? in that it more purely asks the question about whether one can use reflection to determine the name of a local variable, whereas the other question (including the accepted answer) is more focused on testing values of variables.

5

8 Answers 8

73

As of Java 8, some local variable name information is available through reflection. See the "Update" section below.

Complete information is often stored in class files. One compile-time optimization is to remove it, saving space (and providing some obsfuscation). However, when it is is present, each method has a local variable table attribute that lists the type and name of local variables, and the range of instructions where they are in scope.

Perhaps a byte-code engineering library like ASM would allow you to inspect this information at runtime. The only reasonable place I can think of for needing this information is in a development tool, and so byte-code engineering is likely to be useful for other purposes too.


Update: Limited support for this was added to Java 8. Parameter (a special class of local variable) names are now available via reflection. Among other purposes, this can help to replace @ParameterName annotations used by dependency injection containers.

Sign up to request clarification or add additional context in comments.

Comments

56

It is not possible at all. Variable names aren't communicated within Java (and might also be removed due to compiler optimizations).

EDIT (related to comments):

If you step back from the idea of having to use it as function parameters, here's an alternative (which I wouldn't use - see below):

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

There will be issues if a == b, a == r, or b == r or there are other fields which have the same references.

EDIT now unnecessary since question got clarified

9 Comments

-1: I think you've misunderstood. @David is after fields, not local variables. Local variables are indeed unavailable through the Reflection API.
I think Pourquoi Litytestdata is right. Obviously fields can't be optimized away, so Marcel J. must be thinking of local variables.
@David: You need to edit to clarify that you meant fields rather than local variables. The original question gives code that declares b, a, and r as local variables.
I did mean local variables, and I've edited the question to reflect that. I thought it might not be possible to get variable names, but I figured I'd ask SO before considering it impossible.
|
34

(Edit: two previous answers removed, one for answering the question as it stood before edits and one for being, if not absolutely wrong, at least close to it.)

If you compile with debug information on (javac -g), the names of local variables are kept in the .class file. For example, take this simple class:

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

After compiling with javac -g:vars TestLocalVarNames.java, the names of local variables are now in the .class file. javap's -l flag ("Print line number and local variable tables") can show them.

javap -l -c TestLocalVarNames shows:

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

The VM spec explains what we're seeing here:

§4.7.9 The LocalVariableTable Attribute:

The LocalVariableTable attribute is an optional variable-length attribute of a Code (§4.7.3) attribute. It may be used by debuggers to determine the value of a given local variable during the execution of a method.

The LocalVariableTable stores the names and types of the variables in each slot, so it is possible to match them up with the bytecode. This is how debuggers can do "Evaluate expression".

As erickson said, though, there's no way to access this table through normal reflection. If you're still determined to do this, I believe the Java Platform Debugger Architecture (JPDA) will help (but I've never used it myself).

4 Comments

Uh-oh, erickson posted while I was editing and now I'm contradicting him. Which probably means I'm wrong.
By default, javac puts a local variable table in the class for each method to assist debugging. Use the -l option to javap to see the local variable table.
Not by default, it seems. I had to use javac -g:vars to get it. (I've been trying to edit this answer for the past three hours, but as I said my network connection is having problems, which makes it hard to research.)
You're right, sorry about that. It's line numbers that are "on" by default.
19
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)); } } } 

The result of this program is

int i = 5 class java.lang.Integer test = 5 class java.lang.String omghi = der class java.lang.String testStatic = THIS IS STATIC 

1 Comment

getDeclaredFields() can be used in case you want private field’s names as well
14

You can do like this:

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

1 Comment

Your answer works fine to get all the fieads. To get only one field, when I use: YourClass.class.getDeclaredField("field1"); I get NullPointer. What is the problem in using it? How I should use getDeclaredField method?
0

All you need to do is make an array of fields and then set it to the class you want like shown below.

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

For example if you did

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

you would get

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 

Comments

0

update @Marcel Jackwerth's answer for general.

and only working with class attribute, not working with method variable.

/** * 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)); } } } 

Comments

-1

see this example :

PersonneTest pt=new PersonneTest(); System.out.println(pt.getClass().getDeclaredFields().length); Field[]x=pt.getClass().getDeclaredFields(); System.out.println(x[1].getName()); 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.