Java 编程问题:七、Java 反射类、接口、构造器、方法和字段3https://developer.aliyun.com/article/1426148
异常的泛型
异常的泛型类型在TypeVariable或ParameterizedType实例中具体化。这一次,基于TypeVariable的泛型信息提取和打印的助手方法可以写为:
public static void printGenericsOfExceptions(Type genericType) { if (genericType instanceof TypeVariable) { TypeVariable typeVariable = (TypeVariable) genericType; GenericDeclaration genericDeclaration = typeVariable.getGenericDeclaration(); System.out.println("Generic declaration: " + genericDeclaration); System.out.println("Bounds: "); for (Type type: typeVariable.getBounds()) { System.out.println(type); } } }
有了这个助手,我们可以通过getGenericExceptionTypes()将方法抛出的异常传递给它。如果异常类型是类型变量(TypeVariable)或参数化类型(ParameterizedType),则创建它。否则,将解决:
Type[] exceptionsTypes = sliceMethod.getGenericExceptionTypes();
此外,我们为每个Type调用printGenerics():
for (Type paramType: exceptionsTypes) { printGenericsOfExceptions(paramType); }
输出如下:
Generic declaration: class modern.challenge.Melon Bounds: class java.lang.Exception
最可能的情况是,打印有关泛型的提取信息将没有用处,因此,可以根据您的需要随意调整前面的帮助程序。例如,收集信息并以List、Map等形式返回。
162 获取公共和私有字段
这个问题的解决依赖于Modifier.isPublic()和Modifier.isPrivate()方法。
假设下面的Melon类有两个public字段和两个private字段:
public class Melon { private String type; private int weight; public Peeler peeler; public Juicer juicer; ... }
首先需要通过getDeclaredFields()方法获取该类对应的Field[]数组:
Class<Melon> clazz = Melon.class; Field[] fields = clazz.getDeclaredFields();
Field[]包含前面的四个字段。此外,让我们迭代这个数组,并对每个Field应用Modifier.isPublic()和Modifier.isPrivate()标志方法:
List<Field> publicFields = new ArrayList<>(); List<Field> privateFields = new ArrayList<>(); for (Field field: fields) { if (Modifier.isPublic(field.getModifiers())) { publicFields.add(field); } if (Modifier.isPrivate(field.getModifiers())) { privateFields.add(field); } }
publicFields列表只包含public字段,privateFields列表只包含private字段。如果我们通过System.out.println()快速打印这两个列表,那么输出如下:
Public fields: [public modern.challenge.Peeler modern.challenge.Melon.peeler, public modern.challenge.Juicer modern.challenge.Melon.juicer] Private fields: [private java.lang.String modern.challenge.Melon.type, private int modern.challenge.Melon.weight]
163 使用数组
Java 反射 API 附带了一个专用于处理数组的类。这个类被命名为java.lang.reflect.Array。
例如,下面的代码片段创建了一个数组int。第一个参数告诉数组中每个元素的类型。第二个参数表示数组的长度。因此,10 个整数的数组可以通过Array.newInstance()定义如下:
int[] arrayOfInt = (int[]) Array.newInstance(int.class, 10);
使用 Java 反射,我们可以改变数组的内容。有一个通用的set()方法和一堆set*Foo*()方法(例如setInt()、setFloat())。将索引 0 处的值设置为 100 可以按以下方式进行:
Array.setInt(arrayOfInt, 0, 100);
从数组中获取值可以通过get()和getFoo()方法完成(这些方法将数组和索引作为参数,并从指定的索引返回值):
int valueIndex0 = Array.getInt(arrayOfInt, 0);
获取一个数组的Class可以如下操作:
Class<?> stringClass = String[].class; Class<?> clazz = arrayOfInt.getClass();
我们可以通过getComponentType()提取数组的类型:
// int Class<?> typeInt = clazz.getComponentType(); // java.lang.String Class<?> typeString = stringClass.getComponentType();
164 检查模块
Java9 通过 Java 平台模块系统增加了模块的概念。基本上,模块是由该模块管理的一组包(例如,模块决定哪些包在模块外部可见)。
具有两个模块的应用的形状可以如以下屏幕截图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VGW3J1dG-1657284514773)(https://github.com/apachecn/apachecn-java-zh/raw/master/docs/java-coding-prob/img/53a35686-cd53-4883-9763-1d15ace0a122.png)]
有两个模块-org.player和org.tournament。org.player模块需要org.tournament模块,org.tournament模块导出com.management包。
Java 反射 API 通过java.lang.Module类(在java.base module中)表示一个模块。通过 Java 反射 API,我们可以提取信息或修改模块。
最开始,我们可以得到一个Module实例,如下两个例子所示:
Module playerModule = Player.class.getModule(); Module managerModule = Manager.class.getModule();
模块名称可以通过Module.getName()方法获得:
// org.player System.out.println("Class 'Player' is in module: " + playerModule.getName()); // org.tournament System.out.println("Class 'Manager' is in module: " + managerModule.getName());
有一个Module实例,我们可以调用几种方法来获取不同的信息。例如,我们可以确定某个模块是否已命名,或者是否已导出或打开某个包:
boolean playerModuleIsNamed = playerModule.isNamed(); // true boolean managerModuleIsNamed = managerModule.isNamed(); // true boolean playerModulePnExported = playerModule.isExported("com.members"); // false boolean managerModulePnExported = managerModule.isExported("com.management"); // true boolean playerModulePnOpen = playerModule.isOpen("com.members"); // false boolean managerModulePnOpen = managerModule.isOpen("com.management"); // false
除了获取信息外,Module类还允许我们修改模块。例如,org.player模块没有将com.members包导出到org.tournament模块。我们可以快速检查:
boolean before = playerModule.isExported( "com.members", managerModule); // false
但我们可以通过反射来改变这一点。我们可以通过Module.addExports()方法进行导出(同一类别中我们有addOpens()、addReads()、addUses():
playerModule.addExports("com.members", managerModule);
现在,让我们再次检查:
boolean after = playerModule.isExported( "com.members", managerModule); // true
模块还利用了自己的描述符。ModuleDescriptor类可用作处理模块的起点:
ModuleDescriptor descriptorPlayerModule = playerModule.getDescriptor();
例如,我们可以按如下方式获取模块的包:
Set<String> pcks = descriptorPlayerModule.packages();
165 动态代理
动态代理可用于支持不同功能的实现,这些功能属于交叉切入点(CCC)类别。CCC 是那些表示核心功能的辅助功能的关注点,例如数据库连接管理、事务管理(例如 Spring@Transactional)、安全性和日志记录。
更确切地说,Java 反射附带了一个名为java.lang.reflect.Proxy的类,其主要目的是为在运行时创建接口的动态实现提供支持。Proxy反映了具体接口在运行时的实现。
我们可以将Proxy看作是前包装器,它将我们的调用传递给正确的方法。可选地,Proxy可以在委托调用之前干预该过程。
动态代理依赖于单个类(InvocationHandler)和单个方法(invoke()),如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pP5qtUUQ-1657284514774)(https://github.com/apachecn/apachecn-java-zh/raw/master/docs/java-coding-prob/img/7aa4bc4f-a8ec-42a5-b2ad-a4fdc0c20e83.png)]
如果我们从这个图中描述流程,那么我们得到以下步骤:
- 参与者通过公开的动态代理调用所需的方法(例如,如果我们要调用
List.add()方法,我们将通过动态代理,而不是直接调用) - 动态代理将调用分派给一个
InvocationHandler实现的实例(每个代理实例都有一个关联的调用处理器) - 分派的调用将以包含代理对象、要调用的方法(作为
Method实例)和此方法的参数数组的三元组的形式命中invoke()方法 InvocationHandler将运行额外的可选功能(例如,CCC)并调用相应的方法InvocationHandler将调用结果作为对象返回
如果我们尝试恢复此流,那么可以说动态代理通过单个类(InvocationHandler)和单个方法(invoke())支持对任意类的多个方法的调用。
实现动态代理
例如,让我们编写一个动态代理来统计List方法的调用次数。
通过Proxy.newProxyInstance()方法创建动态代理。newProxyInstance()方法有三个参数:
ClassLoader:用于加载动态代理类Class[]:这是要实现的接口数组InvocationHandler:这是将方法调用分派到的调用处理器
看看这个例子:
List<String> listProxy = (List<String>) Proxy.newProxyInstance( List.class.getClassLoader(), new Class[] { List.class}, invocationHandler);
这段代码返回List接口的动态实现。此外,通过该代理的所有调用都将被调度到invocationHandler实例。
主要地,InvocationHandler实现的框架如下所示:
public class DummyInvocationHandler implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { ... } }
因为我们要计算List的方法的调用次数,所以我们应该存储所有的方法签名以及每个方法的调用次数。这可以通过在CountingInvocationHandler的构造器中初始化Map来实现(这是我们的InvocationHandler实现,invocationHandler是它的一个实例):
public class CountingInvocationHandler implements InvocationHandler { private final Map<String, Integer> counter = new HashMap<>(); private final Object targetObject; public CountingInvocationHandler(Object targetObject) { this.targetObject = targetObject; for (Method method:targetObject.getClass().getDeclaredMethods()) { this.counter.put(method.getName() + Arrays.toString(method.getParameterTypes()), 0); } } ... }
targetObject字段保存List接口的实现(在本例中为ArrayList)。
我们创建一个CountingInvocationHandler实例如下:
CountingInvocationHandler invocationHandler = new CountingInvocationHandler(new ArrayList<>());
invoke()方法只是对调用进行计数,并使用指定的参数调用Method:
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object resultOfInvocation = method.invoke(targetObject, args); counter.computeIfPresent(method.getName() + Arrays.toString(method.getParameterTypes()), (k, v) -> ++v); return resultOfInvocation; }
最后,我们公开了一个方法,该方法返回给定方法的调用次数:
public Map<String, Integer> countOf(String methodName) { Map<String, Integer> result = counter.entrySet().stream() .filter(e -> e.getKey().startsWith(methodName + "[")) .filter(e -> e.getValue() != 0) .collect(Collectors.toMap(Entry::getKey, Entry::getValue)); return result; }
绑定到本书的代码将这些代码片段粘在一个名为CountingInvocationHandler的类中。
此时我们可以使用listProxy调用几个方法,如下所示:
listProxy.add("Adda"); listProxy.add("Mark"); listProxy.add("John"); listProxy.remove("Adda"); listProxy.add("Marcel"); listProxy.remove("Mark"); listProxy.add(0, "Akiuy");
让我们看看我们调用了多少次add()和remove()方法:
// {add[class java.lang.Object]=4, add[int, class java.lang.Object]=1} invocationHandler.countOf("add"); // {remove[class java.lang.Object]=2} invocationHandler.countOf("remove");
因为add()方法是通过它的两个签名调用的,所以得到的Map包含两个条目。
总结
这是本章的最后一个问题。希望我们已经完成了对 Java 反射 API 的全面遍历。我们已经详细讨论了有关类、接口、构造器、方法、字段、注解等的问题
从本章下载应用以查看结果和其他详细信息。