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


相关实践学习
消息队列+Serverless+Tablestore:实现高弹性的电商订单系统
基于消息队列以及函数计算,快速部署一个高弹性的商品订单系统,能够应对抢购场景下的高并发情况。
云安全基础课 - 访问控制概述
课程大纲 课程目标和内容介绍视频时长 访问控制概述视频时长 身份标识和认证技术视频时长 授权机制视频时长 访问控制的常见攻击视频时长
相关文章
|
2天前
|
Java
java的类详解
在 Java 中,类是面向对象编程的核心概念,用于定义具有相似特性和行为的对象模板。以下是类的关键特性:唯一且遵循命名规则的类名;描述对象状态的私有属性;描述对象行为的方法,包括实例方法和静态方法;用于初始化对象的构造方法;通过封装保护内部属性;通过继承扩展其他类的功能;以及通过多态增强代码灵活性。下面是一个简单的 `Person` 类示例,展示了属性、构造方法、getter 和 setter 方法及行为方法的使用。
|
2天前
|
Java 程序员
Java编程中的对象和类: 初学者指南
【9月更文挑战第9天】在Java的世界中,对象和类构成了编程的基石。本文将引导你理解这两个概念的本质,并展示如何通过它们来构建你的程序。我们将一起探索类的定义,对象的创建,以及它们如何互动。准备好了吗?让我们开始这段Java的旅程吧!
|
8天前
|
监控 Java 调度
【Java学习】多线程&JUC万字超详解
本文详细介绍了多线程的概念和三种实现方式,还有一些常见的成员方法,CPU的调动方式,多线程的生命周期,还有线程安全问题,锁和死锁的概念,以及等待唤醒机制,阻塞队列,多线程的六种状态,线程池等
64 6
【Java学习】多线程&JUC万字超详解
|
2天前
|
Java 调度 开发者
Java并发编程:深入理解线程池
在Java的世界中,线程池是提升应用性能、实现高效并发处理的关键工具。本文将深入浅出地介绍线程池的核心概念、工作原理以及如何在实际应用中有效利用线程池来优化资源管理和任务调度。通过本文的学习,读者能够掌握线程池的基本使用技巧,并理解其背后的设计哲学。
|
1天前
|
缓存 监控 Java
Java中的并发编程:理解并应用线程池
在Java的并发编程中,线程池是提高应用程序性能的关键工具。本文将深入探讨如何有效利用线程池来管理资源、提升效率和简化代码结构。我们将从基础概念出发,逐步介绍线程池的配置、使用场景以及最佳实践,帮助开发者更好地掌握并发编程的核心技巧。
|
3天前
|
缓存 监控 Java
java中线程池的使用
java中线程池的使用
|
3天前
|
算法 Java 数据处理
Java并发编程:解锁多线程的力量
在Java的世界里,掌握并发编程是提升应用性能和响应能力的关键。本文将深入浅出地探讨如何利用Java的多线程特性来优化程序执行效率,从基础的线程创建到高级的并发工具类使用,带领读者一步步解锁Java并发编程的奥秘。你将学习到如何避免常见的并发陷阱,并实际应用这些知识来解决现实世界的问题。让我们一起开启高效编码的旅程吧!
|
8天前
|
存储 Java 程序员
优化Java多线程应用:是创建Thread对象直接调用start()方法?还是用个变量调用?
这篇文章探讨了Java中两种创建和启动线程的方法,并分析了它们的区别。作者建议直接调用 `Thread` 对象的 `start()` 方法,而非保持强引用,以避免内存泄漏、简化线程生命周期管理,并减少不必要的线程控制。文章详细解释了这种方法在使用 `ThreadLocal` 时的优势,并提供了代码示例。作者洛小豆,文章来源于稀土掘金。
|
5天前
|
Java 开发者
Java中的多线程编程基础与实战
【9月更文挑战第6天】本文将通过深入浅出的方式,带领读者了解并掌握Java中的多线程编程。我们将从基础概念出发,逐步深入到代码实践,最后探讨多线程在实际应用中的优势和注意事项。无论你是初学者还是有一定经验的开发者,这篇文章都能让你对Java多线程有更全面的认识。
14 1
|
12天前
|
安全 Java 程序员
Java编程中实现线程安全的策略
【8月更文挑战第31天】在多线程环境下,保证数据一致性和程序的正确运行是每个程序员的挑战。本文将通过浅显易懂的语言和实际代码示例,带你了解并掌握在Java编程中确保线程安全的几种策略。让我们一起探索如何用同步机制、锁和原子变量等工具来保护我们的数据,就像保护自己的眼睛一样重要。