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 的全面遍历。我们已经详细讨论了有关类、接口、构造器、方法、字段、注解等的问题
从本章下载应用以查看结果和其他详细信息。