锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

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

时间:2023-12-21 22:07:01 df37nc连接器ba附带连接器fce连接器

原文:Java Coding Problems

协议:CC BY-NC-SA 4.0

贡献者:飞龙

本文来自【ApacheCN Java 谷歌翻译用谷歌翻译。

本章包括涉及 Java 反射 API 的 17 问题。从经典主题,如检查和实例化 Java 工件(如模块、包、类、接口、超类、结构、方法、注释和数组)合成桥接基于嵌套的结构或访问控制(JDK11)本章详细介绍 Java 反射 API。本章结束时,Java 反射 API 没有秘密会被发现,你会准备好向同事展示反射能做什么。

问题

用以下问题来测试你 Java 反射 API 编程能力。在使用解决方案和下载示例程序之前,我强烈建议您尝试每个问题:

  1. 检查包:写几个检查 Java 包的示例(如名称、类列表等。

  2. 检查类和超类:写几个检查类和超类的例子(例如,通过类名、修饰符、实现接口、结构、方法和字段获取Class)。

  3. 通过反射结构器实例化:编写通过反射创建实例的程序。

  4. 获取接收器类型的注释:编写获取接收器类型注释的程序。

  5. 获得合成和桥接结构:通过反射编写程序获得合成桥接结构。

  6. 检查变量数:编写程序,检查方法是否获得变量数。

  7. 检查默认方法:编程检查方法是否准备?default

  8. 基于嵌套的反射访问控制:编写一个基于嵌套结构的访问程序。

  9. 获取器和设置器的反射:写几个例子,通过反射调用获取器和设置器。此外,通过反射编写程序生成获取器和设置器。

  10. 反射注解:写几个通过反射获得不同类型注释的例子。

  11. 调用实例法:通过反射调用实例法编写程序。

  12. 获取static方法:为给定类编写程序static该方法分组,并通过反射调用其中一种方法。

  13. 获取方法、字段和异常泛型:编写程序,通过反射获得给定的方法、字段和异常的泛型类型。

  14. 获取公共和私有字段:编写一个程序,通过反射获得给定类publicprivate字段。

  15. 使用数组:通过反射射使用数组的例子。

  16. 检查模块:通过反射检查写几个 Java9 模块的例子。

  17. 动态代理:编写依赖动态代理统计给定接口的调用次数。

解决方案

以下部分介绍了上述问题的解决方案。请记住,通常没有正确的方法来解决特定的问题。此外,请记住,这里显示的解释只包括解决问题所需的最有趣和最重要的细节。您可以从此页面下载示例解决方案,查看更多详细信息并尝试程序。

149 检查包

当我们需要获取特定包的信息时,java.lang.Package类别是我们的主要焦点。使用这个类别,我们可以找到包的名称,实现包的供应商,它的标题,包的版本等等。

这通常用于查找包含特定类别的包的名称。Integer类的包名可以容易地获得如下:

Class clazz = Class.forName("java.lang.Integer"); Package packageOfClazz = clazz.getPackage();  // java.lang String packageNameOfClazz = packageOfClazz.getName(); 

现在,我们来看看File类的包名:

File file = new File("."); Package packageOfFile = file.getClass().getPackage();  // java.io String packageNameOfFile = packageOfFile.getName(); 

如果我们试图找到当前类别的包名,我们可以依靠它this.getClass().getPckage().getName()。这在非静态环境中工作。

但是如果我们只想快速列出当前类装入器的所有包,那么我们可以依赖getPackages()方法,如下所示:

Package[] packages = Package.getPackages();

基于getPackages()方法,我们可以列出调用者的类装入器定义的所有包,以及以给定前缀开头的祖先包,如下所示:

public static List<String> fetchPackagesByPrefix(String prefix) { 
        

  return Arrays.stream(Package.getPackages())
    .map(Package::getName)
    .filter(n -> n.startsWith(prefix))
    .collect(Collectors.toList());
}

如果这个方法存在于一个名为Packages的实用类中,那么我们可以如下调用它:

List<String> packagesSamePrefix 
  = Packages.fetchPackagesByPrefix("java.util");

您将看到类似于以下内容的输出:

java.util.function, java.util.jar, java.util.concurrent.locks,
java.util.spi, java.util.logging, ...

有时,我们只想在系统类加载器中列出一个包的所有类。让我们看看怎么做。

获取包的类

例如,我们可能希望列出当前应用的一个包中的类(例如,modern.challenge包)或编译时库中的一个包中的类(例如,commons-lang-2.4.jar

类被包装在可以在 Jar 中存档的包中,尽管它们不必这样。为了涵盖这两种情况,我们需要发现给定的包是否存在于 JAR 中。我们可以通过ClassLoader.getSystemClassLoader().getResource(package_path)加载资源并检查返回的资源 URL 来完成。如果包不在 JAR 中,那么资源将是以file:方案开始的 URL,如下面的示例(我们使用的是modern.challenge):

file:/D:/Java%20Modern%20Challenge/Code/Chapter%207/Inspect%20packages/build/classes/modern/challenge

但是如果包在 JAR 中(例如,org.apache.commons.lang3.builder,那么 URL 将以jar:方案开始,如下例所示:

jar:file:/D:/.../commons-lang3-3.9.jar!/org/apache/commons/lang3/builder

如果我们考虑到来自 JAR 的包的资源以jar:前缀开头,那么我们可以编写一个方法来区分它们,如下所示:

private static final String JAR_PREFIX = "jar:";

public static List<Class<?>> fetchClassesFromPackage(
    String packageName) throws URISyntaxException, IOException { 
        

  List<Class<?>> classes = new ArrayList<>();
  String packagePath = packageName.replace('.', '/');

  URL resource = ClassLoader
    .getSystemClassLoader().getResource(packagePath);

  if (resource != null) { 
        
    if (resource.toString().startsWith(JAR_PREFIX)) { 
        
      classes.addAll(fetchClassesFromJar(resource, packageName));
    } else { 
        
      File file = new File(resource.toURI());
      classes.addAll(fetchClassesFromDirectory(file, packageName));
    }
  } else { 
        
    throw new RuntimeException("Resource not found for package: " 
      + packageName);
  }

  return classes;
}

因此,如果给定的包在 JAR 中,那么我们调用另一个辅助方法fetchClassesFromJar();否则,我们调用这个辅助方法fetchClassesFromDirectory()。顾名思义,这些助手知道如何从 JAR 或目录中提取给定包的类。

主要来说,这两种方法只是一些用来识别具有.class扩展名的文件的意大利面代码片段。每个类都通过Class.forName()来确保返回的是Class,而不是String。这两种方法在本书附带的代码中都可用。

如何列出不在系统类加载器中的包中的类,例如,外部 JAR 中的包?实现这一点的便捷方法依赖于URLClassLoader。此类用于从引用 JAR 文件和目录的 URL 搜索路径加载类和资源。我们将只处理 Jar,但对目录也这样做非常简单。

因此,根据给定的路径,我们需要获取所有 Jar 并将它们返回为URL[](这个数组需要定义URLClassLoader。例如,我们可以依赖于Files.find()方法遍历给定的路径并提取所有 Jar,如下所示:

public static URL[] fetchJarsUrlsFromClasspath(Path classpath)
    throws IOException { 
        

  List<URL> urlsOfJars = new ArrayList<>();
  List<File> jarFiles = Files.find(
      classpath,
      Integer.MAX_VALUE,
      (path, attr) -> !attr.isDirectory() &&
        path.toString().toLowerCase().endsWith(JAR_EXTENSION))
      .map(Path::toFile)
      .collect(Collectors.toList());

  for (File jarFile: jarFiles) { 
        

    try { 
        
      urlsOfJars.add(jarFile.toURI().toURL());
    } catch (MalformedURLException e) { 
        
      logger.log(Level.SEVERE, "Bad URL for{0} {1}",
        new Object[] { 
        
          jarFile, e
        });
    }
  }

  return urlsOfJars.toArray(URL[]::new);
}

注意,我们正在扫描所有子目录,从给定的路径开始。当然,这是一个设计决策,很容易参数化搜索深度。现在,让我们从tomcat8/lib文件夹中获取 Jar(不需要为此安装 Tomcat;只需使用 Jar 的任何其他本地目录并进行适当的修改):

URL[] urls = Packages.fetchJarsUrlsFromClasspath(
  Path.of("D:/tomcat8/lib"));

现在,我们可以实例化URLClassLoader

URLClassLoader urlClassLoader = new URLClassLoader(
  urls, Thread.currentThread().getContextClassLoader());

这将为给定的 URL 构造一个新的URLClassLoader对象,并使用当前的类加载器进行委托(第二个参数也可以是null)。我们的URL[]只指向 JAR,但根据经验,假设任何jar:方案 URL 都引用 JAR 文件,而任何以/结尾的file:方案 URL 都引用目录。

tomcat8/lib文件夹中的一个 Jar 称为tomcat-jdbc.jar。在这个 JAR 中,有一个名为org.apache.tomcat.jdbc.pool的包。让我们列出这个包的类:

List<Class<?>> classes = Packages.fetchClassesFromPackage(
  "org.apache.tomcat.jdbc.pool", urlClassLoader);

fetchClassesFromPackage()方法是一个助手,它只扫描URLClassLoaderURL[]数组并获取给定包中的类。它的源代码与本书附带的代码一起提供。

检查模块内的包

如果我们使用 Java9 模块化,那么我们的包将生活在模块中。例如,如果我们在一个名为org.tournament的模块中的一个名为com.management的包中有一个名为Manager的类,那么我们可以这样获取该模块的所有包:

Manager mgt = new Manager();
Set<String> packages = mgt.getClass().getModule().getPackages();

另外,如果我们想创建一个类,那么我们需要以下的Class.forName()风格:

Class<?> clazz = Class.forName(mgt.getClass()
  .getModule(), "com.management.Manager");

请记住,每个模块在磁盘上都表示为具有相同名称的目录。例如,org.tournament模块在磁盘上有一个同名文件夹。此外,每个模块被映射为一个具有此名称的单独 JAR(例如,org.tournament.jar)。通过记住这些坐标,很容易修改本节中的代码,从而列出给定模块的给定包的所有类。

150 检查类

通过使用 Java 反射 API,我们可以检查类的详细信息,对象的类名、修饰符、构造器、方法、字段、实现接口等。

假设我们有以下Pair类:

public final class Pair<L, R> extends Tuple implements Comparable { 
        

  final L left;
  final R right;

  public Pair(L left, R right) { 
        
    this.left = left;
    this.right = right;
  }

  public class Entry<L, R> { 
        }
    ...
}

我们还假设有一个实例:

Pair pair = new Pair(1, 1);

现在,让我们使用反射来获取Pair类的名称。

通过实例获取Pair类的名称

通过拥有Pair的实例(对象),我们可以通过调用getClass()方法,以及Class.getName()getSimpleName()getCanonicalName()找到其类的名称,如下例所示:

Class<?> clazz = pair.getClass();

// modern.challenge.Pair
System.out.println("Name: " + clazz.getName());

// Pair
System.out.println("Simple name: " + clazz.getSimpleName());

// modern.challenge.Pair
System.out.println("Canonical name: " + clazz.getCanonicalName());

匿名类没有简单的和规范的名称。

注意,getSimpleName()返回非限定类名。或者,我们可以获得如下类:

Class<Pair> clazz = Pair.class;
Class<?> clazz = Class.forName("modern.challenge.Pair");

获取Pair类修饰符

为了得到类的修饰符(publicprotectedprivatefinalstaticabstractinterface,我们可以调用Class.getModifiers()方法。此方法返回一个int值,该值将每个修饰符表示为标志位。为了解码结果,我们依赖于Modifier类,如下所示:

int modifiers = clazz.getModifiers();

System.out.println("Is public? " 
  + Modifier.isPublic(modifiers)); // true
System.out.println("Is final? " 
  + Modifier.isFinal(modifiers)); // true
System.out.println("Is abstract? " 
  + Modifier.isAbstract(modifiers)); // false

获取Pair类实现的接口

为了获得由类或对象表示的接口直接实现的接口,我们只需调用Class.getInterfaces()。此方法返回一个数组。因为Pair类实现了一个接口(Comparable,所以返回的数组将包含一个元素:

Class<?>[] interfaces = clazz.getInterfaces();

// interface java.lang.Comparable
System.out.println("Interfaces: " + Arrays.toString(interfaces));

// Comparable
System.out.println("Interface simple name: " 
  + interfaces[0].getSimpleName());

获取Pair类构造器

类的public构造器可以通过Class.getConstructors()类获得。返回结果为Constructor[]

Constructor<?>[] constructors = clazz.getConstructors();

// public modern.challenge.Pair(java.lang.Object,java.lang.Object)
System.out.println("Constructors: " + Arrays.toString(constructors));

要获取所有声明的构造器(例如,privateprotected构造器),请调用getDeclaredConstructors()。搜索某个构造器时,调用getConstructor​(Class... parameterTypes)getDeclaredConstructor​(Class... parameterTypes)

获取Pair类字段

类的所有字段都可以通过Class.getDeclaredFields()方法访问。此方法返回一个数组Field

Field[] fields = clazz.getDeclaredFields();

// final java.lang.Object modern.challenge.Pair.left
// final java.lang.Object modern.challenge.Pair.right
System.out.println("Fields: " + Arrays.toString(fields));

为了获取字段的实际名称,我们可以很容易地提供一个辅助方法:

public static List<String> getFieldNames(Field[] fields) { 
        

  return Arrays.stream(fields)
    .map(Field::getName)
    .collect(Collectors.toList());
}

现在,我们只收到字段的名称:

List<String> fieldsName = getFieldNames(fields);

// left, right
System.out.println("Fields names: " + fieldsName);

获取字段的值可以通过一个名为Object get(Object obj)的通用方法和一组getFoo()方法来完成(有关详细信息,请参阅文档)。obj表示static或实例字段。例如,假设ProcedureOutputs类有一个名为callableStatementprivate字段,其类型为CallableStatement。让我们用Field.get()方法访问此字段,检查CallableStatement是否关闭:

ProcedureOutputs procedureOutputs 
  = storedProcedure.unwrap(ProcedureOutputs.class);

Field csField = procedureOutputs.getClass()
  .getDeclaredField("callableStatement"); 
csField.setAccessible(true);

CallableStatement cs 
  = (CallableStatement) csField.get(procedureOutputs);

System.out.println("Is closed? " + cs.isClosed());

如果只获取public字段,请调用getFields()。要搜索某个字段,请调用getField​(String fieldName)getDeclaredField​(String name)

获取Pair类方法

类的public方法可以通过Class.getMethods()方法访问。此方法返回一个数组Method

Method[] methods = clazz.getMethods();
// public boolean modern.challenge.Pair.equals(java.lang.Object)
// public int modern.challenge.Pair.hashCode()
// public int modern.challenge.Pair.compareTo(java.lang.Object)
// ...
System.out.println("Methods: " + Arrays.toString(methods));

为了获取方法的实际名称,我们可以快速提供一个辅助方法:

public static List<String> getMethodNames(Method[] methods) { 
        

  return Arrays.stream(methods)
    .map(Method::getName)
    .collect(Collectors.toList());
}

现在,我们只检索方法的名称:

List<String> methodsName = getMethodNames(methods);

// equals, hashCode, compareTo, wait, wait,
// wait, toString, getClass, notify, notifyAll
System.out.println("Methods names: " + methodsName);

获取所有声明的方法(例如,privateprotected),调用getDeclaredMethods()。要搜索某个方法,请调用getMethod​(String name, Class... parameterTypes)getDeclaredMethod​(String name, Class... parameterTypes)

获取Pair类模块

如果我们使用 JDK9 模块化,那么我们的类将生活在模块中。Pair类不在模块中,但是我们可以通过 JDK9 的Class.getModule()方法很容易得到类的模块(如果类不在模块中,那么这个方法返回null):

// null, since Pair is not in a Module
Module module = clazz.getModule();

获取Pair类超类

Pair类扩展了Tuple类,因此Tuple类是Pair的超类。我们可以通过Class.getSuperclass()方法得到,如下所示:

Class<?> superClass = clazz.getSuperclass();
// modern.challenge.Tuple
System.out.println("Superclass: " + superClass.getName());

获取某个类型的名称

从 JDK8 开始,我们可以获得特定类型名称的信息字符串。

此方法返回与getName()getSimpleName()getCanonicalName()中的一个或多个相同的字符串:

  • 对于原始类型,它会为所有三个方法返回相同的结果:
System.out.println("Type: " + int.class.getTypeName()); // int
  • 对于Pair,返回与getName()getCanonicalName()相同的东西:
// modern.challenge.Pair
System.out.println("Type name: " + clazz.getTypeName());
  • 对于内部类(比如Entry代表Pair,它返回与getName()相同的东西:
// modern.challenge.Pair$Entry
System.out.println( 

相关文章