跳至主要內容

反射(二)

guodongAndroid大约 10 分钟

前言

上一篇文章 中我们学习了与反射相关的 Type 知识。在 Retrofit 中与反射相关的主要是 Method,所以本文学习反射的重点在于 Method 反射相关的知识点。

在学习 Method 之前,我们先看下如何获取 Method

获取Method

Method 可以通过 Class 对象中以下几个方法获取:

返回值方法名称与描述
Method[]getDeclaredMethods()
返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。
Method[]getMethods()
返回 Method 对象的一个数组,这些对方反映此 Class 对象表示的类或接口声明的所有公共方法,包括类或接口声明的方法以及从父类和父接口继承的方法。
MethodgetDeclaredMethod(String name, Class<?>... parameterTypes)
返回一个 Method 对象,该对象反映此 Class 对象所表示的类或接口的指定声明方法。
MethodgetMethod(String name, Class<?>... parameterTypes)
返回一个 Method 对象,该对象反映此 Class 对象表示的类或接口的指定公共成员方法。
MethodgetEnclosingMethod()
如果此 Class 对象表示方法中的本地或匿名类,则返回 Method 对象,表示基础类的直接封闭方法。否则返回 null。

测试

创建父类:

public class TestParent {
    public void parent() {
        parent2();
    }

    public void parent2() {
    }
}

创建测试类:

public class Test extends TestParent implements Runnable {

  @Override
  public void run() {
  }

  @Override
  public void parent() {
  }

  private Object object() {
    class ClassA {}
    return new ClassA();
  }

  public void testPublic() {
    System.out.println("test");
  }

  protected void testProtected() {
    System.out.println("testProtected");
  }

  private void testPrivate() {
    System.out.println("testPrivate");
  }

  public static void main(String[] args) throws Exception {
    Test test = new Test();
    Class<? extends Test> testClass = test.getClass();
    Method[] declaredMethods = testClass.getDeclaredMethods();
    System.out.println("declaredMethods: " + Arrays.toString(declaredMethods));

    Method[] methods = testClass.getMethods();
    System.out.println("methods: " + Arrays.toString(methods));

    Method testPrivate = testClass.getDeclaredMethod("testPrivate");
    System.out.println("testPrivate: " + testPrivate);

    Method run = testClass.getMethod("run");
    System.out.println("run: " + run);

    Method enclosingMethod = test.object().getClass().getEnclosingMethod();
    System.out.println("enclosingMethod: " + enclosingMethod);
  }
}

程序输出结果如下:

declaredMethods: [public static void com.guodong.android.retrofit.Test.main(java.lang.String[]) throws java.lang.Exception, public void com.guodong.android.retrofit.Test.run(), public void com.guodong.android.retrofit.Test.parent(), protected void com.guodong.android.retrofit.Test.testProtected(), private void com.guodong.android.retrofit.Test.testPrivate(), public void com.guodong.android.retrofit.Test.testPublic(), private java.lang.Object com.guodong.android.retrofit.Test.object()]

methods: [public static void com.guodong.android.retrofit.Test.main(java.lang.String[]) throws java.lang.Exception, public void com.guodong.android.retrofit.Test.run(), public void com.guodong.android.retrofit.Test.parent(), public void com.guodong.android.retrofit.Test.testPublic(), public void com.guodong.android.retrofit.TestParent.parent2(), public final void java.lang.Object.wait() throws java.lang.InterruptedException, public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException, public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException, public boolean java.lang.Object.equals(java.lang.Object), public java.lang.String java.lang.Object.toString(), public native int java.lang.Object.hashCode(), public final native java.lang.Class java.lang.Object.getClass(), public final native void java.lang.Object.notify(), public final native void java.lang.Object.notifyAll()]

testPrivate: private void com.guodong.android.retrofit.Test.testPrivate()
run: public void com.guodong.android.retrofit.Test.run()
enclosingMethod: private java.lang.Object com.guodong.android.retrofit.Test.object()

小结

方法名称方法总结
getDeclaredMethods()返回当前 Class 对象声明的所有方法,不包含父类的方法。
getMethods()返回当前 Class 对象声明的所有公共方法,包含父类的方法。
getDeclaredMethod(String name, Class<?>... parameterTypes)返回当前 Class 对象声明的指定方法,不包含父类的方法。
getMethod(String name, Class<?>... parameterTypes)返回当前 Class 对象声明的指定公共方法,包含父类的方法。
getEnclosingMethod()返回构建本地和匿名类的方法。

在使用过程中,我们可以根据实际情况选择不同的获取 Method 的方式,比如想看看类自己声明了哪些方法,可以使用 getDeclaredMethods() 方法。

Method

Method 中的方法较多,我们只学习其中的某些方法,这些方法是 Retrofit 中使用的或者笔者认为需要学习的。

方法返回值

Method 中有三个方法可以获取方法返回值类型:

返回值方法名称与描述
Class<?>getReturnType()
返回 Class 对象,此对象表示方法的返回类型,不包含泛型信息。
TypegetGenericReturnType()
返回 Type 对象,此对象表示方法底层的正式返回类型,包含泛型信息。
AnnotatedTypegetAnnotatedReturnType()
返回 AnnotatedType 对象,此对象表示方法底层的正式返回类型,包含泛型信息。

测试代码:

public class Test {
    public int getInt() {
        return 1;
    }

    public <T> List<T> getT() {
        return new ArrayList<>();
    }

    public static void main(String[] args) throws Exception {
        Test test = new Test();
        Class<? extends Test> testClass = test.getClass();

        for (Method declaredMethod : testClass.getDeclaredMethods()) {
            System.out.println("MethodName: " + declaredMethod.getName());
            System.out.println("ReturnType: " + declaredMethod.getReturnType());
            System.out.println("GenericReturnType: " + declaredMethod.getGenericReturnType());
            System.out.println("AnnotatedReturnType: " + declaredMethod.getAnnotatedReturnType().getType());
            System.out.println("================================");
        }
    }
}

输出结果如下:

MethodName: main
ReturnType: void
GenericReturnType: void
AnnotatedReturnType: void
================================
MethodName: getInt
ReturnType: int
GenericReturnType: int
AnnotatedReturnType: int
================================
MethodName: getT
ReturnType: interface java.util.List
GenericReturnType: java.util.List<T>
AnnotatedReturnType: java.util.List<T>
================================

从输出结果可以看出,对于 getInt() 方法而言,它的返回值没有泛型,所以它的 ReturnTypeGenericReturnTypeAnnotatedReturnType 表现一致,而 getT() 方法它的返回值包含泛型,它的 ReturnType 返回的是泛型擦除后的 List 对象,而 GenericReturnTypeAnnotatedReturnType 返回了带有泛型信息真实的方法返回值类型 List<T>

方法参数

Method 中有六个方法与获取方法参数有关:

返回值方法名称与描述
intgetParameterCount()
获取方法参数个数。
Parameter[]getParameters()
返回 Parameter 对象的一个数组,这些对象表示方法参数的名称,修饰符等信息。
Class<?>[]getParameterTypes()
返回 Class 对象的一个数组,这些对象表示方法参数的类型,不包含泛型信息。
Type[]getGenericParameterTypes()
返回 Type 对象的一个数组,这些对象表示方法参数的实际类型,包含泛型信息。
AnnotatedType[]getAnnotatedParameterTypes()
返回 AnnotatedType 对象的一个数组,这些对象表示方法参数的实际类型,包含泛型信息。
Annotation[][]getParameterAnnotations()
返回 Annotation 对象的一个二维数组,这些对象表示了方法参数注解的类型。

创建注解类:

@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
}

测试代码:

public class Test {

    public <T> List<T> getT(int i, @MyAnnotation String s, T p) {
        return new ArrayList<>();
    }

    public static void main(String[] args) throws Exception {
        Test test = new Test();
        Class<? extends Test> testClass = test.getClass();
        test.getT(1, "2", "3");

        Method getT = testClass.getDeclaredMethod("getT", int.class, String.class, Object.class);

        System.out.println("MethodName: " + getT.getName());
        System.out.println("ParameterCount: " + getT.getParameterCount());
        System.out.println("Parameters: " + Arrays.toString(getT.getParameters()));
        System.out.println("ParameterTypes: " + Arrays.toString(getT.getParameterTypes()));
        System.out.println("GenericParameterTypes: " + Arrays.toString(getT.getGenericParameterTypes()));
        System.out.println("AnnotatedParameterTypes: " + Arrays.toString(Arrays.stream(getT.getAnnotatedParameterTypes()).map(AnnotatedType::getType).toArray()));
        System.out.println("ParameterAnnotations: " + Arrays.toString(Arrays.stream(getT.getParameterAnnotations()).flatMap(annotations -> Arrays.stream(annotations).map(Annotation::annotationType)).toArray()));
    }
}

输出结果如下:

MethodName: getT
ParameterCount: 3
Parameters: [int arg0, java.lang.String arg1, T arg2]
ParameterTypes: [int, class java.lang.String, class java.lang.Object]
GenericParameterTypes: [int, class java.lang.String, T]
AnnotatedParameterTypes: [int, class java.lang.String, T]
ParameterAnnotations: [interface com.guodong.android.retrofit.MyAnnotation]

从输出结果中可以比较直观的看出 getParameters()getParameterTypes()getGenericParameterTypes() 以及 getAnnotatedParameterTypes() 这四个方法返回值的不同,通过 getParameterAnnotations() 方法可以获取方法参数注解的信息。

方法注解

Method 中有六个方法与获取方法注解有关:

返回值方法名称与描述
Annotation[]getAnnotations()
返回 Annotation 对象数组,这些对象表示方法注解的类型,包含继承的注解。如果父类方法被子类重写,那么注解不会被继承,如果父类方法没有被子类重写,那么注解会被继承。
Annotation[]getDeclaredAnnotations()
返回 Annotation 对象数组,这些对象表示方法注解的类型,不包含继承的注解。
<T extends Annotation> TgetAnnotation(Class<T> annotationClass)
根据传入的注解 Class 对象返回 Annotation 对象,如果没有对应的注解则返回 null,此对象表示方法注解的类型,包含继承的注解。
<T extends Annotation> TgetDeclaredAnnotation(Class<T> annotationClass)
根据传入的注解 Class 对象返回 Annotation 对象,如果没有对应的注解则返回 null,此对象表示方法注解的类型,不包含继承的注解。
<T extends Annotation> T[]getAnnotationsByType(Class<T> annotationClass)
根据传入的注解 Class 对象返回 Annotation 对象数组,如果没有对应的注解则返回空的数组,这些对象表示方法注解的类型,包含继承的注解。JDK 1.8 新增,因 1.8 增加了 @Repeatable 注解。
<T extends Annotation> T[]getDeclaredAnnotationsByType(Class<T> annotationClass)
根据传入的注解 Class 对象返回 Annotation 对象数组,如果没有对应的注解则返回空的数组,这些对象表示方法注解的类型,不包含继承的注解。JDK 1.8 新增,因 1.8 增加了 @Repeatable 注解。

创建两个注解,一个在父类中使用,一个在子类中使用:

@Retention(RetentionPolicy.RUNTIME)
public @interface ParentAnnotation {
}

@Retention(RetentionPolicy.RUNTIME)
public @interface SubAnnotation {
}

创建父类,其中有两个方法 parent()parent2(),两个方法都有 @ParentAnnotation 注解标记:

public class TestParent {

    @ParentAnnotation
    public void parent() {
    }
    
    @ParentAnnotation
    public void parent2() {
    }
}

创建子类,在子类中重写了父类的 parent() 方法,而没有重写父类的 parent2() 方法,同时为子类的 parent() 方法增加 @GET@SubAnnotation 注解。

public class Test extends TestParent {

    @SubAnnotation
    @GET
    @Override
    public void parent() {
    }
}

测试代码,首先看下 parent() 方法的注解:

public static void main(String[] args) throws Exception {
    Test test = new Test();
    Class<? extends Test> testClass = test.getClass();

    // 获取`parent`方法
    Method parent = testClass.getMethod("parent");
    System.out.println("Annotations: " + Arrays.toString(Arrays.stream(parent.getAnnotations()).map(Annotation::annotationType).toArray()));
    System.out.println("DeclaredAnnotations: " + Arrays.toString(Arrays.stream(parent.getDeclaredAnnotations()).map(Annotation::annotationType).toArray()));

    System.out.println("============================================================================");

    System.out.println("getAnnotation-ParentAnnotation: " + parent.getAnnotation(ParentAnnotation.class));
    System.out.println("getDeclaredAnnotation-ParentAnnotation: " + parent.getDeclaredAnnotation(ParentAnnotation.class));

    System.out.println("getAnnotation-SubAnnotation: " + parent.getAnnotation(SubAnnotation.class));
    System.out.println("getDeclaredAnnotation-SubAnnotation: " + parent.getDeclaredAnnotation(SubAnnotation.class));

    System.out.println("============================================================================");

    System.out.println("getAnnotationsByType-ParentAnnotation: " + Arrays.toString(parent.getAnnotationsByType(ParentAnnotation.class)));
    System.out.println("getDeclaredAnnotationsByType-ParentAnnotation: " + Arrays.toString(parent.getDeclaredAnnotationsByType(ParentAnnotation.class)));

    System.out.println("getAnnotationsByType-SubAnnotation: " + Arrays.toString(parent.getAnnotationsByType(SubAnnotation.class)));
    System.out.println("getDeclaredAnnotationsByType-SubAnnotation: " + Arrays.toString(parent.getDeclaredAnnotationsByType(SubAnnotation.class)));
    System.out.println("============================================================================");
}

输出结果如下:

Annotations: [interface com.guodong.android.retrofit.SubAnnotation, interface retrofit2.http.GET]
DeclaredAnnotations: [interface com.guodong.android.retrofit.SubAnnotation, interface retrofit2.http.GET]
============================================================================
getAnnotation-ParentAnnotation: null
getDeclaredAnnotation-ParentAnnotation: null
getAnnotation-SubAnnotation: @com.guodong.android.retrofit.SubAnnotation()
getDeclaredAnnotation-SubAnnotation: @com.guodong.android.retrofit.SubAnnotation()
============================================================================
getAnnotationsByType-ParentAnnotation: []
getDeclaredAnnotationsByType-ParentAnnotation: []
getAnnotationsByType-SubAnnotation: [@com.guodong.android.retrofit.SubAnnotation()]
getDeclaredAnnotationsByType-SubAnnotation: [@com.guodong.android.retrofit.SubAnnotation()]
============================================================================

从输出结果可以看出:对于子类重写的方法,getAnnotations() 方法不会获取父类方法上的注解,这种情况 getAnnotations()getDeclaredAnnotations() 方法表现一致,同时注解没有被继承,所以 getAnnotation(ParentAnnotation.class) 获取不到 @ParentAnnotation 注解,但是可以获取到 @SubAnnotation 注解,同理, getAnnotationsByType()getDeclaredAnnotationsByType() 方法也是如此。

接下来,我们看下 parent2() 方法的注解,只需替换 testClass.getMethod("parent");testClass.getMethod("parent2"); 即可,输出结果如下:

Annotations: [interface com.guodong.android.retrofit.ParentAnnotation]
DeclaredAnnotations: [interface com.guodong.android.retrofit.ParentAnnotation]
============================================================================
getAnnotation-ParentAnnotation: @com.guodong.android.retrofit.ParentAnnotation()
getDeclaredAnnotation-ParentAnnotation: @com.guodong.android.retrofit.ParentAnnotation()
getAnnotation-SubAnnotation: null
getDeclaredAnnotation-SubAnnotation: null
============================================================================
getAnnotationsByType-ParentAnnotation: [@com.guodong.android.retrofit.ParentAnnotation()]
getDeclaredAnnotationsByType-ParentAnnotation: [@com.guodong.android.retrofit.ParentAnnotation()]
getAnnotationsByType-SubAnnotation: []
getDeclaredAnnotationsByType-SubAnnotation: []
============================================================================

从输出结果可以看出:对于子类没有重写的方法,可以获取到父类方法上的注解。

可重复注解

最后我们看下 @Repeatable 注解对各 getXXXAnnotationX 方法的影响,首先创建两个新的注解,一个标记为可重复注解 @Element,另一个为 @Element 的容器注解 @Array

@Retention(RetentionPolicy.RUNTIME)
public @interface Array {
    Element[] value() default {};
}

@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Array.class)
public @interface Element {
}

修改子类的 parent() 方法,新增 @Element 注解:

@Element
@Element
@SubAnnotation
@GET
@Override
public void parent() {
}

修改测试代码,新增获取 @Element@Array 注解:

System.out.println("getAnnotation-Array: " + parent.getAnnotation(Array.class));
System.out.println("getDeclaredAnnotation-Array: " + parent.getDeclaredAnnotation(Array.class));

System.out.println("getAnnotation-Element: " + parent.getAnnotation(Element.class));
System.out.println("getDeclaredAnnotation-Element: " + parent.getDeclaredAnnotation(Element.class));

System.out.println("getAnnotationsByType-Array: " + Arrays.toString(parent.getAnnotationsByType(Array.class)));
System.out.println("getDeclaredAnnotationsByType-Array: " + Arrays.toString(parent.getDeclaredAnnotationsByType(Array.class)));

System.out.println("getAnnotationsByType-Element: " + Arrays.toString(parent.getAnnotationsByType(Element.class)));
System.out.println("getDeclaredAnnotationsByType-Element: " + Arrays.toString(parent.getDeclaredAnnotationsByType(Element.class)));

看下输出结果:

Annotations: [interface com.guodong.android.retrofit.Array, interface com.guodong.android.retrofit.SubAnnotation, interface retrofit2.http.GET]
DeclaredAnnotations: [interface com.guodong.android.retrofit.Array, interface com.guodong.android.retrofit.SubAnnotation, interface retrofit2.http.GET]
============================================================================
getAnnotation-ParentAnnotation: null
getDeclaredAnnotation-ParentAnnotation: null
getAnnotation-SubAnnotation: @com.guodong.android.retrofit.SubAnnotation()
getDeclaredAnnotation-SubAnnotation: @com.guodong.android.retrofit.SubAnnotation()
getAnnotation-Array: @com.guodong.android.retrofit.Array(value=[@com.guodong.android.retrofit.Element(), @com.guodong.android.retrofit.Element()])
getDeclaredAnnotation-Array: @com.guodong.android.retrofit.Array(value=[@com.guodong.android.retrofit.Element(), @com.guodong.android.retrofit.Element()])
getAnnotation-Element: null
getDeclaredAnnotation-Element: null
============================================================================
getAnnotationsByType-ParentAnnotation: []
getDeclaredAnnotationsByType-ParentAnnotation: []
getAnnotationsByType-SubAnnotation: [@com.guodong.android.retrofit.SubAnnotation()]
getDeclaredAnnotationsByType-SubAnnotation: [@com.guodong.android.retrofit.SubAnnotation()]
getAnnotationsByType-Array: [@com.guodong.android.retrofit.Array(value=[@com.guodong.android.retrofit.Element(), @com.guodong.android.retrofit.Element()])]
getDeclaredAnnotationsByType-Array: [@com.guodong.android.retrofit.Array(value=[@com.guodong.android.retrofit.Element(), @com.guodong.android.retrofit.Element()])]
getAnnotationsByType-Element: [@com.guodong.android.retrofit.Element(), @com.guodong.android.retrofit.Element()]
getDeclaredAnnotationsByType-Element: [@com.guodong.android.retrofit.Element(), @com.guodong.android.retrofit.Element()]
============================================================================

从输出结果可以看出:

  1. getAnnotations()getDeclaredAnnotations() 方法没有返回 @Element 注解,而是 @Array 注解,
  2. getAnnotation(Array.class)getDeclaredAnnotation(Array.class) 方法也没有返回 @Element 注解,也是 @Array 注解,
  3. getAnnotation(Element.class)getDeclaredAnnotation(Element.class) 方法却返回了 null,这是不是说明 parent() 方法没有 @Element 注解呢?
  4. getAnnotationsByType(Array.class)getDeclaredAnnotationsByType(Array.class) 方法返回了 @Array 注解,
  5. getAnnotationsByType(Element.class)getDeclaredAnnotationsByType(Element.class) 方法返回了 @Element 注解,终于看到 @Element 注解了。

可重复注解小结:

  1. 对于可重复注解,有两个以上注解时,会被替换为容器注解,如上面的 @Element 注解,parent() 方法有两个 @Element 注解,那么编译后会替换为容器注解 @Array
  2. 对于可重复注解,要获取真实的 @Element 注解,只能通过 getDeclaredAnnotationsByType(Element.class) 方法获取。

总结

  1. Method 的学习,可以让我们更好的理解 Java 的设计思想,比如 Method 是如何抽象表示 Java 中的所有方法:方法参数的抽象,方法返回值的抽象以及方法注解的获取。
  2. Method 的学习,可以让我们更好的分析 Retrofit 是如何解析我们定义的接口方法,比如如何获取方法参数与方法参数注解,如何获取方法返回值,如何获取方法上的注解等。

反射相关的知识先学到这吧,目前关于反射的知识相信足以分析 Retrofit 相关的源码了,若是在分析时遇到新的知识点,我们再学习吧😃。

happy~