JDK 8中一個不多被說起的特性就是它能夠方法參數的元信息存儲到編譯完的class文件中(JEP 118)。這個特性使得Java程序能夠在運行時經過反射來獲取參數的元信息。 windows
在Java教程——反射API中有節課叫獲取方法參數的名字, 裏面講到了如何使用Java 8的這個新特性。這個教程提供了一個MethodParameterSpy類,你能夠用它來檢測某個Java類的方法參數的屬性。文中同時說起,將額外的 參數信息存儲在.class文件裏面是一個可選的特性,由於這會增長class文件的大小。它同時還指出,有些狀況下參數名內包含一些敏感信息,所以開發 人員並不但願將它保存到編譯後的.class文件中。 oracle
在Java 8中(Java 7之前使用javac -g有相似效果),使用javac編譯器的時候加上-parameters參數的話,會在生成的.class文件中額外存儲參數的元信息。當你輸入javac -help的時候,你會看到-parameters這個選項,就像下面的截圖中那樣。 ide
Oracle在javac的官方文檔中,講到了如何在運行時獲取這個額外的方法參數元信息:"將方法及構造方法的參數存儲在生成的.class文件 中,這樣能夠經過反射API java.lang.reflect.Executable.getParameters來獲取到這些信息"。下面的代碼片斷 (ParameterDisplayer類)將會展現這個功能。 工具
package dustin.examples.jdk8; import static java.lang.System.out; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Parameter; /** * Uses JDK 8 Parameter class to demonstrate metadata related to the parameters * of the methods and constructors of the provided class (includes private, * protected, and public methods, but does not include methods inherited from * parent classes; those classes should be individually submitted). * * @author Dustin */ public class ParameterDisplayer { private static void displayParametersMetadata(final String[] classesNames) { for (final String className : classesNames) { try { final Class clazz = Class.forName(className); // Get all class's declared methods (does not get inherited methods) final Method[] declaredMethods = clazz.getDeclaredMethods(); for (final Method method : declaredMethods) { writeHeader( "Method " + method.toGenericString() + " has " + method.getParameterCount() + " Parameters:"); int parameterCount = 0; final Parameter[] parameters = method.getParameters(); for (final Parameter parameter : parameters) { out.println( "\targ" + parameterCount++ + ": " + (parameter.isNamePresent() ? parameter.getName() : "Parameter Name not provided,") + (isParameterFinal(parameter) ? " IS " : " is NOT ") + "final, type " + parameter.getType().getCanonicalName() + ", and parameterized type of " + parameter.getParameterizedType() + " and " + (parameter.isVarArgs() ? "IS " : "is NOT ") + "variable." ); } } } catch (ClassNotFoundException cnfEx) { out.println("Unable to find class " + className); } } } private static void writeHeader(final String headerText) { out.println("\n=========================================================="); out.println("= " + headerText); out.println("=========================================================="); } /** * Indicate whether provided Parameter is final. * * @param parameter Parameter to be tested for 'final' modifier. * @return {@code true} if provided Parameter is 'final'. */ private static boolean isParameterFinal(final Parameter parameter) { return Modifier.isFinal(parameter.getModifiers()); } public static void main(final String[] arguments) { if (arguments.length < 1) { out.println("You must provide the fully qualified name of at least one class."); System.exit(-1); } displayParametersMetadata(arguments); } }
一開始我想用這個程序來測試下JDK裏的某個你們比較熟悉的類,不事後來想了下可能沒用,由於JDK的類編譯的時候應該是沒加-parameters選項。所以爲了演示的須要,我本身又簡單地寫了一個類。 測試
package dustin.examples.jdk8; import java.util.List; /** * Class with numerous methods intended to be used in demonstrating JDK 8's new * Parameter class. * * @author Dustin */ public class ManyMethods { public ManyMethods() {} private void addArrayOfStrings(String[] strings) {} private void addManyStrings(final String ... strings) {} private void addListOfStrings(final List<String> strings) {} @Override public String toString() { return "ManyMethods"; } }
下面兩個截圖演示的是沒加-parameters選項和加了這個選項的時候,用ParameterDisplayer來運行ManyMethods 類的結果。最明顯的區別就是,沒加-parameters選項的時候,參數名是沒法獲取到的。還有就是若是沒加這個選項,參數是不是final類型的是不 肯定的。若是沒加-parameters選項的話,無論參數是否是final類型的,Parameter.getModifiers()返回的結果都是不 包含final的。 spa
ParameterDisplayer類使用了Parameter.isNamePresent()來自動檢測參數名是否存在(是否用 -parameters選項編譯了)。不這麼作的話,若是參數名不存在,Parameter.getName()返回的是"arg"加上參數的序號(第一 個參數的話是arg0,第二個arg1,依次類推)。 unix
ManyMethods類裏有兩個方法都包含一個final類型的參數。只有當編譯的時候加上-parameters選項,才能經過Parameter.getModifiers()方法來正確的識別出是不是final類型。 code
一點題外話:Sun/Oracle的工具文檔中一般包含一個"windows"頁及"solaris"頁,後者一般用來講明如何在Linux /Unix的各個平臺上運行某個工具。我注意到在Java 8的文檔中這個已經變了。文檔仍然有一個windows版本,但Unix/Linux版本的URL如今標識的是"unix"了。爲了說明這點,下面列舉了 Java SE7和Java SE8裏javac工具的文檔頁的URL:
+http://docs.oracle.com/javase/8/docs/technotes/tools/windows/javac.html +http://docs.oracle.com/javase/8/docs/technotes/tools/unix/javac.html +http://docs.oracle.com/javase/7/docs/technotes/tools/windows/javac.html +http://docs.oracle.com/javase/7/docs/technotes/tools/solaris/javac.html
再回到Parameter類的話題上,值得注意的是存儲額外的參數元信息的.class文件的大小會增長。就上面這個ManyMethods類而言,.class文件從909字節增長到了961字節。
Constructor,和Method同樣,都繼承了Executable接口,所以Constructor類和Methods類同樣,也有一個 getParamaters方法。在Java 8裏,若是代碼編譯的時候增長了這個額外的信息的話,你能獲取到更詳細的關於方法參數的信息。