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 的全面遍历。我们已经详细讨论了有关类、接口、构造器、方法、字段、注解等的问题

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

相关文章
|
6天前
|
JSON Java Apache
非常实用的Http应用框架,杜绝Java Http 接口对接繁琐编程
UniHttp 是一个声明式的 HTTP 接口对接框架,帮助开发者快速对接第三方 HTTP 接口。通过 @HttpApi 注解定义接口,使用 @GetHttpInterface 和 @PostHttpInterface 等注解配置请求方法和参数。支持自定义代理逻辑、全局请求参数、错误处理和连接池配置,提高代码的内聚性和可读性。
|
14天前
|
存储 安全 Java
java.util的Collections类
Collections 类位于 java.util 包下,提供了许多有用的对象和方法,来简化java中集合的创建、处理和多线程管理。掌握此类将非常有助于提升开发效率和维护代码的简洁性,同时对于程序的稳定性和安全性有大有帮助。
37 17
|
6天前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
|
7天前
|
Java
java线程接口
Thread的构造方法创建对象的时候传入了Runnable接口的对象 ,Runnable接口对象重写run方法相当于指定线程任务,创建线程的时候绑定了该线程对象要干的任务。 Runnable的对象称之为:线程任务对象 不是线程对象 必须要交给Thread线程对象。 通过Thread的构造方法, 就可以把任务对象Runnable,绑定到Thread对象中, 将来执行start方法,就会自动执行Runable实现类对象中的run里面的内容。
21 1
|
10天前
|
存储 Java 程序员
Java基础的灵魂——Object类方法详解(社招面试不踩坑)
本文介绍了Java中`Object`类的几个重要方法,包括`toString`、`equals`、`hashCode`、`finalize`、`clone`、`getClass`、`notify`和`wait`。这些方法是面试中的常考点,掌握它们有助于理解Java对象的行为和实现多线程编程。作者通过具体示例和应用场景,详细解析了每个方法的作用和重写技巧,帮助读者更好地应对面试和技术开发。
50 4
|
12天前
|
Java 开发者
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
40 4
|
11天前
|
Java 编译器 开发者
Java异常处理的最佳实践,涵盖理解异常类体系、选择合适的异常类型、提供详细异常信息、合理使用try-catch和finally语句、使用try-with-resources、记录异常信息等方面
本文探讨了Java异常处理的最佳实践,涵盖理解异常类体系、选择合适的异常类型、提供详细异常信息、合理使用try-catch和finally语句、使用try-with-resources、记录异常信息等方面,帮助开发者提高代码质量和程序的健壮性。
26 2
|
14天前
|
Java 测试技术 Maven
Java一分钟之-PowerMock:静态方法与私有方法测试
通过本文的详细介绍,您可以使用PowerMock轻松地测试Java代码中的静态方法和私有方法。PowerMock通过扩展Mockito,提供了强大的功能,帮助开发者在复杂的测试场景中保持高效和准确的单元测试。希望本文对您的Java单元测试有所帮助。
27 2
|
3月前
|
Java 开发者
奇迹时刻!探索 Java 多线程的奇幻之旅:Thread 类和 Runnable 接口的惊人对决
【8月更文挑战第13天】Java的多线程特性能显著提升程序性能与响应性。本文通过示例代码详细解析了两种核心实现方式:Thread类与Runnable接口。Thread类适用于简单场景,直接定义线程行为;Runnable接口则更适合复杂的项目结构,尤其在需要继承其他类时,能保持代码的清晰与模块化。理解两者差异有助于开发者在实际应用中做出合理选择,构建高效稳定的多线程程序。
58 7
|
22天前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
16 3