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 开始,我们可以使用显式的接收器参数。这主要意味着我们可以声明一个实例方法,该实例方法使用this
Java 关键字获取封闭类型的参数。
通过显式的接收器参数,我们可以将类型注解附加到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()
方法,可以在显式接收器参数上进行反射注解。该方法在Constructor
和Method
类中也有,因此可以这样使用:
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.class
和Car$Engine.class
)。与此一致,我们的期望意味着外部类和嵌套类可以访问彼此的private
成员。
但是在不同的文件中,这是不可能的。为了维持我们的期望,Java 增加了桥接package
—private
方法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.class
的javap
输出(注意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
类,其中包含几个嵌套类(Slice
、Peeler
和Juicer
):
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();
这里应该强调两点。首先,注意Melon
的NestHost
是Melon
本身。第二,注意Peeler
的NestHost
是Melon
,而不是Slice
。由于Peeler
是Slice
的一个内部类,我们可以认为它的NestHost
是Slice
,但这个假设是不成立的。
现在,让我们列出每个类的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
和第一个大写字母)和字段的类型(对于设置器)传递给它来获得获取器和设置器。
最后,一个更优雅的解决方案将依赖于PropertyDescriptor
和Introspector
api。这些 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
有三个字段(type
、weight
和ripe
),只定义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 类(例如,Package
、Constructor
、Class
、Method
和Field
揭示了一组处理注解的常用方法。常用方法包括:
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