java 内部方法签名与构造函数参数不匹配 (javap)

java internal method signature doesn't match constructor parameters (javap)

我有以下 class 包含本地内部 class:

class Outer {
    private boolean beep;
    private int foo;

    public Outer(boolean beep) {
        this.beep = beep;
    }

    public void start(boolean beep) {
        class LocalInner {
            private LocalInner() {

            }
            public void method() {
                System.out.println("Test.");
                if (beep) {
                    System.out.println("beeping.");
                }
            }
        }

        LocalInner li = new LocalInner();
        li.method();
    }
}

当我编译 class javac Outer.class,然后用 javap -private Outer$1LocalClass.class 检查 OuterLocalInner 的编译成员时,我得到这个:

class OuterLocalInner {
  final boolean val$beep;
  final Outer this[=12=];
  OuterLocalInner();
  public void method();
}

我原以为构造函数将被编译为:OuterLocalInner(Outer, boolean)。当我试图查看字节码时 javap -c -s -private OuterLocalInner.class 我得到了这个:

class OuterLocalInner {
  final boolean val$beep;
    descriptor: Z
                                                                                                         
  final Outer this[=13=];
    descriptor: LOuter;
                                                                                                         
  OuterLocalInner();
    descriptor: (LOuter;Z)V
    Code:                    
       0: aload_0   
       1: aload_1
       2: putfield      #1                  // Field this[=13=]:LOuter;   
       5: aload_0
       6: iload_2
       7: putfield      #2                  // Field val$beep:Z
      10: aload_0                                                                                        
      11: invokespecial #3                  // Method java/lang/Object."<init>":()V
      14: return 
                                                                                                         
...
}

现在这很有趣!让我们仔细看看这两行:

  OuterLocalInner();
    descriptor: (LOuter;Z)V
  1. 为什么 OuterLocalInner() 构造函数没有参数,但我可以在方法描述符中看到它确实接受两个参数,正如我所期望的 Outerboolean?

  2. 为什么编译器会忽略local inner class的访问修饰符?我声明它是私有的,但反汇编版本是包修饰符。

  1. 相信描述符。 javap 可能没有扩展上面一行中的 ctor 参数,因为它知道这是一个内部 class 并且源代码没有声明这样的特殊参数。
  2. 这可能取决于您编译时所针对的 Java 版本。 Java 11 支持 nestmates,因此不需要降级到 package-private 修饰符。

好像,javac生成了一个Signature Attribute:

A Signature attribute stores a signature (§4.7.9.1) for a class, interface, constructor, method, field, or record component whose declaration in the Java programming language uses type variables or parameterized types.

这个目的不符合场景,因为这个本地 class 确实使用类型变量或参数化类型,但我们可以证明 javap 的行为符合。

例如,当我们运行javap -s java.util.function.Supplier时,我们得到

Compiled from "Supplier.java"
public interface java.util.function.Supplier<T> {
  public abstract T get();
    descriptor: ()Ljava/lang/Object;
}

显示 javap 打印通用类型系统看到的方法声明,同时打印 JVM 使用的描述符是第二行。这意味着在打印方法声明时使用 Signature 属性中的信息。

我们甚至可以强制 javap 打印 Signature 属性。
使用 javap -v java.util.function.Supplier:

[class declaration and constant pool omitted]
  public abstract T get();
    descriptor: ()Ljava/lang/Object;
    flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
    Signature: #8                           // ()TT;
}
Signature: #9                           // <T:Ljava/lang/Object;>Ljava/lang/Object;
SourceFile: "Supplier.java"
RuntimeVisibleAnnotations:
  0: #13()
    java.lang.FunctionalInterface

注意行 Signature: #8 // ()TT;

当我用你的例子 运行 javap -p -v my.test.OuterLocalInner 时,我得到

…
  private my.test.OuterLocalInner();
    descriptor: (Lmy/test/Outer;Z)V
    flags: (0x0002) ACC_PRIVATE
    Code:
      stack=2, locals=3, args_size=3
         0: aload_0
         1: aload_1
         2: putfield      #1                  // Field this[=12=]:Lmy/test/Outer;
         5: aload_0
         6: iload_2
         7: putfield      #7                  // Field val$beep:Z
        10: aload_0
        11: invokespecial #11                 // Method java/lang/Object."<init>":()V
        14: return
      LineNumberTable:
        line 31: 0
        line 33: 14
    Signature: #16                          // ()V
…

这与该方法具有 Signature 属性报告 ()V 导致 javap 打印不带参数的声明的理论一致。

使用这个属性来编码构造函数的源代码外观,即使它没有使用类型变量或泛型类型,也没有被提及作为这个属性的用途,Eclipse 的编译器不会生成这样的属性。

请注意 the documentation 还表示:

Oracle's Java Virtual Machine implementation does not check the well-formedness of Signature attributes during class loading or linking. Instead, Signature attributes are checked by methods of the Java SE Platform class libraries which expose generic signatures of classes, interfaces, constructors, methods, and fields.

所以这个属性的存在与否对代码的执行没有直接的影响。但是当我将以下代码附加到您的 start 方法的末尾时

try {
    Constructor<?> c = li.getClass().getDeclaredConstructor(Outer.class, boolean.class);
    System.out.println(Arrays.asList(c.getParameterTypes()));
    System.out.println(Arrays.asList(c.getGenericParameterTypes()));
} catch(ReflectiveOperationException ex) {}

它打印

[class my.test.Outer, boolean]
[class my.test.Outer, boolean]

当使用 Eclipse 编译并且

[class my.test.Outer, boolean]
[]

使用 javac 编译时,显示 getGenericParameterTypes() 解释此 Signature 属性。


上面的所有测试都是使用 JDK 17 进行的。自 JDK 11 起,外部 class 可以直接访问 private 构造函数,因此构造函数声明为private 被编译为 private.

在 JDK 11 之前,本地 class 的 private 修饰符被移除。这不同于非本地嵌套 classes,其中 javac 保留 private 修饰符并生成另一个非 private 委托构造函数。