深入理解 Java 反射:Method (成员方法)

灰太狼 2022-06-03 07:29 244阅读 0赞

Method 介绍

继承的方法(包括重载、重写和隐藏的)会被编译器强制执行,这些方法都无法反射。

因此,反射一个类的方法时不考虑父类的方法,只考虑当前类的方法。

每个方法都由 修饰符、返回值、参数、注解和抛出的异常组成。

java.lang.reflect.Method 方法为我们提供了获取上述部分的 API。
获取方法的信息

下面的代码演示了如何获得一个方法的 修饰符、返回值、参数、注解和抛出的异常 等信息:

public class MethodTypeSpy extends BaseTestClass {
private static final String fmt = “%24s: %s\n”;
private static final String HELLO_WORLD = “I’m cute shixin”;

  1. @Deprecated
  2. public static void main(String[] args) throws ClassNotFoundException {
  3. MethodTypeSpy methodTypeSpy = new MethodTypeSpy();
  4. Class<? extends MethodTypeSpy> cls = methodTypeSpy.getClass();
  5. printFormat("Class:%s \n", cls.getCanonicalName());
  6. Method[] declaredMethods = cls.getDeclaredMethods();
  7. for (Method declaredMethod : declaredMethods) {
  8. printFormat(fmt, "Method name", declaredMethod.getName()); //获得单独的方法名
  9. //获得完整的方法信息(包括修饰符、返回值、路径、名称、参数、抛出值)
  10. printFormat(fmt, "toGenericString", declaredMethod.toGenericString());
  11. int modifiers = declaredMethod.getModifiers(); //获得修饰符
  12. printFormat(fmt, "Modifiers", Modifier.toString(modifiers));
  13. System.out.format(fmt, "ReturnType", declaredMethod.getReturnType()); //获得返回值
  14. System.out.format(fmt, "getGenericReturnType", declaredMethod.getGenericReturnType());//获得完整信息的返回值
  15. Class<?>[] parameterTypes = declaredMethod.getParameterTypes(); //获得参数类型
  16. Type[] genericParameterTypes = declaredMethod.getGenericParameterTypes();
  17. for (int i = 0; i < parameterTypes.length; i++) {
  18. System.out.format(fmt, "ParameterType", parameterTypes[i]);
  19. System.out.format(fmt, "GenericParameterType", genericParameterTypes[i]);
  20. }
  21. Class<?>[] exceptionTypes = declaredMethod.getExceptionTypes(); //获得异常名称
  22. Type[] genericExceptionTypes = declaredMethod.getGenericExceptionTypes();
  23. for (int i = 0; i < exceptionTypes.length; i++) {
  24. System.out.format(fmt, "ExceptionTypes", exceptionTypes[i]);
  25. System.out.format(fmt, "GenericExceptionTypes", genericExceptionTypes[i]);
  26. }
  27. Annotation[] annotations = declaredMethod.getAnnotations(); //获得注解
  28. for (Annotation annotation : annotations) {
  29. System.out.format(fmt, "Annotation", annotation);
  30. System.out.format(fmt, "AnnotationType", annotation.annotationType());
  31. }
  32. }
  33. }

}

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21
  22. 22
  23. 23
  24. 24
  25. 25
  26. 26
  27. 27
  28. 28
  29. 29
  30. 30
  31. 31
  32. 32
  33. 33
  34. 34
  35. 35
  36. 36
  37. 37
  38. 38
  39. 39
  40. 40
  41. 41
  42. 42
  43. 43

查看当前类 MethodTypeSpy的方法 main() 的信息,运行结果:

Class:net.sxkeji.shixinandroiddemo2.test.reflection.MethodTypeSpy
Method name: main
toGenericString: public static void net.sxkeji.shixinandroiddemo2.test.reflection.MethodTypeSpy.main(java.lang.String[]) throws java.lang.ClassNotFoundException
Modifiers: public static
ReturnType: void
getGenericReturnType: void
ParameterType: class [Ljava.lang.String;
GenericParameterType: class [Ljava.lang.String;
ExceptionTypes: class java.lang.ClassNotFoundException
GenericExceptionTypes: class java.lang.ClassNotFoundException
Annotation: @java.lang.Deprecated()
AnnotationType: interface java.lang.Deprecated

Process finished with exit code 0

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14

获取方法的参数名称

从 JDK 1.8 开始,java.lang.reflect.Executable.getParameters 为我们提供了获取普通方法或者构造方法的名称的能力。

在 JDK 中 java.lang.reflect.Method 和 java.lang.reflect.Constructor 都继承自 Executable,因此它俩也有同样的能力。

然而在 Android SDK 中 Method, Constructor 继承自 AbstractMethod,无法获得方法的参数名:

public final class Method extends AbstractMethod implements GenericDeclaration, Member

  1. 1

你可以在 J2EE 环境下练习官方的 获取参数名称代码:

/*
* Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of Oracle or the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS
* IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

import java.lang.reflect.*;
import java.util.function.*;
import static java.lang.System.out;

public class MethodParameterSpy {

  1. private static final String fmt = "%24s: %s%n";
  2. // for the morbidly curious
  3. <E extends RuntimeException> void genericThrow() throws E {}
  4. public static void printClassConstructors(Class c) {
  5. Constructor[] allConstructors = c.getConstructors();
  6. out.format(fmt, "Number of constructors", allConstructors.length);
  7. for (Constructor currentConstructor : allConstructors) {
  8. printConstructor(currentConstructor);
  9. }
  10. Constructor[] allDeclConst = c.getDeclaredConstructors();
  11. out.format(fmt, "Number of declared constructors",
  12. allDeclConst.length);
  13. for (Constructor currentDeclConst : allDeclConst) {
  14. printConstructor(currentDeclConst);
  15. }
  16. }
  17. public static void printClassMethods(Class c) {
  18. Method[] allMethods = c.getDeclaredMethods();
  19. out.format(fmt, "Number of methods", allMethods.length);
  20. for (Method m : allMethods) {
  21. printMethod(m);
  22. }
  23. }
  24. public static void printConstructor(Constructor c) {
  25. out.format("%s%n", c.toGenericString());
  26. Parameter[] params = c.getParameters();
  27. out.format(fmt, "Number of parameters", params.length);
  28. for (int i = 0; i < params.length; i++) {
  29. printParameter(params[i]);
  30. }
  31. }
  32. public static void printMethod(Method m) {
  33. out.format("%s%n", m.toGenericString());
  34. out.format(fmt, "Return type", m.getReturnType());
  35. out.format(fmt, "Generic return type", m.getGenericReturnType());
  36. Parameter[] params = m.getParameters();
  37. for (int i = 0; i < params.length; i++) {
  38. printParameter(params[i]);
  39. }
  40. }
  41. public static void printParameter(Parameter p) {
  42. out.format(fmt, "Parameter class", p.getType());
  43. out.format(fmt, "Parameter name", p.getName());
  44. out.format(fmt, "Modifiers", p.getModifiers());
  45. out.format(fmt, "Is implicit?", p.isImplicit());
  46. out.format(fmt, "Is name present?", p.isNamePresent());
  47. out.format(fmt, "Is synthetic?", p.isSynthetic());
  48. }
  49. public static void main(String... args) {
  50. try {
  51. printClassConstructors(Class.forName(args[0]));
  52. printClassMethods(Class.forName(args[0]));
  53. } catch (ClassNotFoundException x) {
  54. x.printStackTrace();
  55. }
  56. }

}

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21
  22. 22
  23. 23
  24. 24
  25. 25
  26. 26
  27. 27
  28. 28
  29. 29
  30. 30
  31. 31
  32. 32
  33. 33
  34. 34
  35. 35
  36. 36
  37. 37
  38. 38
  39. 39
  40. 40
  41. 41
  42. 42
  43. 43
  44. 44
  45. 45
  46. 46
  47. 47
  48. 48
  49. 49
  50. 50
  51. 51
  52. 52
  53. 53
  54. 54
  55. 55
  56. 56
  57. 57
  58. 58
  59. 59
  60. 60
  61. 61
  62. 62
  63. 63
  64. 64
  65. 65
  66. 66
  67. 67
  68. 68
  69. 69
  70. 70
  71. 71
  72. 72
  73. 73
  74. 74
  75. 75
  76. 76
  77. 77
  78. 78
  79. 79
  80. 80
  81. 81
  82. 82
  83. 83
  84. 84
  85. 85
  86. 86
  87. 87
  88. 88
  89. 89
  90. 90
  91. 91
  92. 92
  93. 93
  94. 94
  95. 95
  96. 96
  97. 97
  98. 98
  99. 99
  100. 100
  101. 101
  102. 102
  103. 103

获取方法的修饰符

方法可以被以下修饰符修饰:

  1. 访问权限控制符:public, protected, private
  2. 限制只能有一个实例的:static
  3. 不允许修改的:final
  4. 抽象,要求子类重写:abstract
  5. 预防重入的同步锁:synchronized
  6. 用其他语言实现的方法:native
  7. 严格的浮点型强度:strictfp
  8. 注解

类似获取 Class 的修饰符,我们可以使用 “Method.getModifiers()方法获取当前成员变量的修饰符。
返回java.lang.reflect.Modifier“` 中定义的整形值。

举个例子:

public class MethodModifierSpy extends BaseTestClass {

  1. private final static String CLASS_NAME = "java.lang.String";
  2. public static void main(String[] args) {
  3. MethodModifierSpy methodModifierSpy = new MethodModifierSpy();
  4. Class<? extends MethodModifierSpy> cls = methodModifierSpy.getClass();
  5. printFormat("Class: %s \n\n", cls.getCanonicalName());
  6. Method[] declaredMethods = cls.getDeclaredMethods();
  7. for (Method declaredMethod : declaredMethods) {
  8. printFormat("\n\nMethod name: %s \n", declaredMethod.getName());
  9. printFormat("Method toGenericString: %s \n", declaredMethod.toGenericString());
  10. int modifiers = declaredMethod.getModifiers();
  11. printFormat("Method Modifiers: %s\n", Modifier.toString(modifiers));
  12. System.out.format("synthetic= %-5b, var_args= %-5b, bridge= %-5b \n"
  13. , declaredMethod.isSynthetic(), declaredMethod.isVarArgs(), declaredMethod.isBridge());
  14. }
  15. }
  16. public final void varArgsMethod(String... strings) {
  17. }

}

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21
  22. 22
  23. 23
  24. 24
  25. 25
  26. 26
  27. 27
  28. 28

运行结果:

Class: net.sxkeji.shixinandroiddemo2.test.reflection.MethodModifierSpy

Method name: main
Method toGenericString: public static void net.sxkeji.shixinandroiddemo2.test.reflection.MethodModifierSpy.main(java.lang.String[])
Method Modifiers: public static
synthetic= false, var_args= false, bridge= false

Method name: varArgsMethod
Method toGenericString: public final void net.sxkeji.shixinandroiddemo2.test.reflection.MethodModifierSpy.varArgsMethod(java.lang.String…)
Method Modifiers: public final transient
synthetic= false, var_args= true , bridge= false

Process finished with exit code 0

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16

注意:上面的最后一行可以看到,方法有三种类型:synthetic, varagrs, bridge。

下面介绍这三种方法类型:
synthetic method:合成方法

  1. 这个知识点主要学习自:http://www.oschina.net/code/snippet_2438265_54869

什么是合成方法呢?

首先需要理解一个概念:

  1. 对于 Java 编译器而言,内部类也会被单独编译成一个class文件。
  2. 那么原有代码中的相关属性可见性就难以维持,synthetic method也就是为了这个目的而生成的。生成的synthetic方法是包访问性的static方法.

还是有些抽象,举个例子:

public class Foo {
private Object baz = “Hello”;
private int get(){
return 1;
}
private class Bar {
private Bar() {
System.out.println(get());
}
}
}

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11

上面的代码中,Bar 访问了 Foo 的 private 方法 get()。

使用 javap -private Foo看一下:

public class Foo {
private java.lang.Object baz;
public Foo();
private int get();
static int access$000(Foo); //多出来的 synthetic 方法,为了在 Bar 中的这段代码 System.out.println(get());
}

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6

因此可以这么理解:

  1. Synthetic (合成)方法是由编译器产生的、源代码中没有的方法。
  2. 当内部类与外部类之前有互相访问 private 属性、方法时,编译器会在运行时为调用方创建一个 synthetic 方法。

合成方法主要创建于嵌套内部类中。

我们可以使用 Method.isSynthetic() 方法判断某个方法是否为 synthetic 。
varargs ( variable arguments) method:Java 可变参数方法

public void testVarargs(String… strings){
//…
}

  1. 1
  2. 2
  3. 3
  4. 创建时必须放在方法尾部,即一个方法只能有一个可变数组参数
  5. 调用时可以传入一个数组:
  6. testVarargs(new String[]{"shixin","zhang"});
  7. 也可以分别传入多个参数:
  8. testVarargs("shixin","zhang");

推荐使用后者。

我们可以使用 Method.isVarArgs() 方法判断某个方法包含可变参数 。
bridge method:桥接方法

  1. 这个知识点主要学习自:http://blog.csdn.net/mhmyqn/article/details/47342577

桥接方法是为了泛型的向前兼容提出的,不太熟悉泛型的同学可以查看《
Java 进阶巩固:深入理解 泛型》。

我们知道,为了兼容 JDK 1.5 以前的代码,泛型会在编译时被去除(泛型擦除),这时需要创建桥接方法。

举个例子:

/**
* @author Mikan
* @date 2015-08-05 16:22
*/
public interface SuperClass {

  1. T method(T param);

}

package com.mikan;

/**
* @author Mikan
* @date 2015-08-05 17:05
*/
public class SubClass implements SuperClass {
public String method(String param) {
return param;
}
}

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21

上面的代码创建了一个泛型接口和实现类。

实现类在运行时的字节码如下:

localhost:mikan mikan$ javap -c SubClass.class
Compiled from “SubClass.java”
public class com.mikan.SubClass implements com.mikan.SuperClass

发表评论

表情:
评论列表 (有 0 条评论,244人围观)

还没有评论,来说两句吧...

相关阅读