前言

在這篇文章 54 關於BeanUtils.copyProperties複製不生效 的問題思考的時候, 我曾經想過一個問題, 就是 MutableEntity 的 attr 的 properties 的可讀可寫, 是否和 MutableEntity 的方法聲明的順序有關係呢 ? (當然後面再仔細一看顯然是和方法的順序沒得關係, 因為是先讀取的getter, 然後再以getter為基準查詢setter) 

然後 我看了一下 對應的獲取方法列表的地方, 發現使用的是 java.lang.Class.getMethods 來獲取方法列表 

61 關於Class.getMethods獲取到的方法的順序_Access

 

然後在 getMethods 的註釋如下 

/**
     * Returns an array containing {@code Method} objects reflecting all
     * the public <em>member</em> methods of the class or interface represented
     * by this {@code Class} object, including those declared by the class
     * or interface and those inherited from superclasses and
     * superinterfaces.  Array classes return all the (public) member methods
     * inherited from the {@code Object} class.  The elements in the array
     * returned are not sorted and are not in any particular order.  This
     * method returns an array of length 0 if this {@code Class} object
     * represents a class or interface that has no public member methods, or if
     * this {@code Class} object represents a primitive type or void.
     *
     * <p> The class initialization method {@code <clinit>} is not
     * included in the returned array. If the class declares multiple public
     * member methods with the same parameter types, they are all included in
     * the returned array.
     *
     * <p> See <em>The Java Language Specification</em>, sections 8.2 and 8.4.
     *
     * @return the array of {@code Method} objects representing the
     * public methods of this class
     * @exception  SecurityException
     *             If a security manager, <i>s</i>, is present and any of the
     *             following conditions is met:
     *
     *             <ul>
     *
     *             <li> invocation of
     *             {@link SecurityManager#checkMemberAccess
     *             s.checkMemberAccess(this, Member.PUBLIC)} denies
     *             access to the methods within this class
     *
     *             <li> the caller's class loader is not the same as or an
     *             ancestor of the class loader for the current class and
     *             invocation of {@link SecurityManager#checkPackageAccess
     *             s.checkPackageAccess()} denies access to the package
     *             of this class
     *
     *             </ul>
     *
     * @since JDK1.1
     */
    @CallerSensitive
    public Method[] getMethods() throws SecurityException {
        // be very careful not to change the stack depth of this
        // checkMemberAccess call for security reasons
        // see java.lang.SecurityManager.checkMemberAccess
        checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);
        return copyMethods(privateGetPublicMethods());
    }

方法描述上面有這麼一句 : The elements in the array returned are not sorted and are not in any particular order. 

雖然説註釋説的是沒有特別的順序, 那麼在我們通常使用的 java編譯器 和 java虛擬機 中, 會不會有什麼規則呢 ?

 

 

我們這裏以 jdk1.7.40 為例來進行探討, 一下代碼截圖(測試代碼, javac, javap) 基於 jdk1.7.40,  hotspot 部分代碼截圖 基於 jdk9 

 

 

1. Class.getMethods 的實現 

從上面的 Class.getMethos 的實現可以看到, 就是調用了 privateGetPublicMethods 然後做了一份拷貝 

61 關於Class.getMethods獲取到的方法的順序_Access_02

Class. privateGetPublicMethods 的實現如上圖, 其實就是拿 自己的public方法, 拿基類, 實現的接口的 public 方法(遞歸), 然後後面做了一些 額外的操作就返回了  

 

Class.privateGetDeclaredMethods(boolean) 的方法實現如下 

61 關於Class.getMethods獲取到的方法的順序_Access_03

getDeclaredMethods0 是一個 native 方法 

 

查看 Class 中註冊的本地方法信息, 這個 native 方法對應的實現是 JVM_GetClassDeclaredMethods

61 關於Class.getMethods獲取到的方法的順序_java_04

 

JVM_GetClassDeclaredMethods 的實現如下 

61 關於Class.getMethods獲取到的方法的順序_java_05

61 關於Class.getMethods獲取到的方法的順序_#java_06

獲取當前類對應的 instanceKlass 的方法列表, 然後採集目標方法, 入參有 want_constructor, publicOnly 進行過濾 

然後截圖未截取完畢的部分就是根據方法索引獲取方法封裝 java.lang.reflect.Method 返回 

那麼這裏看來 返回的方法列表其實就依賴於 instanceKlass.methods 的順序了 ?

 

 

2. instanceKlass.method的順序 ?

那麼 instanceKlass.method 又是由什麼決定的呢 ?? 

我們來到 創建並初始化 instanceKlass 的地方 

61 關於Class.getMethods獲取到的方法的順序_#method_07

原來這個 instanceKlass.methods 是來自於解析好的 classFileParser, 那麼我們看下 classFileParser 解析方法的這個部分呢 

 

61 關於Class.getMethods獲取到的方法的順序_#java_08

我們發現 這個是 methods 是直接讀取的 字節碼中按照順序讀取的方法, 那也就是説 instanceKlass.methods 的順序其實就是 字節碼中寫入的方法的順序呢 ??

 

 

3. class 中方法列表的寫出

一下調試代碼基於 : 52 一些關於java編譯器的問題 裏面的 Test11InitAndClinit

在 javac 寫出方法的時候, 是如下地方寫出的 

61 關於Class.getMethods獲取到的方法的順序_#sort_09

61 關於Class.getMethods獲取到的方法的順序_#method_10

可以看到這裏寫出是按照 methods 來寫出的, 然後 methods 依賴於 傳入的 Scope$Entry (c.members().elems) 

methods 的是 c.members().elems (基於sibling的單鏈表) 的逆序的排列 

 

4. c.members().elems 依賴什麼 ?

那麼 c.members().elems 又是怎麼被組織的呢 ? 

在 Scope.enter 裏面打一個條件斷點, 我們發現我們這裏的 c.members().elems 整理方式如下 

61 關於Class.getMethods獲取到的方法的順序_#java_11

這裏是在 memerEnter 階段, 遍歷了一下 tree.defs (Test11InitAndClinit的成員列表)

然後如果是變量定義, 方法定義 需要註冊到 tree.sym.members_field裏面去 

 

這裏的幾個變量, 方法的 enter 如下圖(依次序 : x, <init>(), <init>(int), main([LString;) )  

61 關於Class.getMethods獲取到的方法的順序_#method_12

61 關於Class.getMethods獲取到的方法的順序_Access_13

61 關於Class.getMethods獲取到的方法的順序_#java_14

61 關於Class.getMethods獲取到的方法的順序_#sort_15

 

然後這裏的 scope 又是如何和 tree.sym.members_field 關聯起來的呢 ? 

61 關於Class.getMethods獲取到的方法的順序_Access_16

61 關於Class.getMethods獲取到的方法的順序_#sort_17

 

然後 如果你足夠仔細的話, 你會發現一個問題, 圖1 裏面不是還有一個 <clinit> 麼 ?, 怎麼以上的截圖裏面沒有呢 ? 

在 Gen.genClass 裏面有一個 normalizeDefs 會重組構造方法 和 類初始化方法 

相關擴展可以參見這篇文章 : 52 一些關於java編譯器的問題

61 關於Class.getMethods獲取到的方法的順序_java_18

 

從上面 Scope.enter 的代碼可以發現, 這個單鏈表的添加元素的方式是 新來的節點作為頭結點, 鏈接原有的鏈表 

所以在  c.members().elems (基於sibling的單鏈表) 裏面的順序是 : <clinit>(), main([LString;), <init>(int), <init>(), x 

並且這裏 得到的答案是 c.members().elems 依賴於 tree.defs 

 

5. tree.defs 依賴什麼 ? 

tree.defs 又是怎麼來的呢 ?

61 關於Class.getMethods獲取到的方法的順序_#java_19

從上圖可以看到, 這是解析 java代碼 中的解析 類主體的部分實現, 可以看到 tree.defs 依賴的是源代碼中的編碼順序 

所以到這裏 為止 我們可以得出的結論是(可能會被打臉, 打臉後面再來修正) : 字節碼中的方法的順序是, java代碼 中方法的順序, 有一部分 方法比如這裏的 <clinit> 是加在最後的

比如 編譯器自動添加的 無參構造方法是加在 : 所有的定義的最前面 

61 關於Class.getMethods獲取到的方法的順序_java_20

其他的特殊的情況 可能需要根據實際情況討論吧 

 

6. javap 讀取 class 的方法的展示 

61 關於Class.getMethods獲取到的方法的順序_#sort_21

可以看到 依次序讀的, 那就是和 寫的時候的順序一致 

 

 

7. 理論上的東西我們上面提及了這麼多, 那麼實際泡一下?

測試代碼如下 

package com.hx.test03;

import java.lang.reflect.Method;

/**
 * MethodOrder
 *
 * @author Jerry.X.He
 * @version 1.0
 * @date 2020-02-28 12:07
 */
public class Test26MethodOrder {

  public Test26MethodOrder() {

  }

  public Test26MethodOrder(String str) {

  }

  protected Test26MethodOrder(int x) {

  }

  Test26MethodOrder(long x) {

  }

  private Test26MethodOrder(double x) {

  }

  // Test26MethodOrder
  public static void main(String[] args) {

//    Method[] methods = Test26MethodOrder.class.getMethods();
    Method[] declaredMethods = Test26MethodOrder.class.getDeclaredMethods();
//    Constructor[] constructors = Test26MethodOrder.class.getConstructors();
//    Constructor[] declaredConstructors = Test26MethodOrder.class.getDeclaredConstructors();

    for(Method method : declaredMethods) {
      System.out.println(method.getName());
    }
    System.out.println(" ending ... ");

  }

  // funcN
  public void func001() {

  }


  // funcN
  public static void func002() {

  }

  // funcN
  protected void func003() {

  }


  // funcN
  protected static void func004() {

  }

  // funcN
  void func005() {

  }


  // funcN
  static void func006() {

  }

  // funcN
  private void func007() {

  }


  // funcN
  private static void func008() {

  }

}

上面沒有使用 Class.getMethods, 使用的是 Class.getDeclaredMethods, 是因為後者效果更加明顯, 而且 重要的調用部分 和前者幾乎一致 

 

測試結果截圖如下 

61 關於Class.getMethods獲取到的方法的順序_java_22

61 關於Class.getMethods獲取到的方法的順序_#sort_23

wtf ??, 怎麼測試結果 和我們上面分析 一點關係都沒有.. !! 

哈哈 我其實也是走了一通上面的理論之後, 看到這個結果 有點詫異,, 所以 實踐是檢驗整理的唯一標準 啊, 沒有我們看起來那麼 "簡單"

 

 

8. classFileParser.post_process_parsed_stream方法的排序

在晚上搜索了一下, 看到了這樣一篇文章 OpenJDK中的JVM_GetClassDeclaredMethods方法返回順序 , 看了一下, 跟了一下 好像確實是忽略了這個呢(嘿嘿 加上這個之後, 不知道還沒有其他的影響因素, 先這樣吧, 後面發現再來附上)  

 

一下內容截取了一下 方法是如何排序的, 以及相關的棧幀信息 

61 關於Class.getMethods獲取到的方法的順序_java_24

61 關於Class.getMethods獲取到的方法的順序_java_25

61 關於Class.getMethods獲取到的方法的順序_#sort_26

可以發現 在 classFileParser 解析了class之後的 post_process 階段對方法進行了一次 排序, 排序的規則是 給定的方法的 name (類型為Symbol) 的地址比較大小 

 

解析常量池的時候, 會創建所有的 字符串 對應的 Symbol(我們這裏的方法名字對應的字符串 也在其中) 

61 關於Class.getMethods獲取到的方法的順序_#java_27

 

分配 Symbol 採用 new 分配 分配的地址就和分配策略有關係了, 這個 我們就不深究了(水平有限, 也深究不下去了)  

61 關於Class.getMethods獲取到的方法的順序_java_28

以上 這個問題就到這裏, 後面有了新的發現 再附上來

以上也凸顯了一些問題, 只看理論 和 實際調試起來 是兩碼事情, 如果是我能先跑兩次程序, 也就不用去跟蹤 javac 這這一方的代碼了, 也會少走一些彎路 

 

 

完 

 

 

參考 

OpenJDK中的JVM_GetClassDeclaredMethods方法返回順序

https://www.jianshu.com/p/16b97baae1ca

jls 8.2. Class Members

https://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.2

jvms Chapter 4. The class File Format

https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.1