Java 编程问题:七、Java 反射类、接口、构造器、方法和字段2

简介: Java 编程问题:七、Java 反射类、接口、构造器、方法和字段2

Java 编程问题:七、Java 反射类、接口、构造器、方法和字段1https://developer.aliyun.com/article/1426147

从 JAR 实例化类

假设我们在D:/Java Modern Challenge/Code/lib/文件夹中有一个 Guava JAR,我们想创建一个CountingInputStream的实例并从一个文件中读取一个字节。

首先,我们为番石榴罐子定义一个URL[]数组,如下所示:

URL[] classLoaderUrls = new URL[] {
  new URL(
    "file:///D:/Java Modern Challenge/Code/lib/guava-16.0.1.jar")
};

然后,我们将为这个URL[]数组定义URLClassLoader

URLClassLoader urlClassLoader = new URLClassLoader(classLoaderUrls);

接下来,我们将加载目标类(CountingInputStream是一个计算从InputStream读取的字节数的类):

Class<?> cisClass = urlClassLoader.loadClass(
  "com.google.common.io.CountingInputStream");

一旦目标类被加载,我们就可以获取它的构造器(CountingInputStream有一个单独的构造器包装给定的InputStream

Constructor<?> constructor 
  = cisClass.getConstructor(InputStream.class);

此外,我们还可以通过这个构造器创建一个CountingInputStream的实例:

Object instance = constructor.newInstance(
  new FileInputStream(Path.of("test.txt").toFile()));

为了确保返回的实例是可操作的,我们调用它的两个方法(read()方法一次读取一个字节,而getCount()方法返回读取的字节数):

Method readMethod = cisClass.getMethod("read");
Method countMethod = cisClass.getMethod("getCount");

接下来,让我们读一个字节,看看getCount()返回什么:

readMethod.invoke(instance);
Object readBytes = countMethod.invoke(instance);
System.out.println("Read bytes (should be 1): " + readBytes); // 1

有用的代码片段

作为奖励,让我们看看在使用反射和构造器时通常需要的几个代码片段。

首先,让我们获取可用构造器的数量:

Class<Car> clazz = Car.class;
Constructor<?>[] cnstrs = clazz.getConstructors();
System.out.println("Car class has " 
  + cnstrs.length + " constructors"); // 4

现在,让我们看看这四个构造器中有多少个参数:

for (Constructor<?> cnstr : cnstrs) {
  int paramCount = cnstr.getParameterCount();
  System.out.println("\nConstructor with " 
    + paramCount + " parameters");
}

为了获取构造器的每个参数的详细信息,我们可以调用Constructor.getParameters()。该方法返回Parameter数组(JDK8 中添加了该类,提供了解剖参数的综合方法列表):

for (Constructor<?> cnstr : cnstrs) {
  Parameter[] params = cnstr.getParameters();
  ...
}

如果我们只需要知道参数的类型,那么Constructor.getParameterTypes()将完成以下工作:

for (Constructor<?> cnstr : cnstrs) {
  Class<?>[] typesOfParams = cnstr.getParameterTypes();
  ...
}

152 获取接收器类型的注解

从 JDK8 开始,我们可以使用显式的接收器参数。这主要意味着我们可以声明一个实例方法,该实例方法使用thisJava 关键字获取封闭类型的参数。

通过显式的接收器参数,我们可以将类型注解附加到this。例如,假设我们有以下注解:

@Target({ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Ripe {}

我们用它来注解Melon类的eat()方法中的this

public class Melon {
  ...
  public void eat(@Ripe Melon this) {}
  ...
}

也就是说,只有当Melon的实例代表一个成熟的瓜时,我们才能调用eat()方法:

Melon melon = new Melon("Gac", 2000);
// works only if the melon is ripe
melon.eat();

通过 JDK8,采用java.lang.reflect.Executable.getAnnotatedReceiverType()方法,可以在显式接收器参数上进行反射注解。该方法在ConstructorMethod类中也有,因此可以这样使用:

Class<Melon> clazz = Melon.class;
Method eatMethod = clazz.getDeclaredMethod("eat");
AnnotatedType annotatedType = eatMethod.getAnnotatedReceiverType();
// modern.challenge.Melon
System.out.println("Type: " + annotatedType.getType().getTypeName());
// [@modern.challenge.Ripe()]
System.out.println("Annotations: " 
  + Arrays.toString(annotatedType.getAnnotations()));
// [interface java.lang.reflect.AnnotatedType]
System.out.println("Class implementing interfaces: " 
  + Arrays.toString(annotatedType.getClass().getInterfaces()));
AnnotatedType annotatedOwnerType 
  = annotatedType.getAnnotatedOwnerType();
// null
System.out.println("\nAnnotated owner type: " + annotatedOwnerType);

153 获得合成和桥接构造

通过使用合成构造,我们几乎可以理解编译器添加的任何构造。更确切地说,符合 Java 语言规范:Java 编译器引入的任何构造,如果在源代码中没有对应的构造,则必须标记为合成,除了默认构造器、类初始化方法以及Enum类的valueOf()方法和values

有不同种类的合成构造(例如,字段、方法和构造器),但是让我们看一个合成字段的示例。假设我们有以下类:

public class Melon {
  ...
  public class Slice {}
  ...
}

注意,我们有一个名为Slice的内部类。在编译代码时,编译器将通过添加一个用于引用顶级类的合成字段来更改此类。这个合成字段提供了从嵌套类访问封闭类成员的便利。

为了检查这个合成字段的存在,让我们获取所有声明的字段并对它们进行计数:

Class<Melon.Slice> clazzSlice = Melon.Slice.class;
Field[] fields = clazzSlice.getDeclaredFields();
// 1
System.out.println("Number of fields: " + fields.length);

即使我们没有显式声明任何字段,也要注意报告了一个字段。让我们看看它是否是合成,看看它的名字:

// true
System.out.println("Is synthetic: " + fields[0].isSynthetic());
// this$0
System.out.println("Name: " + fields[0].getName());

与本例类似,我们可以通过Method.isSynthetic()Constructor.isSynthetic()方法检查方法或构造器是否是合成的

现在,我们来谈谈桥接方法。这些方法也是合成,它们的目标是处理泛型的类型擦除

考虑以下Melon类:

public class Melon implements Comparator<Melon> {
  @Override
  public int compare(Melon m1, Melon m2) {
    return Integer.compare(m1.getWeight(), m2.getWeight());
  }
  ...
}

在这里,我们实现Comparator接口并覆盖compare()方法。此外,我们明确规定了compare()方法需要两个Melon实例。编译器将继续执行类型擦除,并创建一个包含两个对象的新方法,如下所示:

public int compare(Object m1, Object m2) {
  return compare((Melon) m1, (Melon) m2);
}

这种方法被称为桥接方法。我们看不到,但是 Java 反射 API 可以:

Class<Melon> clazz = Melon.class;
Method[] methods = clazz.getDeclaredMethods();
Method compareBridge = Arrays.asList(methods).stream()
  .filter(m -> m.isSynthetic() && m.isBridge())
  .findFirst()
  .orElseThrow();
// public int modern.challenge.Melon.compare(
// java.lang.Object, java.lang.Object)
System.out.println(compareBridge);

154 检查参数的可变数量

在 Java 中,如果一个方法的签名包含一个varargs类型的参数,那么该方法可以接收数量可变的参数。

例如,plantation()方法采用可变数量的参数,例如,Seed... seeds

public class Melon {
  ...
  public void plantation(String type, Seed...seeds) {}
  ...
}

现在,Java 反射 API 可以通过Method.isVarArgs()方法判断这个方法是否支持可变数量的参数,如下所示:

Class<Melon> clazz = Melon.class;
Method[] methods = clazz.getDeclaredMethods();
for (Method method: methods) {
  System.out.println("Method name: " + method.getName() 
    + " varargs? " + method.isVarArgs());
}

您将收到类似以下内容的输出:

Method name: plantation, varargs? true
Method name: getWeight, varargs? false
Method name: toString, varargs? false
Method name: getType, varargs? false

155 检查默认方法

Java8 用default方法丰富了接口的概念。这些方法编写在接口内部,并有一个默认实现。例如,Slicer接口有一个默认方法,叫做slice()

public interface Slicer {
  public void type();
  default void slice() {
    System.out.println("slice");
  }
}

现在,Slicer的任何实现都必须实现type()方法,并且可以选择性地覆盖slice()方法或依赖于默认实现。

Java 反射 API 可以通过Method.isDefault()标志方法识别default方法:

Class<Slicer> clazz = Slicer.class;
Method[] methods = clazz.getDeclaredMethods();
for (Method method: methods) {
  System.out.println("Method name: " + method.getName() 
    + ", is default? " + method.isDefault());
}

我们将收到以下输出:

Method name: type, is default? false
Method name: slice, is default? true

156 基于反射的嵌套访问控制

在 JDK11 的特性中,我们有几个热点(字节码级别的变化)。其中一个热点被称为 JEP181,或者基于嵌套的访问控制NESTS)。基本上,NEST 术语定义了一个新的访问控制上下文,允许逻辑上属于同一代码实体的类,但是用不同的类文件编译的类,访问彼此的私有成员,而不需要编译器插入可访问性方法(第 11 页)

因此,换句话说,嵌套允许将嵌套类编译为属于同一封闭类的不同类文件。然后允许它们访问彼此的私有类,而无需使用合成/桥接方法。

让我们考虑以下代码:

public class Car {
  private String type = "Dacia";
  public class Engine {
    private String power = "80 hp";
    public void addEngine() {
      System.out.println("Add engine of " + power 
        + " to car of type " + type);
    }
  }
}

让我们在 JDK10 中为Car.class运行javap(Java 类文件反汇编工具,它允许我们分析字节码)。以下屏幕截图突出显示了此代码的重要部分:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sGQch3H0-1657284514765)(https://github.com/apachecn/apachecn-java-zh/raw/master/docs/java-coding-prob/img/e2d1c933-70ae-43e5-87b4-7e44b9ab0893.png)]

我们可以看到,为了从Engine.addEngine()方法访问封闭类字段Car.type,Java 修改了代码并添加了一个桥接package-private方法,称为access$000()。主要是综合生成的,可以通过Method.isSynthetic()Method.isBridge()方法反射看到。

即使我们看到(或感知到)Car(外部)和Engine(嵌套)类在同一个类中,它们也被编译到不同的文件(Car.classCar$Engine.class)。与此一致,我们的期望意味着外部类和嵌套类可以访问彼此的private成员。

但是在不同的文件中,这是不可能的。为了维持我们的期望,Java 增加了桥接packageprivate方法access$000()

然而,Java11 引入了嵌套访问控制上下文,它为外部类和嵌套类中的private访问提供支持。这一次,外部类和嵌套类被链接到两个属性,它们形成了一个嵌套(我们说它们是嵌套伙伴)。嵌套类主要链接到NestMembers属性,而外部类链接到NestHost属性。不产生额外的合成方法。

在下面的屏幕截图中,我们可以看到在 JDK11 中为Car.class执行javap(注意NestMembers属性):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YNfkbnKW-1657284514766)(https://github.com/apachecn/apachecn-java-zh/raw/master/docs/java-coding-prob/img/cadb2b26-b9ae-4ffa-bb61-49103cc8d62b.png)]

下面的屏幕截图显示了 JDK11 中针对Car$Engine.classjavap输出(注意NestHost属性):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zgZIhAMW-1657284514766)(https://github.com/apachecn/apachecn-java-zh/raw/master/docs/java-coding-prob/img/7b8c248d-5c48-4756-ab33-419f92a4a49b.png)]

通过反射 API 的访问

如果没有基于嵌套的访问控制,反射功能也会受到限制。例如,在 JDK11 之前,下面的代码片段将抛出IllegalAccessException

Car newCar = new Car();
Engine engine = newCar.new Engine();
Field powerField = Engine.class.getDeclaredField("power");
powerField.set(engine, power);

我们可以通过显式调用powerField.setAccessible(true)来允许访问:

...
Field powerField = Engine.class.getDeclaredField("power");
powerField.setAccessible(true);
powerField.set(engine, power);
...

从 JDK11 开始,不需要调用setAccessible()

此外,JDK11 还提供了三种方法,它们通过支持嵌套来丰富 Java 反射 API。这些方法是Class.getNestHost()Class.getNestMembers()Class.isNestmateOf()

让我们考虑下面的Melon类,其中包含几个嵌套类(SlicePeelerJuicer):

public class Melon {
  ...
  public class Slice {
    public class Peeler {}
  }
  public class Juicer {}
  ...
}

现在,让我们为它们中的每一个定义一个Class

Class<Melon> clazzMelon = Melon.class;
Class<Melon.Slice> clazzSlice = Melon.Slice.class;
Class<Melon.Juicer> clazzJuicer = Melon.Juicer.class;
Class<Melon.Slice.Peeler> clazzPeeler = Melon.Slice.Peeler.class;

为了查看每个类的NestHost,我们需要调用Class.getNestHost()

// class modern.challenge.Melon
Class<?> nestClazzOfMelon = clazzMelon.getNestHost();
// class modern.challenge.Melon
Class<?> nestClazzOfSlice = clazzSlice.getNestHost();
// class modern.challenge.Melon
Class<?> nestClazzOfPeeler = clazzPeeler.getNestHost();
// class modern.challenge.Melon
Class<?> nestClazzOfJuicer = clazzJuicer.getNestHost();

这里应该强调两点。首先,注意MelonNestHostMelon本身。第二,注意PeelerNestHostMelon,而不是Slice。由于PeelerSlice的一个内部类,我们可以认为它的NestHostSlice,但这个假设是不成立的。

现在,让我们列出每个类的NestMembers

Class<?>[] nestMembersOfMelon = clazzMelon.getNestMembers();
Class<?>[] nestMembersOfSlice = clazzSlice.getNestMembers();
Class<?>[] nestMembersOfJuicer = clazzJuicer.getNestMembers();
Class<?>[] nestMembersOfPeeler = clazzPeeler.getNestMembers();

它们将返回相同的NestMembers

[class modern.challenge.Melon, class modern.challenge.Melon$Juicer, class modern.challenge.Melon$Slice, class modern.challenge.Melon$Slice$Peeler]

最后,让我们检查一下嵌套伙伴

boolean melonIsNestmateOfSlice 
  = clazzMelon.isNestmateOf(clazzSlice);  // true
boolean melonIsNestmateOfJuicer 
  = clazzMelon.isNestmateOf(clazzJuicer); // true
boolean melonIsNestmateOfPeeler 
  = clazzMelon.isNestmateOf(clazzPeeler); // true
boolean sliceIsNestmateOfJuicer 
  = clazzSlice.isNestmateOf(clazzJuicer); // true
boolean sliceIsNestmateOfPeeler 
  = clazzSlice.isNestmateOf(clazzPeeler); // true
boolean juicerIsNestmateOfPeeler 
  = clazzJuicer.isNestmateOf(clazzPeeler); // true

157 读写器的反射

简单提醒一下,获取器和设置器是用于访问类的字段(例如,private字段)的方法(也称为访问器)。

首先,让我们看看如何获取现有的获取器和设置器。稍后,我们将尝试通过反射生成缺少的获取器和设置器。

获取获取器和设置器

主要有几种通过反射获得类的获取器和设置器的解决方案。假设我们要获取以下Melon类的获取器和设置器:

public class Melon {
  private String type;
  private int weight;
  private boolean ripe;
  ...
  public String getType() {
    return type;
  }
  public void setType(String type) {
    this.type = type;
  }
  public int getWeight() {
    return weight;
  }
  public void setWeight(int weight) {
    this.weight = weight;
  }
  public boolean isRipe() {
    return ripe;
  }
  public void setRipe(boolean ripe) {
    this.ripe = ripe;
  }
  ...
}

让我们从一个通过反射(例如,通过Class.getDeclaredMethods())获取类的所有声明方法的解决方案开始。现在,循环Method[]并通过特定于获取器和设置器的约束对其进行过滤(例如,从get/set前缀开始,返回void或某个类型,等等)。

另一种解决方案是通过反射(例如,通过Class.getDeclaredFields())获取类的所有声明字段。现在,循环Field[]并尝试通过Class.getDeclaredMethod()将字段的名称(前缀为get/set/is和第一个大写字母)和字段的类型(对于设置器)传递给它来获得获取器和设置器。

最后,一个更优雅的解决方案将依赖于PropertyDescriptorIntrospectorapi。这些 API 在java.beans.*包中提供,专门用于处理 JavaBeans。

这两个类暴露的许多特征依赖于场景背后的反射。

PropertyDescriptor类可以通过getReadMethod()返回用于读取 JavaBean 属性的方法。此外,它还可以通过getWriteMethod()返回用于编写 JavaBean 属性的方法。依靠这两种方法,我们可以获取Melon类的获取器和设置器,如下所示:

for (PropertyDescriptor pd:
    Introspector.getBeanInfo(Melon.class).getPropertyDescriptors()) {
  if (pd.getReadMethod() != null && !"class".equals(pd.getName())) {
    System.out.println(pd.getReadMethod());
  }
  if (pd.getWriteMethod() != null && !"class".equals(pd.getName())) {
    System.out.println(pd.getWriteMethod());
  }
}

输出如下:

public boolean modern.challenge.Melon.isRipe()
public void modern.challenge.Melon.setRipe(boolean)
public java.lang.String modern.challenge.Melon.getType()
public void modern.challenge.Melon.setType(java.lang.String)
public int modern.challenge.Melon.getWeight()
public void modern.challenge.Melon.setWeight(int)

现在,假设我们有以下Melon实例:

Melon melon = new Melon("Gac", 1000);

在这里,我们要称之为getType()获取器:

// the returned type is Gac
Object type = new PropertyDescriptor("type",
  Melon.class).getReadMethod().invoke(melon);

现在,让我们称之为setWeight()设定者:

// set weight of Gac to 2000
new PropertyDescriptor("weight", Melon.class)
  .getWriteMethod().invoke(melon, 2000);

调用不存在的属性将导致IntrospectionException

try {
  Object shape = new PropertyDescriptor("shape",
      Melon.class).getReadMethod().invoke(melon);
  System.out.println("Melon shape: " + shape);
} catch (IntrospectionException e) {
  System.out.println("Property not found: " + e);
}

生成获取器和设置器

假设Melon有三个字段(typeweightripe),只定义type的获取器和ripe的设置器:

public class Melon {
  private String type;
  private int weight;
  private boolean ripe;
  ...
  public String getType() {
    return type;
  }
  public void setRipe(boolean ripe) {
    this.ripe = ripe;
  }
  ...
}

为了生成丢失的获取器和设置器,我们首先识别它们。下面的解决方案循环给定类的声明字段,并假设foo字段没有获取器,如果以下情况适用:

  • 没有get/isFoo()方法
  • 返回类型与字段类型不同
  • 参数的数目不是 0

对于每个缺少的获取器,此解决方案在映射中添加一个包含字段名和类型的条目:

private static Map<String, Class<?>> 
    fetchMissingGetters(Class<?> clazz) {
  Map<String, Class<?>> getters = new HashMap<>();
  Field[] fields = clazz.getDeclaredFields();
  String[] names = new String[fields.length];
  Class<?>[] types = new Class<?>[fields.length];
  Arrays.setAll(names, i -> fields[i].getName());
  Arrays.setAll(types, i -> fields[i].getType());
  for (int i = 0; i < names.length; i++) {
    String getterAccessor = fetchIsOrGet(names[i], types[i]);
    try {
      Method getter = clazz.getDeclaredMethod(getterAccessor);
      Class<?> returnType = getter.getReturnType();
      if (!returnType.equals(types[i]) ||
          getter.getParameterCount() != 0) {
        getters.put(names[i], types[i]);
      }
    } catch (NoSuchMethodException ex) {
      getters.put(names[i], types[i]);
      // log exception
    }
  }
  return getters;
}

此外,解决方案循环给定类的声明字段,并假设foo字段没有设置器,如果以下情况适用:

  • 字段不是final
  • 没有setFoo()方法
  • 方法返回void
  • 该方法只有一个参数
  • 参数类型与字段类型相同
  • 如果参数名存在,则应与字段名相同

对于每个缺少的设置器,此解决方案在映射中添加一个包含字段名和类型的条目:

private static Map<String, Class<?>> 
    fetchMissingSetters(Class<?> clazz) {
  Map<String, Class<?>> setters = new HashMap<>();
  Field[] fields = clazz.getDeclaredFields();
  String[] names = new String[fields.length];
  Class<?>[] types = new Class<?>[fields.length];
  Arrays.setAll(names, i -> fields[i].getName());
  Arrays.setAll(types, i -> fields[i].getType());
  for (int i = 0; i < names.length; i++) {
    Field field = fields[i];
    boolean finalField = !Modifier.isFinal(field.getModifiers());
    if (finalField) {
      String setterAccessor = fetchSet(names[i]);
      try {
        Method setter = clazz.getDeclaredMethod(
            setterAccessor, types[i]);
        if (setter.getParameterCount() != 1 ||
            !setter.getReturnType().equals(void.class)) {
          setters.put(names[i], types[i]);
          continue;
        }
        Parameter parameter = setter.getParameters()[0];
        if ((parameter.isNamePresent() &&
              !parameter.getName().equals(names[i])) ||
                !parameter.getType().equals(types[i])) {
          setters.put(names[i], types[i]);
        }
      } catch (NoSuchMethodException ex) {
        setters.put(names[i], types[i]);
        // log exception
      }
    }
  }
  return setters;
}

到目前为止,我们知道哪些字段没有获取器和设置器。它们的名称和类型存储在映射中。让我们循环映射并生成获取器:

public static StringBuilder generateGetters(Class<?> clazz) {
  StringBuilder getterBuilder = new StringBuilder();
  Map<String, Class<?>> accessors = fetchMissingGetters(clazz);
  for (Entry<String, Class<?>> accessor: accessors.entrySet()) {
    Class<?> type = accessor.getValue();
    String field = accessor.getKey();
    String getter = fetchIsOrGet(field, type);
    getterBuilder.append("\npublic ")
      .append(type.getSimpleName()).append(" ")
      .append(getter)
      .append("() {\n")
      .append("\treturn ")
      .append(field)
      .append(";\n")
      .append("}\n");
  }
  return getterBuilder;
}

让我们生成设置器:

public static StringBuilder generateSetters(Class<?> clazz) {
  StringBuilder setterBuilder = new StringBuilder();
  Map<String, Class<?>> accessors = fetchMissingSetters(clazz);
  for (Entry<String, Class<?>> accessor: accessors.entrySet()) {
    Class<?> type = accessor.getValue();
    String field = accessor.getKey();
    String setter = fetchSet(field);
    setterBuilder.append("\npublic void ")
      .append(setter)
      .append("(").append(type.getSimpleName()).append(" ")
      .append(field).append(") {\n")
      .append("\tthis.")
      .append(field).append(" = ")
      .append(field)
      .append(";\n")
      .append("}\n");
  }
  return setterBuilder;
}

前面的解决方案依赖于下面列出的三个简单助手。代码很简单:

private static String fetchIsOrGet(String name, Class<?> type) {
  return "boolean".equalsIgnoreCase(type.getSimpleName()) ?
    "is" + uppercase(name) : "get" + uppercase(name);
}
private static String fetchSet(String name) {
  return "set" + uppercase(name);
}
private static String uppercase(String name) {
  return name.substring(0, 1).toUpperCase() + name.substring(1);
}

现在,我们把它命名为Melon类:

Class<?> clazz = Melon.class;
StringBuilder getters = generateGetters(clazz);
StringBuilder setters = generateSetters(clazz);

输出将显示以下生成的获取器和设置器:

public int getWeight() {
  return weight;
}
public boolean isRipe() {
  return ripe;
}
public void setWeight(int weight) {
  this.weight = weight;
}
public void setType(String type) {
  this.type = type;
}

158 反射注解

Java 注解从 Java 反射 API 得到了很多关注。让我们看看几种用于检查几种注解(例如,包、类和方法)的解决方案。

主要地,表示支持注解的工件的所有主要反射 API 类(例如,PackageConstructorClassMethodField揭示了一组处理注解的常用方法。常用方法包括:

  • getAnnotations():返回特定于某个工件的所有注解
  • getDeclaredAnnotations():返回直接声明给某个工件的所有注解
  • getAnnotation():按类型返回注解
  • getDeclaredAnnotation():通过直接声明给某个工件的类型返回注解(JDK1.8)
  • getDeclaredAnnotationsByType():按类型返回直接声明给某个工件的所有注解(JDK1.8)
  • isAnnotationPresent():如果在给定工件上找到指定类型的注解,则返回true

getAnnotatedReceiverType()在前面“在接收器类型上获取注解”部分中进行了讨论。

在下一节中,我们将讨论如何检查包、类、方法等的注解。

检查包注解

package-info.java中添加了特定于包的注解,如下面的屏幕截图所示。在这里,modern.challenge包被注解为@Packt注解:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V12cskFq-1657284514767)(https://github.com/apachecn/apachecn-java-zh/raw/master/docs/java-coding-prob/img/e48d485d-b2c7-4def-85a2-8f297f863e6e.png)]

检查包的注解的一个方便的解决方案是从它的一个类开始的。例如,如果在这个包(modern.challenge中,我们有Melon类,那么我们可以得到这个包的所有注解,如下所示:

Class<Melon> clazz = Melon.class;
Annotation[] pckgAnnotations = clazz.getPackage().getAnnotations();

通过Arrays.toString()打印的Annotation[]显示一个结果:

[@modern.challenge.Packt()]

检查类注解

Melon类有一个注解@Fruit

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fwEhZKhz-1657284514768)(https://github.com/apachecn/apachecn-java-zh/raw/master/docs/java-coding-prob/img/db423fce-65bb-4f44-8c69-321f7240a260.png)]

但我们可以通过getAnnotations()将它们全部取出来:

Class<Melon> clazz = Melon.class;
Annotation[] clazzAnnotations = clazz.getAnnotations();

通过Arrays.toString()打印的返回数组显示一个结果:

[@modern.challenge.Fruit(name="melon", value="delicious")]

为了访问注解的名称和值属性,我们可以按如下方式强制转换它:

Fruit fruitAnnotation = (Fruit) clazzAnnotations[0];
System.out.println("@Fruit name: " + fruitAnnotation.name());
System.out.println("@Fruit value: " + fruitAnnotation.value());

或者我们可以使用getDeclaredAnnotation()方法直接获取正确的类型:

Fruit fruitAnnotation = clazz.getDeclaredAnnotation(Fruit.class);

检查方法注解

我们来看看Melon类中eat()方法的@Ripe注解:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K6XYSiTi-1657284514769)(https://github.com/apachecn/apachecn-java-zh/raw/master/docs/java-coding-prob/img/a62edba6-1f84-46b8-9c24-dda5c288f30f.png)]

首先,让我们获取所有声明的注解,然后,让我们继续到@Ripe

Class<Melon> clazz = Melon.class;
Method methodEat = clazz.getDeclaredMethod("eat");
Annotation[] methodAnnotations = methodEat.getDeclaredAnnotations();

通过Arrays.toString()打印的返回数组显示一个结果:

[@modern.challenge.Ripe(value=true)]

让我们把methodAnnotations[0]转换成Ripe

Ripe ripeAnnotation = (Ripe) methodAnnotations[0];
System.out.println("@Ripe value: " + ripeAnnotation.value());

或者我们可以使用getDeclaredAnnotation()方法直接获取正确的类型:

Ripe ripeAnnotation = methodEat.getDeclaredAnnotation(Ripe.class);

检查抛出异常的注解

为了检查抛出异常的注解,我们需要调用getAnnotatedExceptionTypes()方法:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QlfFPSYj-1657284514769)(https://github.com/apachecn/apachecn-java-zh/raw/master/docs/java-coding-prob/img/b9027589-c212-446e-be95-acd214df17c2.png)]

此方法返回抛出的异常类型,包括注解的异常类型:

Class<Melon> clazz = Melon.class;
Method methodEat = clazz.getDeclaredMethod("eat");
AnnotatedType[] exceptionsTypes 
  = methodEat.getAnnotatedExceptionTypes();

通过Arrays.toString()打印的返回数组显示一个结果:

[@modern.challenge.Runtime() java.lang.IllegalStateException]

提取第一个异常类型的步骤如下:

// class java.lang.IllegalStateException
System.out.println("First exception type: "
  + exceptionsTypes[0].getType());

提取第一个异常类型的注解可以按如下方式进行:

// [@modern.challenge.Runtime()]
System.out.println("Annotations of the first exception type: " 
  + Arrays.toString(exceptionsTypes[0].getAnnotations()));

检查返回类型的注解

为了检查方法返回的注解,我们需要调用getAnnotatedReturnType()方法:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QppkIlLo-1657284514770)(https://github.com/apachecn/apachecn-java-zh/raw/master/docs/java-coding-prob/img/3f955909-7c68-42e5-a1ad-06f887e73368.png)]

此方法返回给定方法的带注解的返回类型:

Class<Melon> clazz = Melon.class;
Method methodSeeds = clazz.getDeclaredMethod("seeds");
AnnotatedType returnType = methodSeeds.getAnnotatedReturnType();
// java.util.List<modern.challenge.Seed>
System.out.println("Return type: " 
  + returnType.getType().getTypeName());
// [@modern.challenge.Shape(value="oval")]
System.out.println("Annotations of the return type: " 
  + Arrays.toString(returnType.getAnnotations()));

检查方法参数的注解

有方法,可以调用getParameterAnnotations()来检查其参数的注解:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jrOKuH90-1657284514770)(https://github.com/apachecn/apachecn-java-zh/raw/master/docs/java-coding-prob/img/9381a23e-4a3b-4dfc-ba66-be8ccaaf06ed.png)]

此方法返回一个矩阵(数组数组),其中包含形式参数上的注解,顺序如下:

Class<Melon> clazz = Melon.class;
Method methodSlice = clazz.getDeclaredMethod("slice", int.class);
Annotation[][] paramAnnotations 
  = methodSlice.getParameterAnnotations();

获取每个参数类型及其注解(在本例中,我们有一个带有两个注解的int参数)可以通过getParameterTypes()完成。由于此方法也维护了声明顺序,因此我们可以提取一些信息,如下所示:

Class<?>[] parameterTypes = methodSlice.getParameterTypes();
int i = 0;
for (Annotation[] annotations: paramAnnotations) {
  Class parameterType = parameterTypes[i++];
  System.out.println("Parameter: " + parameterType.getName());
  for (Annotation annotation: annotations) {
    System.out.println("Annotation: " + annotation);
    System.out.println("Annotation name: " 
      + annotation.annotationType().getSimpleName());
  }
}

并且,输出应如下所示:

Parameter type: int
Annotation: @modern.challenge.Ripe(value=true)
Annotation name: Ripe
Annotation: @modern.challenge.Shape(value="square")
Annotation name: Shape

检查字段注解

有一个字段,我们可以通过getDeclaredAnnotations()获取它的注解:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-56b4S5vP-1657284514771)(https://github.com/apachecn/apachecn-java-zh/raw/master/docs/java-coding-prob/img/6aa9184c-29ee-4e3d-b36e-4a9ad3ada53f.png)]

代码如下:

Class<Melon> clazz = Melon.class;
Field weightField = clazz.getDeclaredField("weight");
Annotation[] fieldAnnotations = weightField.getDeclaredAnnotations();

获取@Unit注解的值可以如下所示:

Unit unitFieldAnnotation = (Unit) fieldAnnotations[0];
System.out.println("@Unit value: " + unitFieldAnnotation.value());

或者,使用getDeclaredAnnotation()方法直接获取正确的类型:

Unit unitFieldAnnotation 
  = weightField.getDeclaredAnnotation(Unit.class);

检查超类的注解

为了检查超类的注解,我们需要调用getAnnotatedSuperclass()方法:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Clmgr8xb-1657284514772)(https://github.com/apachecn/apachecn-java-zh/raw/master/docs/java-coding-prob/img/08f3f44f-a25c-4a17-8fb0-1401133208ed.png)]

此方法返回带注解的超类类型:

Class<Melon> clazz = Melon.class;
AnnotatedType superclassType = clazz.getAnnotatedSuperclass();

我们也来了解一下:

// modern.challenge.Cucurbitaceae
 System.out.println("Superclass type: " 
   + superclassType.getType().getTypeName());
 // [@modern.challenge.Family()]
 System.out.println("Annotations: " 
   + Arrays.toString(superclassType.getDeclaredAnnotations()));
 System.out.println("@Family annotation present: " 
   + superclassType.isAnnotationPresent(Family.class)); // true

检查接口注解

为了检查实现接口的注解,我们需要调用getAnnotatedInterfaces()方法:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SCI79fL6-1657284514772)(https://github.com/apachecn/apachecn-java-zh/raw/master/docs/java-coding-prob/img/ac95c0fb-01d8-44f8-b31a-f2d8c07800a4.png)]

此方法返回带注解的接口类型:

Class<Melon> clazz = Melon.class;
AnnotatedType[] interfacesTypes = clazz.getAnnotatedInterfaces();

通过Arrays.toString()打印的返回数组显示一个结果:

[@modern.challenge.ByWeight() java.lang.Comparable]

提取第一个接口类型可以如下完成:

// interface java.lang.Comparable
System.out.println("First interface type: " 
  + interfacesTypes[0].getType());

此外,提取第一接口类型的注解可以如下进行:

// [@modern.challenge.ByWeight()]
System.out.println("Annotations of the first exception type: " 
  + Arrays.toString(interfacesTypes[0].getAnnotations()));

按类型获取注解

在某些组件上有多个相同类型的注解,我们可以通过getAnnotationsByType()获取所有注解。对于一个类,我们可以按如下方式进行:

Class<Melon> clazz = Melon.class;
Fruit[] clazzFruitAnnotations 
  = clazz.getAnnotationsByType(Fruit.class);

获取声明的注解

尝试按类型获取直接在某个工件上声明的单个注解可以按以下示例所示进行:

Class<Melon> clazz = Melon.class;
Method methodEat = clazz.getDeclaredMethod("eat");
Ripe methodRipeAnnotation 
  = methodEat.getDeclaredAnnotation(Ripe.class);

Java 编程问题:七、Java 反射类、接口、构造器、方法和字段3https://developer.aliyun.com/article/1426148


相关实践学习
云安全基础课 - 访问控制概述
课程大纲 课程目标和内容介绍视频时长 访问控制概述视频时长 身份标识和认证技术视频时长 授权机制视频时长 访问控制的常见攻击视频时长
相关文章
|
1天前
|
设计模式 Java
Java接口与抽象类
Java接口与抽象类
10 0
|
1天前
|
存储 Java
Java基础教程(7)-Java中的面向对象和类
【4月更文挑战第7天】Java是面向对象编程(OOP)语言,强调将事务抽象成对象。面向对象与面向过程的区别在于,前者通过对象间的交互解决问题,后者按步骤顺序执行。类是对象的模板,对象是类的实例。创建类使用`class`关键字,对象通过`new`运算符动态分配内存。方法包括构造函数和一般方法,构造函数用于对象初始化,一般方法处理逻辑。方法可以有0个或多个参数,可变参数用`类型...`定义。`this`关键字用于访问当前对象的属性。
|
5天前
|
Java Shell
Java 21颠覆传统:未命名类与实例Main方法的编码变革
Java 21颠覆传统:未命名类与实例Main方法的编码变革
10 0
|
5天前
|
Java
Java 15 神秘登场:隐藏类解析未知领域
Java 15 神秘登场:隐藏类解析未知领域
10 0
|
5天前
|
Java
代码的魔法师:Java反射工厂模式详解
代码的魔法师:Java反射工厂模式详解
18 0
|
5天前
|
安全 Java 编译器
接口之美,内部之妙:深入解析Java的接口与内部类
接口之美,内部之妙:深入解析Java的接口与内部类
25 0
接口之美,内部之妙:深入解析Java的接口与内部类
|
7天前
|
安全 Java
append在Java中是哪个类下的方法
append在Java中是哪个类下的方法
21 9
|
7天前
|
JavaScript Java 测试技术
基于Java的网络类课程思政学习系统的设计与实现(源码+lw+部署文档+讲解等)
基于Java的网络类课程思政学习系统的设计与实现(源码+lw+部署文档+讲解等)
25 0
基于Java的网络类课程思政学习系统的设计与实现(源码+lw+部署文档+讲解等)
|
8天前
|
存储 安全 Java
java多线程之原子操作类
java多线程之原子操作类
|
9天前
|
缓存 安全 Java
Java中函数式接口详解
Java 8引入函数式接口,支持函数式编程。这些接口有单一抽象方法,可与Lambda表达式结合,简化代码。常见函数式接口包括:`Function&lt;T, R&gt;`用于转换操作,`Predicate&lt;T&gt;`用于布尔判断,`Consumer&lt;T&gt;`用于消费输入,`Supplier&lt;T&gt;`用于无参生成结果。开发者也可自定义函数式接口。Lambda表达式使实现接口更简洁。注意异常处理和线程安全。函数式接口广泛应用于集合操作、并行编程和事件处理。提升代码可读性和效率,是现代Java开发的重要工具。
20 0