4

I try to find the reason for a strange effect with .class files. It seems like for interfaces the names of variables passed into a function are not listed, but in implementation classes they are. I stumbled across this effect while de-compiling some of my own class files with JD-Gui.

I checked this with these two files:

Person.java

public interface Person {
    public abstract void setName( String name );
    public void setAge( int age );
}

PersonImpl.java

public class PersonImpl implements Person {
    @Override
    public void setName(String name) {
        System.out.println("This is my name: " + name);
    }
    @Override
    public void setAge(int age) {
        System.out.println("This is my age: " + age);
    }
}

JD-Gui returns this when decompiling:

Interface shows only generic variable type info

Implementing class shows 'real' variable name

Using javap -verbose x.class I get similar results: the printed method signatures differ from interface to implementing class. One misses variable names like I specified them in my source, the other has them.

I tried to answer my question studying the Java Virtual Machine Specification but have to admit I failed finding my way through this document.

Is there a reason why this was designed that way ?

Edit:

Because of all the good answers I received I added some lines to my interface and implementation class in order to back the statements from the answer:s

Person.java

default public void yawn(int count) {
        for (int i = 1; i <= count; i++)
            System.out.println("uaaaaah ....");
    }

JD-Gui is able to determine the name of the parameter:

JD-Gui is able to show the name of the variable

JavaP is able to list it in the LocalVariableTable:

variable names as shown by javap

When I add an abstract method to the implementing class and make the whole class abstract (which I need to because it contains one abstract method) ...

PersonImpl.java

public abstract void setPlanet( String planet );

... then JD-Gui fails to decompile this class file. But fortunately javap still is able to dump the file. All methods that are not abstract keep their LocalVariableTable. And the abstract method has a signature but neither Code, nor Lines or even a LocalVariableTable (this was expected)

dump from javap for PersonImpls abstract method

Marged
  • 10,577
  • 10
  • 57
  • 99
  • 1
    Are you asking why the interface parameter(s) (`String paramString`) differ in name from the implementation parameter(s) (`String name`)? – Jonny Henly Jun 10 '15 at 20:12
  • @JonnyHenly I tried to express this, yes ;-) – Marged Jun 10 '15 at 20:13
  • This is more a guess than anything else but might it be due to the `abstract` keyword? When looking at this from a C++ point of view one could say: when defining a method prototype, the name of the parameter does not matter. Also it is [discouraged to use abstract on interface(-method)s](http://docs.oracle.com/javase/specs/jls/se7/html/jls-9.html#jls-9.1.1.1) – Turing85 Jun 10 '15 at 20:13
  • @Turing85 Good point. Because of this I added two methods, one with "abstract" and one without. Both (I don't have this on the screenshot) miss the variable name. – Marged Jun 10 '15 at 20:16
  • Similar to @Turing85 's comment, the interface's parameter names don't matter, just the parameter types. The interface is only there as a contract with the class to implement methods. The class' parameter names however, do matter due to scoping. – Jonny Henly Jun 10 '15 at 20:18
  • Aren't all interface methods public abstract anyways? – D. Ben Knoble Jun 10 '15 at 20:24
  • @BenKnoble that is, was the documentation says (look at the link i have posted) – Turing85 Jun 10 '15 at 20:28
  • @Turing85 next time I'll follow the link :P – D. Ben Knoble Jun 10 '15 at 20:30
  • Did some more testing. If your inteface defines a `default` method and you execute `javap -verbose ...`, you will see the variable name. Could you check with JD-Gui, whether it sees the name of a `default` method? – Turing85 Jun 10 '15 at 20:32
  • 1
    @Turing85 `javap -l -c` does show a `LocalVariableTable` for a default methods, and JD-Gui seems to show it as well. – Vivin Paliath Jun 10 '15 at 20:54
  • @Marged The `abstract` is superfluous and is discouraged. Method definitions are implicitly abstract in interfaces (unless you provide a default method). – Vivin Paliath Jun 10 '15 at 20:55

3 Answers3

4

There is actually nothing within the class file itself that stores the name of the method parameter. If you look at section 4.3.3, you'll see the following definitions for MethodDescriptor:

A method descriptor represents the parameters that the method takes and the value that it returns:

MethodDescriptor:
    ( ParameterDescriptor* ) ReturnDescriptor
A parameter descriptor represents a parameter passed to a method:

ParameterDescriptor:
    FieldType

A return descriptor represents the type of the value returned from a method. It is a series of characters generated by the grammar:

ReturnDescriptor:
    FieldType
    VoidDescriptor

VoidDescriptor:
    V

The character V indicates that the method returns no value (its return type is void).

You can see this if you print out the bytecode for Person.class and PersonImpl.class using javap -c:

Compiled from "Person.java"
public interface Person {
  public abstract void setName(java.lang.String);

  public abstract void setAge(int);
}

Compiled from "PersonImpl.java"
public class PersonImpl implements Person {
  public PersonImpl();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public void setName(java.lang.String);
    Code:
       0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: new           #3                  // class java/lang/StringBuilder
       6: dup
       7: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
      10: ldc           #5                  // String This is my name:
      12: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      15: aload_1
      16: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      19: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      22: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      25: return

  public void setAge(int);
    Code:
       0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: new           #3                  // class java/lang/StringBuilder
       6: dup
       7: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
      10: ldc           #9                  // String This is my age:
      12: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      15: iload_1
      16: invokevirtual #10                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
      19: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      22: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      25: return
}

You can see that the signature for the method says nothing about the name of the parameter; only its type.

What I suspect is happening is that JD-Gui is probably using some sort of heuristic based on JavaBeans conventions to derive the name of the parameter. Since the name of the method is setName, it assumes that the name of the parameter is name. Try changing the name of the parameter to something other than name and see what JD-Gui prints out.

Debug information, like local variables, will show up if you compile using -g or -g:vars; it does not show up by default. These show up in the LocalVariableTable attribute. From section 4.7.13:

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

Notice the optional part; this is why you don't see it by default. Now if you look at section 4.7.3 for the Code attribute:

The Code attribute is a variable-length attribute in the attributes table of a method_info structure (§4.6). A Code attribute contains the Java Virtual Machine instructions and auxiliary information for a method, including an instance initialization method or a class or interface initialization method (§2.9).

If the method is either native or abstract, its method_info structure must not have a Code attribute in its attributes table. Otherwise, its method_info structure must have exactly one Code attribute in its attributes table.

Since interface method-definitions are effectively abstract (unless you use default methods), you won't see a LocalVariableTable entry for them. I used the latest version of JD-Gui against PersonImpl.class that was not compiled with -g, and found that it did not display name and age. Instead it displayed paramString and paramInt just like you saw for Person.class. However, if you do compile it with the -g flag, you will see name and age.

Vivin Paliath
  • 94,126
  • 40
  • 223
  • 295
  • 1
    I think that JD-Gui is using the `LocalVariableTable` to generate the names (execute `javap -verbose Class.class` and you will see this table). And since an Interface has no implementation, it has no `LocalVariableTable`. – Turing85 Jun 10 '15 at 20:23
  • @Turing85 Only if you compile with debug information (`-g` flag). Otherwise the table does not show up even if you use the `-l` flag with `javap`. – Vivin Paliath Jun 10 '15 at 20:33
3

It is due to the method being abstract in an interface. The information about names of method parameters is contained the LocalVariableTable attribute in the bytecode which is present in the Code attribute:

The LocalVariableTable attribute is an optional variable-length attribute in the attributes table of a Code (§4.7.3) attribute.

The Code attribute is defined as follows:

The Code attribute is a variable-length attribute in the attributes table of a method_info (§4.6) structure. A Code attribute contains the Java Virtual Machine instructions and auxiliary information for a single method, instance initialization method (§2.9), or class or interface initialization method (§2.9). Every Java Virtual Machine implementation must recognize Code attributes. If the method is either native or abstract, its method_info structure must not have a Code attribute. Otherwise, its method_info structure must have exactly one Code attribute.

M A
  • 71,713
  • 13
  • 134
  • 174
  • It appears that the table is only present if the `-g` flag was used during compilation, and does not exist by default. Then using `-l` with `javap` will show the table. Otherwise it doesn't show. Note it says that: *"The LocalVariableTable attribute is an **optional** variable-length attribute in the attributes table of a Code attribute (§4.7.3). It may be used by debuggers to determine the value of a given local variable during the execution of a method."* – Vivin Paliath Jun 10 '15 at 20:34
0

As explained by the other answers, the presence of a LocalVariableTable relies on the presence of a Code attribute and hence isn’t available for abstract methods. Note that Java 8 introduced an attribute for conserving parameter names which works independently to the debugging information. The creation of this attribute must be opted via compile-time flag:

Given your interface:

public interface Person {
    void setName(String name);
    void setAge(int age);
}
> javac Person.java

> javap -v Person
Classfile /C:/Users/pietsch/AppData/Local/Temp/Person.class
  Last modified 11.06.2015; size 159 bytes
  MD5 checksum 2fc084aa2f41b0b98e1417be7faeff8b
  Compiled from "Person.java"
public interface Person
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
Constant pool:
   #1 = Class              #9             // Person
   #2 = Class              #10            // java/lang/Object
   #3 = Utf8               setName
   #4 = Utf8               (Ljava/lang/String;)V
   #5 = Utf8               setAge
   #6 = Utf8               (I)V
   #7 = Utf8               SourceFile
   #8 = Utf8               Person.java
   #9 = Utf8               Person
  #10 = Utf8               java/lang/Object
{
  public abstract void setName(java.lang.String);
    descriptor: (Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_ABSTRACT

  public abstract void setAge(int);
    descriptor: (I)V
    flags: ACC_PUBLIC, ACC_ABSTRACT
}
SourceFile: "Person.java"
> javac -parameters Person.java

> javap -v Person
Classfile /C:/Users/pietsch/AppData/Local/Temp/Person.class
  Last modified 11.06.2015; size 213 bytes
  MD5 checksum 63dfd86ff035e339baf7b9e9ae65020f
  Compiled from "Person.java"
public interface Person
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
Constant pool:
   #1 = Class              #12            // Person
   #2 = Class              #13            // java/lang/Object
   #3 = Utf8               setName
   #4 = Utf8               (Ljava/lang/String;)V
   #5 = Utf8               MethodParameters
   #6 = Utf8               name
   #7 = Utf8               setAge
   #8 = Utf8               (I)V
   #9 = Utf8               age
  #10 = Utf8               SourceFile
  #11 = Utf8               Person.java
  #12 = Utf8               Person
  #13 = Utf8               java/lang/Object
{
  public abstract void setName(java.lang.String);
    descriptor: (Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_ABSTRACT
    MethodParameters:
      Name                           Flags
      name

  public abstract void setAge(int);
    descriptor: (I)V
    flags: ACC_PUBLIC, ACC_ABSTRACT
    MethodParameters:
      Name                           Flags
      age
}
SourceFile: "Person.java"

I don’t know whether JD-Gui is capable of using this information.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • can you tell me which compile-time flag this is ? – Marged Jun 11 '15 at 15:05
  • I thought I did. When running `javac` on the command line, as shown in my second console output, the compiler option is `-parameters`. IDEs might have associated toggles, e.g. in Eclipse the checkbox is labelled “Store information about method parameters (usable via reflection)”… – Holger Jun 11 '15 at 18:05