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

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

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

异常的泛型

异常的泛型类型在TypeVariableParameterizedType实例中具体化。这一次,基于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

最可能的情况是,打印有关泛型的提取信息将没有用处,因此,可以根据您的需要随意调整前面的帮助程序。例如,收集信息并以ListMap等形式返回。

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.playerorg.tournamentorg.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)]

如果我们从这个图中描述流程,那么我们得到以下步骤:

  1. 参与者通过公开的动态代理调用所需的方法(例如,如果我们要调用List.add()方法,我们将通过动态代理,而不是直接调用)
  2. 动态代理将调用分派给一个InvocationHandler实现的实例(每个代理实例都有一个关联的调用处理器)
  3. 分派的调用将以包含代理对象、要调用的方法(作为Method实例)和此方法的参数数组的三元组的形式命中invoke()方法
  4. InvocationHandler将运行额外的可选功能(例如,CCC)并调用相应的方法
  5. 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 的全面遍历。我们已经详细讨论了有关类、接口、构造器、方法、字段、注解等的问题

从本章下载应用以查看结果和其他详细信息。

相关文章
|
21天前
|
数据采集 JSON Java
利用Java获取京东SKU接口指南
本文介绍如何使用Java通过京东API获取商品SKU信息。首先,需注册京东开放平台账号并创建应用以获取AppKey和AppSecret。接着,查阅API文档了解调用方法。明确商品ID后,构建请求参数并通过HTTP客户端发送请求。最后,解析返回的JSON数据提取SKU信息。注意遵守API调用频率限制及数据保护法规。此方法适用于电商平台及其他数据获取场景。
|
26天前
|
安全 Java API
java如何请求接口然后终止某个线程
通过本文的介绍,您应该能够理解如何在Java中请求接口并根据返回结果终止某个线程。合理使用标志位或 `interrupt`方法可以确保线程的安全终止,而处理好网络请求中的各种异常情况,可以提高程序的稳定性和可靠性。
48 6
|
2月前
|
安全 Java 开发者
Java中WAIT和NOTIFY方法必须在同步块中调用的原因
在Java多线程编程中,`wait()`和`notify()`方法是实现线程间协作的关键。这两个方法必须在同步块或同步方法中调用,这一要求背后有着深刻的原因。本文将深入探讨为什么`wait()`和`notify()`方法必须在同步块中调用,以及这一机制如何确保线程安全和避免死锁。
51 4
|
2月前
|
Java
深入探讨Java中的中断机制:INTERRUPTED和ISINTERRUPTED方法详解
在Java多线程编程中,中断机制是协调线程行为的重要手段。了解和正确使用中断机制对于编写高效、可靠的并发程序至关重要。本文将深入探讨Java中的`Thread.interrupted()`和`Thread.isInterrupted()`方法的区别及其应用场景。
72 4
|
2月前
|
Java 数据处理 数据安全/隐私保护
Java处理数据接口方法
Java处理数据接口方法
29 1
|
2月前
|
Java API
Java中内置的函数式接口
Java中内置的函数式接口
35 2
|
Java
Java接口和抽象类
Java接口和抽象类
94 0
|
5月前
|
设计模式 Java
【惊天揭秘】Java编程绝技大曝光:接口、抽象类、静态类与非静态类的神秘面纱终被揭开!
【8月更文挑战第22天】Java支持面向对象编程,通过接口、抽象类、静态类(如枚举与工具类)及普通类实现设计原则。接口定义行为规范,允许多重继承;抽象类含未实现的抽象方法,需子类完成;静态类常为工具类,提供静态方法;普通类则实例化对象。恰当运用这些结构能提升程序质量。
45 2
|
8月前
|
设计模式 搜索推荐 Java
java接口和抽象类的区别,以及使用选择
java接口和抽象类的区别,以及使用选择
83 0
|
5月前
|
Java 开发者
Java中的接口和抽象类
Java中的接口和抽象类
39 3