簡體   English   中英

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檢查Outer$1LocalInner的編譯成員時,我得到這個:

class Outer$1LocalInner {
  final boolean val$beep;
  final Outer this$0;
  Outer$1LocalInner();
  public void method();
}

我期望構造函數將被編譯為: Outer$1LocalInner(Outer, boolean) 當我試圖查看字節碼javap -c -s -private Outer$1LocalInner.class ,我得到了這個:

class Outer$1LocalInner {
  final boolean val$beep;
    descriptor: Z
                                                                                                         
  final Outer this$0;
    descriptor: LOuter;
                                                                                                         
  Outer$1LocalInner();
    descriptor: (LOuter;Z)V
    Code:                    
       0: aload_0   
       1: aload_1
       2: putfield      #1                  // Field this$0: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 
                                                                                                         
...
}

現在這很有趣:讓我們仔細看看這兩行:

  Outer$1LocalInner();
    descriptor: (LOuter;Z)V
  1. 為什么Outer$1LocalInner()構造函數沒有參數,但我可以在方法描述符中看到它確實接受兩個參數,因為我期待Outerboolean

  2. 為什么編譯器會忽略local inner class的訪問修飾符? 我聲明它是私有的,但反匯編版本是 package 修飾符。

看來, javac生成了一個Signature Attribute

Signature屬性存儲 class 接口、構造函數、方法、字段或記錄組件的簽名( §4.7.9.1 ),其在 Java 編程語言中的聲明使用類型變量或參數化類型。

這個目的與場景不匹配,因為這個本地 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.Outer$1LocalInner時,我得到

…
  private my.test.Outer$1LocalInner();
    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$0: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 的編譯器不會生成這樣一個屬性。

請注意, 文檔還說:

Oracle 的 Java 虛擬機實現在 class 加載或鏈接期間不檢查簽名屬性的格式是否正確。 相反,簽名屬性由 Java SE 平台 class 庫的方法檢查,這些庫公開類、接口、構造函數、方法和字段的通用簽名。

所以這個屬性的存在與否對代碼的執行沒有直接的影響。 但是當我append以下代碼結束你的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修飾符被刪除。這與非本地嵌套類不同,其中javac保留private修飾符並生成另一個非private委托構造函數。

  1. 相信描述符。 javap 可能沒有擴展上面一行中的 ctor 參數,因為它知道這是一個內部 class 並且源代碼沒有聲明這樣的特殊參數。
  2. 這可能取決於您編譯時所針對的 Java 版本。 Java 11 支持 nestmates,因此不需要降級為 package-private 修飾符。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM