获取方法类型信息
一个方法声明包括:方法名,描述符,参数,返回类型和异常。可以通过java.lang.reflect.Method
类获取这些信息。
下面的例子说明了如何获取一个类中所有的方法,根据名字获取方法的返回类型,参数,异常等。
import java.lang.reflect.Method; import java.lang.reflect.Type; import static java.lang.System.out; public class MethodSpy { private static final String fmt = "%24s: %s%n"; // for the morbidly curious <E extends RuntimeException> void genericThrow() throws E {} public static void main(String... args) { try { Class<?> c = Class.forName(args[0]); Method[] allMethods = c.getDeclaredMethods(); for (Method m : allMethods) { if (!m.getName().equals(args[1])) { continue; } out.format("%s%n", m.toGenericString()); out.format(fmt, "ReturnType", m.getReturnType()); out.format(fmt, "GenericReturnType", m.getGenericReturnType()); Class<?>[] pType = m.getParameterTypes(); Type[] gpType = m.getGenericParameterTypes(); for (int i = 0; i < pType.length; i++) { out.format(fmt,"ParameterType", pType[i]); out.format(fmt,"GenericParameterType", gpType[i]); } Class<?>[] xType = m.getExceptionTypes(); Type[] gxType = m.getGenericExceptionTypes(); for (int i = 0; i < xType.length; i++) { out.format(fmt,"ExceptionType", xType[i]); out.format(fmt,"GenericExceptionType", gxType[i]); } } // production code should handle these exceptions more gracefully } catch (ClassNotFoundException x) { x.printStackTrace(); } } }
运行:
$ java MethodSpy java.lang.Class getConstructor public java.lang.reflect.Constructor<T> java.lang.Class.getConstructor (java.lang.Class<?>[]) throws java.lang.NoSuchMethodException, java.lang.SecurityException ReturnType: class java.lang.reflect.Constructor GenericReturnType: java.lang.reflect.Constructor<T> ParameterType: class [Ljava.lang.Class; GenericParameterType: java.lang.Class<?>[] ExceptionType: class java.lang.NoSuchMethodException GenericExceptionType: class java.lang.NoSuchMethodException ExceptionType: class java.lang.SecurityException GenericExceptionType: class java.lang.SecurityException]
源码中方法定义public Constructor<T> getConstructor(Class<?>... parameterTypes)
。
首先注意,返回类型和参数类型是泛型类型。如果在class文件中提供了Signature Attribute Method.getGenericReturnType() 计算出其泛型类型。如果没有提供则使用Method.getReturnType(),这个并不会带有泛型信息。
其次注意,parameterTypes 是可变参数,类型是java.lang.Class 。可变参数列表被表示为一维数组。可以通过Method.isVarArgs() 来区别是否是可变参数。
如果返回值是泛型运行如下:
$ java MethodSpy java.lang.Class cast public T java.lang.Class.cast(java.lang.Object) ReturnType: class java.lang.Object GenericReturnType: T ParameterType: class java.lang.Object GenericParameterType: class java.lang.Object
对于重载函数则会显示所有的重载函数信息:
$ java MethodSpy java.io.PrintStream format public java.io.PrintStream java.io.PrintStream.format (java.util.Locale,java.lang.String,java.lang.Object[]) ReturnType: class java.io.PrintStream GenericReturnType: class java.io.PrintStream ParameterType: class java.util.Locale GenericParameterType: class java.util.Locale ParameterType: class java.lang.String GenericParameterType: class java.lang.String ParameterType: class [Ljava.lang.Object; GenericParameterType: class [Ljava.lang.Object; public java.io.PrintStream java.io.PrintStream.format (java.lang.String,java.lang.Object[]) ReturnType: class java.io.PrintStream GenericReturnType: class java.io.PrintStream ParameterType: class java.lang.String GenericParameterType: class java.lang.String ParameterType: class [Ljava.lang.Object; GenericParameterType: class [Ljava.lang.Object;]]]]
可以通过java.lang.reflect.Executable.getParameters()
方法获取方法的参数名,Method和Constructor都继承与java.lang.reflect.Executable,所有都可以使用这个函数。然后class文件默认并不保存方法这些信息。因为很多工具生成和获取class文件的时候都不会希望有大量的参数名字信息。尤其是这些工具处理大的class文件的时候,JVM将会使用更多的内存。另外,参数名,例如:secret或者password,可能会暴露一些安全敏感的信息。
可以在编译的时候使用-parameters 将参数名保存到class文件中。
下面的例子展示了如何获取参数信息:
import java.lang.reflect.*; import java.util.function.*; import static java.lang.System.out; public class MethodParameterSpy { private static final String fmt = "%24s: %s%n"; // for the morbidly curious <E extends RuntimeException> void genericThrow() throws E {} public static void printClassConstructors(Class c) { Constructor[] allConstructors = c.getConstructors(); out.format(fmt, "Number of constructors", allConstructors.length); for (Constructor currentConstructor : allConstructors) { printConstructor(currentConstructor); } Constructor[] allDeclConst = c.getDeclaredConstructors(); out.format(fmt, "Number of declared constructors", allDeclConst.length); for (Constructor currentDeclConst : allDeclConst) { printConstructor(currentDeclConst); } } public static void printClassMethods(Class c) { Method[] allMethods = c.getDeclaredMethods(); out.format(fmt, "Number of methods", allMethods.length); for (Method m : allMethods) { printMethod(m); } } public static void printConstructor(Constructor c) { out.format("%s%n", c.toGenericString()); Parameter[] params = c.getParameters(); out.format(fmt, "Number of parameters", params.length); for (int i = 0; i < params.length; i++) { printParameter(params[i]); } } public static void printMethod(Method m) { out.format("%s%n", m.toGenericString()); out.format(fmt, "Return type", m.getReturnType()); out.format(fmt, "Generic return type", m.getGenericReturnType()); Parameter[] params = m.getParameters(); for (int i = 0; i < params.length; i++) { printParameter(params[i]); } } public static void printParameter(Parameter p) { out.format(fmt, "Parameter class", p.getType()); out.format(fmt, "Parameter name", p.getName()); out.format(fmt, "Modifiers", p.getModifiers()); out.format(fmt, "Is implicit?", p.isImplicit()); out.format(fmt, "Is name present?", p.isNamePresent()); out.format(fmt, "Is synthetic?", p.isSynthetic()); } public static void main(String... args) { try { printClassConstructors(Class.forName(args[0])); printClassMethods(Class.forName(args[0])); } catch (ClassNotFoundException x) { x.printStackTrace(); } } }
编译时记得使用-parameter参数,运行 结果如下:
Number of constructors: 1 Constructor #1 public ExampleMethods() Number of declared constructors: 1 Declared constructor #1 public ExampleMethods() Number of methods: 4 Method #1 public boolean ExampleMethods.simpleMethod(java.lang.String,int) Return type: boolean Generic return type: boolean Parameter class: class java.lang.String Parameter name: stringParam Modifiers: 0 Is implicit?: false Is name present?: true Is synthetic?: false Parameter class: int Parameter name: intParam Modifiers: 0 Is implicit?: false Is name present?: true Is synthetic?: false Method #2 public int ExampleMethods.varArgsMethod(java.lang.String...) Return type: int Generic return type: int Parameter class: class [Ljava.lang.String; Parameter name: manyStrings Modifiers: 0 Is implicit?: false Is name present?: true Is synthetic?: false Method #3 public boolean ExampleMethods.methodWithList(java.util.List<java.lang.String>) Return type: boolean Generic return type: boolean Parameter class: interface java.util.List Parameter name: listParam Modifiers: 0 Is implicit?: false Is name present?: true Is synthetic?: false Method #4 public <T> void ExampleMethods.genericMethod(T[],java.util.Collection<T>) Return type: void Generic return type: void Parameter class: class [Ljava.lang.Object; Parameter name: a Modifiers: 0 Is implicit?: false Is name present?: true Is synthetic?: false Parameter class: interface java.util.Collection Parameter name: c Modifiers: 0 Is implicit?: false Is name present?: true Is synthetic?: false
Parameter 类的方法是用:
例如如果不是用-parameter 参数:
public boolean ExampleMethods.simpleMethod(java.lang.String,int) Return type: boolean Generic return type: boolean Parameter class: class java.lang.String Parameter name: arg0 Modifiers: 0 Is implicit?: false Is name present?: false Is synthetic?: false Parameter class: int Parameter name: arg1 Modifiers: 0 Is implicit?: false Is name present?: false Is synthetic?: false
某些构造函数是隐式声明在源码中的。例如ExampleMethods没有一个显式声明的构造函数则会默认一个隐式的构造函数。如:
Number of declared constructors: 1 public ExampleMethods()
对于内部函数:
public class MethodParameterExamples { public class InnerClass { } }
InnerClass是一个非静态的类,其内部也会有一个隐式的构造函数声明,但是这个隐式构造器会包含一个参数,如下,包含了其外围函数的引用:
public class MethodParameterExamples { public class InnerClass { final MethodParameterExamples parent; InnerClass(final MethodParameterExamples this$0) { parent = this$0; } } }
所以运行MethodParameterExample会得到如下结果:
public MethodParameterExamples$InnerClass(MethodParameterExamples) Parameter class: class MethodParameterExamples Parameter name: this$0 Modifiers: 32784 Is implicit?: true Is name present?: true Is synthetic?: false
如果一个构造器既不是显式声明的也不是隐式声明的难而是有Java编译器生成的则被标记为合成的。例如:
public class MethodParameterExamples { enum Colors { RED, WHITE; } }
对于enum java编译的时候会补充完其完整信息:
final class Colors extends java.lang.Enum<Colors> { public final static Colors RED = new Colors("RED", 0); public final static Colors BLUE = new Colors("WHITE", 1); private final static values = new Colors[]{ RED, BLUE }; private Colors(String name, int ordinal) { super(name, ordinal); } public static Colors[] values(){ return values; } public static Colors valueOf(String name){ return (Colors)java.lang.Enum.valueOf(Colors.class, name); } }
这也就是为什么enum不能继承类只能继承接口的原因,因为他已经击沉过了Enum类。
方法的修饰符有:
下例现实了如何获取修饰符信息:
import java.lang.reflect.Method; import java.lang.reflect.Modifier; import static java.lang.System.out; public class MethodModifierSpy { private static int count; private static synchronized void inc() { count++; } private static synchronized int cnt() { return count; } public static void main(String... args) { try { Class<?> c = Class.forName(args[0]); Method[] allMethods = c.getDeclaredMethods(); for (Method m : allMethods) { if (!m.getName().equals(args[1])) { continue; } out.format("%s%n", m.toGenericString()); out.format(" Modifiers: %s%n", Modifier.toString(m.getModifiers())); out.format(" [ synthetic=%-5b var_args=%-5b bridge=%-5b ]%n", m.isSynthetic(), m.isVarArgs(), m.isBridge()); inc(); } out.format("%d matching overload%s found%n", cnt(), (cnt() == 1 ? "" : "s")); // production code should handle this exception more gracefully } catch (ClassNotFoundException x) { x.printStackTrace(); } } }
运行结果如下:
$ java MethodModifierSpy java.lang.Object wait public final void java.lang.Object.wait() throws java.lang.InterruptedException Modifiers: public final [ synthetic=false var_args=false bridge=false ] public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException Modifiers: public final [ synthetic=false var_args=false bridge=false ] public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException Modifiers: public final native [ synthetic=false var_args=false bridge=false ] 3 matching overloads found $ java MethodModifierSpy java.lang.StrictMath toRadians public static double java.lang.StrictMath.toRadians(double) Modifiers: public static strictfp [ synthetic=false var_args=false bridge=false ] 1 matching overload found $ java MethodModifierSpy MethodModifierSpy inc private synchronized void MethodModifierSpy.inc() Modifiers: private synchronized [ synthetic=false var_args=false bridge=false ] 1 matching overload found $ java MethodModifierSpy java.lang.Class getConstructor public java.lang.reflect.Constructor<T> java.lang.Class.getConstructor (java.lang.Class<T>[]) throws java.lang.NoSuchMethodException, java.lang.SecurityException Modifiers: public transient [ synthetic=false var_args=true bridge=false ] 1 matching overload found $ java MethodModifierSpy java.lang.String compareTo public int java.lang.String.compareTo(java.lang.String) Modifiers: public [ synthetic=false var_args=false bridge=false ] public int java.lang.String.compareTo(java.lang.Object) Modifiers: public volatile [ synthetic=true var_args=false bridge=true ] 2 matching overloads found
可以使用java.lang.reflect.Method.invoke()方法调用反射获取的方法。第一个参数是被调用的对象实例,如果是static函数,第一个参数可以是null,接下来的参数是函数的参数。如果执行发生错误,会被java.lang.reflect.InvocationTargetException异常包装,函数原有异常可以功过InvocationTargetExcetpion.getCause()获取。
示例:
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.util.Locale; import static java.lang.System.out; import static java.lang.System.err; public class Deet<T> { private boolean testDeet(Locale l) { // getISO3Language() may throw a MissingResourceException out.format("Locale = %s, ISO Language Code = %s%n", l.getDisplayName(), l.getISO3Language()); return true; } private int testFoo(Locale l) { return 0; } private boolean testBar() { return true; } public static void main(String... args) { if (args.length != 4) { err.format("Usage: java Deet <classname> <langauge> <country> <variant>%n"); return; } try { Class<?> c = Class.forName(args[0]); Object t = c.newInstance(); Method[] allMethods = c.getDeclaredMethods(); for (Method m : allMethods) { String mname = m.getName(); if (!mname.startsWith("test") || (m.getGenericReturnType() != boolean.class)) { continue; } Type[] pType = m.getGenericParameterTypes(); if ((pType.length != 1) || Locale.class.isAssignableFrom(pType[0].getClass())) { continue; } out.format("invoking %s()%n", mname); try { m.setAccessible(true); Object o = m.invoke(t, new Locale(args[1], args[2], args[3])); out.format("%s() returned %b%n", mname, (Boolean) o); // Handle any exceptions thrown by method to be invoked. } catch (InvocationTargetException x) { Throwable cause = x.getCause(); err.format("invocation of %s failed: %s%n", mname, cause.getMessage()); } } // production code should handle these exceptions more gracefully } catch (ClassNotFoundException x) { x.printStackTrace(); } catch (InstantiationException x) { x.printStackTrace(); } catch (IllegalAccessException x) { x.printStackTrace(); } } }
Deet调用getDelcaredMethods()返回所有的方法。Class.isAssignableFrom() 用来检测参数类型和调用的方法的参数类型是否一致。因为Locale是final类型的,所以可以使用Local.class == pType[0].getClass()来测试,不过Class.isAssignableFrom()更常用。 运行结果:
$ java Deet Deet ja JP JP invoking testDeet() Locale = Japanese (Japan,JP), ISO Language Code = jpn testDeet() returned true $ java Deet Deet xx XX XX invoking testDeet() invocation of testDeet failed: Couldn't find 3-letter language code for xx
可变参数调用:
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; public class InvokeMain { public static void main(String... args) { try { Class<?> c = Class.forName(args[0]); Class[] argTypes = new Class[] { String[].class }; Method main = c.getDeclaredMethod("main", argTypes); String[] mainArgs = Arrays.copyOfRange(args, 1, args.length); System.out.format("invoking %s.main()%n", c.getName()); main.invoke(null, (Object)mainArgs); // production code should handle these exceptions more gracefully } catch (ClassNotFoundException x) { x.printStackTrace(); } catch (NoSuchMethodException x) { x.printStackTrace(); } catch (IllegalAccessException x) { x.printStackTrace(); } catch (InvocationTargetException x) { x.printStackTrace(); } } }
运行结果:
$ java InvokeMain Deet Deet ja JP JP invoking Deet.main() invoking testDeet() Locale = Japanese (Japan,JP), ISO Language Code = jpn testDeet() returned true
示例:
import java.lang.reflect.Method; public class MethodTrouble<T> { public void lookup(T t) {} public void find(Integer i) {} public static void main(String... args) { try { String mName = args[0]; Class cArg = Class.forName(args[1]); Class<?> c = (new MethodTrouble<Integer>()).getClass(); Method m = c.getMethod(mName, cArg); System.out.format("Found:%n %s%n", m.toGenericString()); // production code should handle these exceptions more gracefully } catch (NoSuchMethodException x) { x.printStackTrace(); } catch (ClassNotFoundException x) { x.printStackTrace(); } } }
运行结果:
$ java MethodTrouble lookup java.lang.Integer java.lang.NoSuchMethodException: MethodTrouble.lookup(java.lang.Integer) at java.lang.Class.getMethod(Class.java:1605) at MethodTrouble.main(MethodTrouble.java:12) $ java MethodTrouble lookup java.lang.Object Found: public void MethodTrouble.lookup(T)
因为方法声明中有反省类型,编译器会将会使用他的上界替换泛型类型,在这个例子中T的上界是Object。因此编译后无法找到lookup(Integer)的方法。
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; class AnotherClass { private void m() {} } public class MethodTroubleAgain { public static void main(String... args) { AnotherClass ac = new AnotherClass(); try { Class<?> c = ac.getClass(); Method m = c.getDeclaredMethod("m"); // m.setAccessible(true); // solution Object o = m.invoke(ac); // IllegalAccessException // production code should handle these exceptions more gracefully } catch (NoSuchMethodException x) { x.printStackTrace(); } catch (InvocationTargetException x) { x.printStackTrace(); } catch (IllegalAccessException x) { x.printStackTrace(); } } }
调用结果:
$ java MethodTroubleAgain java.lang.IllegalAccessException: Class MethodTroubleAgain can not access a member of class AnotherClass with modifiers "private" at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:65) at java.lang.reflect.Method.invoke(Method.java:588) at MethodTroubleAgain.main(MethodTroubleAgain.java:15)
因为访问修饰符是private所以导致无法调用。
示例:
import java.lang.reflect.Method; public class MethodTroubleToo { public void ping() { System.out.format("PONG!%n"); } public static void main(String... args) { try { MethodTroubleToo mtt = new MethodTroubleToo(); Method m = MethodTroubleToo.class.getMethod("ping"); switch(Integer.parseInt(args[0])) { case 0: m.invoke(mtt); // works break; case 1: m.invoke(mtt, null); // works (expect compiler warning) break; case 2: Object arg2 = null; m.invoke(mtt, arg2); // IllegalArgumentException break; case 3: m.invoke(mtt, new Object[0]); // works break; case 4: Object arg4 = new Object[0]; m.invoke(mtt, arg4); // IllegalArgumentException break; default: System.out.format("Test not found%n"); } // production code should handle these exceptions more gracefully } catch (Exception x) { x.printStackTrace(); } } }
运行结果:
$ java MethodTroubleToo 1 PONG! $ javac MethodTroubleToo.java MethodTroubleToo.java:16: warning: non-varargs call of varargs method with inexact argument type for last parameter; m.invoke(mtt, null); // works (expect compiler warning) ^ cast to Object for a varargs call cast to Object[] for a non-varargs call and to suppress this warning 1 warning $ java MethodTroubleToo 2 java.lang.IllegalArgumentException: wrong number of arguments at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke (NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke (DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at MethodTroubleToo.main(MethodTroubleToo.java:21) $ java MethodTroubleToo 3 PONG! $ java MethodTroubleToo 4 java.lang.IllegalArgumentException: wrong number of arguments at sun.reflect.NativeMethodAccessorImpl.invoke0 (Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke (NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke (DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at MethodTroubleToo.main(MethodTroubleToo.java:28)
示例:
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class MethodTroubleReturns { private void drinkMe(int liters) { if (liters < 0) throw new IllegalArgumentException("I can't drink a negative amount of liquid"); } public static void main(String... args) { try { MethodTroubleReturns mtr = new MethodTroubleReturns(); Class<?> c = mtr.getClass(); Method m = c.getDeclaredMethod("drinkMe", int.class); m.invoke(mtr, -1); // production code should handle these exceptions more gracefully } catch (InvocationTargetException x) { Throwable cause = x.getCause(); System.err.format("drinkMe() failed: %s%n", cause.getMessage()); } catch (Exception x) { x.printStackTrace(); } } }
运行结果:
$ java MethodTroubleReturns drinkMe() failed: I can't drink a negative amount of liquid
本文分享自微信公众号 - 代码拾遗(gh_8f61e8bcb1b1)
原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。
原始发表时间:2018-05-08
本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。
我来说两句