翻译(一):java反射

技术分享  / 倒序浏览   ©

#楼主# 2020-2-11

跳转到指定楼层

马上注册,分享更多源码,享用更多功能,让你轻松玩转云大陆。

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
英语比力差,平时阅读文档都困难,所以想通过翻译一些英文博客来提高自己的英语水平,大概很多地方不正确,还请大家不吝指教。
这里先给出英文教程地址:https://www.baeldung.com/java-reflection
1.概述

在这篇文章中我们将要探讨java反射,它答应我们检查或/和修改运行时类、接口、字段和方法的属性。当在编译时我们不知道他们的名字,反射对我们来说是特殊有用的。
另外,我们可以实例化一个对象,通过反射调用方法或者取/赋值为字段。
2.项目设置

利用java反射,我们不需要引入一些特殊的jar包,也不需要特殊的配置或者Maven依赖。JDK附带了一组类,它由 java.lang.reflect包提供,专门用于此目标(反射)。
因此我们我们需要做的就是将下面这段引入到我们的代码:
import java.lang.reflect.*;为了访问一个实例的类、方法和字段信息,我们调用getClass方法它返回对象运行时类的描述。返回的类对象提供访问类信息的方法。
3.简单的例子

为了尝试一下,我们将要看一个非常基础的例子,它在运行时检查了一个简单的java对象的字段。
让我们创建一个简单的Person类,它仅包含name和age字段,没有方法,下面是Person类的实现:
public class Person {    private String name;    private int age;}我们现在利用java反射去获取这个类所有字段的名称。为了表现反射的强大,我们将要构造一个Person类并且利用Object作为引用类型:
@Testpublic void givenObject_whenGetsFieldNamesAtRuntime_thenCorrect() {    Object person = new Person();    Field[] fields = person.getClass().getDeclaredFields();     List actualFieldNames = getFieldNames(fields);     assertTrue(Arrays.asList("name", "age")      .containsAll(actualFieldNames));}这个测试表明我们可以获得一个Field对象的数组通过我们的person对象,即使利用的是该对象的父类型的引用。
在上面的例子中,我们只对这些字段的名称感兴趣,但是我们能做的另有很多,我们将在后面的章节看到更多的例子。
注意,我们利用了一个辅助函数去提取字段真正的名称,这是非常基础的代码:
private static List getFieldNames(Field[] fields) {    List fieldNames = new ArrayList();    for (Field field : fields)      fieldNames.add(field.getName());    return fieldNames;}4.Java反射用例

在开始讨论java反射的不同特性之前,我们先来讨论一些常见的用法。Java反射极其强大并且在许多方面都能派上用场。
例如,在许多情况下我们对数据库表有一个命名约定。我们可以选择用tbl_预先固定表名来增加一致性,这样拥有学生数据的表被称作tbl_student_data。
在这种情况下,我们大概为拥有学生数据的对象命名为Student或者StudentData。然后利用CRUD(增删改查)范式,对于每个操作,我们都有一个入口点,这样创建操作只接收一个对象参数。
我们利用反射去检索对象名和字段名。在这个入口点,我们可以映射这个数据到数据库表中并且指定对象的字段值到合适的数据库字段名。(对于这段,我也是一知半解)
5.检查Java类

在这节中,我们将要探讨java反射API中最基本的组成。作为我们最先提及的java类对象,它给我们提供访问任何对象内部详情的方法。
我们将要检验内部细节,好比一个对象的类名。他们的修饰符,字段,方法,实现的接口等。
5.1. 准备

为了牢牢掌握作用于Java类的反射API,将列举大量的例子。我们创建了一个实现了Eating接口的Animal抽象类。这个接口为我们创建的任何具体的Animal对象定义了一个吃的行为。
首先,这是Eating接口:
public interface Eating {    String eats();}然后,具体的Animal实现Eating接口:
public abstract class Animal implements Eating {     public static String CATEGORY = "domestic";    private String name;     protected abstract String getSound();     // constructor, standard getters and setters omitted }让我们创建另一个叫做“Locomotion”的接口,它描述了动物如何移动:
public interface Locomotion {    String getLocomotion();}现在我们将要创建一个叫做“Goat”的具体类,它继承了Animal并且实现了“Locomotion”接口。由于父类实现了Eating接口,所以“Goat”也必须实现这个接口的方法:
public class Goat extends Animal implements Locomotion {     @Override    protected String getSound() {        return "bleat";    }     @Override    public String getLocomotion() {        return "walks";    }     @Override    public String eats() {        return "grass";    }     // constructor omitted}从这一点开始,我们将利用java反射来检查出现在上面的类和接口中的java对象的各个方面。
5.2.类名

让我们从Class获取一个对象的名字开始。
@Testpublic void givenObject_whenGetsClassName_thenCorrect() {    Object goat = new Goat("goat");    Class clazz = goat.getClass();     assertEquals("Goat", clazz.getSimpleName());    assertEquals("com.baeldung.reflection.Goat", clazz.getName());    assertEquals("com.baeldung.reflection.Goat", clazz.getCanonicalName());}注意,Class的getSimpleName方法返回对象的基本名称,这出现在它的声明中。然后另外的两个方法返回的是全限定类名包含包的声明。
让我们看一下在仅知道它的全限定类名的情况下,如何创建一个“Goat”的class对象:
@Testpublic void givenClassName_whenCreatesObject_thenCorrect(){    Class clazz = Class.forName("com.baeldung.reflection.Goat");     assertEquals("Goat", clazz.getSimpleName());    assertEquals("com.baeldung.reflection.Goat", clazz.getName());    assertEquals("com.baeldung.reflection.Goat", clazz.getCanonicalName()); }需要注意的是我们通过静态forName方法获取class对象的话,它的name属性应该包含包信息,否则将会抛出一个“ClassNotFoundException”。
5.3.类修饰符

我们可以确定用在类上的修饰符通过调用getModifiers方法,它的返回值是Integer类型。每个修饰符都是一个标志位,可以设置,也可以清除。
这个 java.lang.reflect.Modifier 类提供了静态方法,它通太过析Integer型的返回值判断是否存在特定的修饰符。
让我们确定上面定义的一些类的修饰符:
@Testpublic void givenClass_whenRecognisesModifiers_thenCorrect() {    Class goatClass = Class.forName("com.baeldung.reflection.Goat");    Class animalClass = Class.forName("com.baeldung.reflection.Animal");     int goatMods = goatClass.getModifiers();    int animalMods = animalClass.getModifiers();     assertTrue(Modifier.isPublic(goatMods));    assertTrue(Modifier.isAbstract(animalMods));    assertTrue(Modifier.isPublic(animalMods));}5.4. 包信息

通过利用java反射我们能够获得关于任何类或者对象的包信息。数据被绑定在 Package 类上,这个类通过调用类对象的getPackage方法返回。
让我们运行一个获取包名的测试用例:
@Testpublic void givenClass_whenGetsPackageInfo_thenCorrect() {    Goat goat = new Goat("goat");    Class goatClass = goat.getClass();    Package pkg = goatClass.getPackage();     assertEquals("com.baeldung.reflection", pkg.getName());}5.5. 父类

我们利用java反射也能够获取任何java类的父类。
在许多情况下,尤其当利用类库或者java内置类,我们不能提前知道正在利用的对象的父类是什么,本小节将要展示如何获取该信息。
接下来让我们继续确定“Goat”的父类,另外,我们要知道java.lang.String类是java.lang.Object类的子类:
@Testpublic void givenClass_whenGetsSuperClass_thenCorrect() {    Goat goat = new Goat("goat");    String str = "any string";     Class goatClass = goat.getClass();    Class goatSuperClass = goatClass.getSuperclass();     assertEquals("Animal", goatSuperClass.getSimpleName());    assertEquals("Object", str.getClass().getSuperclass().getSimpleName());}5.6.实现接口

利用java反射,我们也能够获得由给定类实现的接口列表。
让我们检索由“Goat”类和“Animal”类实现的接口类型:
@Testpublic void givenClass_whenGetsImplementedInterfaces_thenCorrect(){    Class goatClass = Class.forName("com.baeldung.reflection.Goat");    Class animalClass = Class.forName("com.baeldung.reflection.Animal");     Class[] goatInterfaces = goatClass.getInterfaces();    Class[] animalInterfaces = animalClass.getInterfaces();     assertEquals(1, goatInterfaces.length);    assertEquals(1, animalInterfaces.length);    assertEquals("Locomotion", goatInterfaces[0].getSimpleName());    assertEquals("Eating", animalInterfaces[0].getSimpleName());}注意断言那,每个类只实现了一个接口。检查接口的名字,我们发现“Goat”实现了“Locomotion”接口并且“Animal”实现了“Eating”接口,和我们代码里写的一样。
你大概注意到“Goat”是“Animal”抽象类的子类,并且这个抽象类实现了接口的eats()方法,所以,“Goat”也继承了“Eating”接口。
因此,值得注意的是,一个类只有显式地用“implements”关键字声明的接口才气出现在返回数组中。
因此,即使一个类实现了接口方法,由于是他的父类实现的那个接口,而且子类没有用“implements”关键字声明那个接口,那么这个接口将不会出现在接口数组中。
5.7. 构造函数、方法和字段

利用反射,我们能够获取到任何对象类的构造函数、方法和字段。
稍后我们将更加深入的了解类的每一个部分,但是现在,仅获取他们的名字就足够了,然后让他们与我们所渴望的结果进行比力。
让我们看看如何获取“Goat”类的结构体:
@Testpublic void givenClass_whenGetsConstructor_thenCorrect(){    Class goatClass = Class.forName("com.baeldung.reflection.Goat");     Constructor[] constructors = goatClass.getConstructors();     assertEquals(1, constructors.length);    assertEquals("com.baeldung.reflection.Goat", constructors[0].getName());}我们也可以像这样获取“Animal”类的字段:
animalClass = Class.forName("com.baeldung.java.reflection.Animal");    Method[] methods = animalClass.getDeclaredMethods();    List actualMethods = getMethodNames(methods);     assertEquals(4, actualMethods.size());    assertTrue(actualMethods.containsAll(Arrays.asList("getName",      "setName", "getSound")));}像“getFieldNames”方法一样,我们添加了一个辅助函数用来从Method对象中获取方法名:
private static List getMethodNames(Method[] methods) {    List methodNames = new ArrayList();    for (Method method : methods)      methodNames.add(method.getName());    return methodNames;}6.获取结构体

利用java反射,我们可以获取任何类的构造函数,甚至可以在运行时创建类对象java.lang.reflect.Constructor类让着成为了大概。
之前,我们仅看了如何获取Constructor对象的数组,从中我们能够获取构造函数的名称。
在这一节,我们将集中在如何获取具体的构造函数。在java中,我们知道,一个类中没有两个构造函数共享完全相同的方法签名。因此,我们将要利用这种唯一性从多个构造函数中获取一个。
为了理解类的特点,我们将要创建一个拥有三个构造函数的Animal的子类Bird。我们将不实现“Locomotion”接口以便可以利用构造函数的参数指定它的行为,去添加更多的变化:
public class Bird extends Animal {    private boolean walks;     public Bird() {        super("bird");    }     public Bird(String name, boolean walks) {        super(name);        setWalks(walks);    }     public Bird(String name) {        super(name);    }     public boolean walks() {        return walks;    }     // standard setters and overridden methods}让我们通过反射确定这个类有三个构造函数:
@Testpublic void givenClass_whenGetsAllConstructors_thenCorrect() {    Class birdClass = Class.forName("com.baeldung.reflection.Bird");    Constructor[] constructors = birdClass.getConstructors();     assertEquals(3, constructors.length);}接下来,我们将要按照声明顺序传递构造函数的参数类型来调用“Bird”类的每一个构造函数:
@Testpublic void givenClass_whenGetsEachConstructorByParamTypes_thenCorrect(){    Class birdClass = Class.forName("com.baeldung.reflection.Bird");     Constructor cons1 = birdClass.getConstructor();    Constructor cons2 = birdClass.getConstructor(String.class);    Constructor cons3 = birdClass.getConstructor(String.class, boolean.class);}这不需要断言,由于当给定顺序的给定参数类型的构造函数不存在时,将抛出一个NoSuchMethodException的异常,并且测试将自动失败。
在最后的测试中,当提供给他们参数时,我们将看到在运行时如何实例化对象:
@Testpublic void givenClass_whenInstantiatesObjectsAtRuntime_thenCorrect() {    Class birdClass = Class.forName("com.baeldung.reflection.Bird");    Constructor cons1 = birdClass.getConstructor();    Constructor cons2 = birdClass.getConstructor(String.class);    Constructor cons3 = birdClass.getConstructor(String.class,      boolean.class);     Bird bird1 = (Bird) cons1.newInstance();    Bird bird2 = (Bird) cons2.newInstance("Weaver bird");    Bird bird3 = (Bird) cons3.newInstance("dove", true);     assertEquals("bird", bird1.getName());    assertEquals("Weaver bird", bird2.getName());    assertEquals("dove", bird3.getName());     assertFalse(bird1.walks());    assertTrue(bird3.walks());}我们通过调用Constructor类的newInstance方法并按照声明顺序传递需要的参数来实例化类对象。然后将结果转换为所需的类型。
对于bird1,我们利用Bird代码中默认的构造函数,自动地设置bird的name属性,并且我们用一个例子测试。
然后我们也实例化了仅有一个name参数的bird2,并进行测试,注意,当我们没有设置移动行为时它将默认为false,如上面两个断言所示。
7.获取字段

之前,我们仅获取字段的名称,在这一章,我们将要展示如何在运行时设置和获取他们的值。
getFeilds()和getField(fieldName)这两个主方法被用来在运行时获取类的字段。
getFeilds()方法返回该类所有可访问公共字段。他将要返回该类和他所有父类的公共字段。
例如,当我们调用Bird类的方法时,我们仅能获取到它的父类Animal的CATEGORY字段,由于Bird类他自己没有声明任何公共字段:
@Testpublic void givenClass_whenGetsPublicFields_thenCorrect() {    Class birdClass = Class.forName("com.baeldung.reflection.Bird");    Field[] fields = birdClass.getFields();    assertEquals(1, fields.length);    assertEquals("CATEGORY", fields[0].getName());}这个方法也有一个变体叫做getField,它通过字段名返回一个Field对象:
@Testpublic void givenClass_whenGetsPublicFieldByName_thenCorrect() {    Class birdClass = Class.forName("com.baeldung.reflection.Bird");    Field field = birdClass.getField("CATEGORY");    assertEquals("CATEGORY", field.getName());}我们不能够访问在父类中声明但没有在子类中声明的私有字段,这也是我们不能够获取字段名的原因。
然而,我们可以通过调用getDeclaredFields方法获取声明在类中的私有字段:
@Testpublic void givenClass_whenGetsDeclaredFields_thenCorrect(){    Class birdClass = Class.forName("com.baeldung.reflection.Bird");    Field[] fields = birdClass.getDeclaredFields();    assertEquals(1, fields.length);    assertEquals("walks", fields[0].getName());}如果我们知道字段的名字,也可以利用其他变体:
@Testpublic void givenClass_whenGetsFieldsByName_thenCorrect() {    Class birdClass = Class.forName("com.baeldung.reflection.Bird");    Field field = birdClass.getDeclaredField("walks");    assertEquals("walks", field.getName());}如果我们输入一个不存在的字段名,我们将会得到一个NoSuchFieldException异常(If we get the name of the field wrong or type in an in-existent field, we will get a NoSuchFieldException.不太理解这个句子,但是根据代码感觉表达的就是我上面说的意思)。
我们得到如下的字段类型:
@Testpublic void givenClassField_whenGetsType_thenCorrect() {    Field field = Class.forName("com.baeldung.reflection.Bird")      .getDeclaredField("walks");    Class fieldClass = field.getType();    assertEquals("boolean", fieldClass.getSimpleName());}接下来,我们将要看看如何获取字段的值以及如何修改他们。为了能够获取和设置字段的值,我们必须先通过调用Field对象的setAccessible函数设置它为可访问的,并给他传入布尔值“true”:
@Testpublic void givenClassField_whenSetsAndGetsValue_thenCorrect() {    Class birdClass = Class.forName("com.baeldung.reflection.Bird");    Bird bird = (Bird) birdClass.newInstance();    Field field = birdClass.getDeclaredField("walks");    field.setAccessible(true);    assertFalse(field.getBoolean(bird));    assertFalse(bird.walks());        field.set(bird, true);        assertTrue(field.getBoolean(bird));    assertTrue(bird.walks());}在上面的测试中,在我们将walks字段的值设置为true之前,它简直是false。
注意我们是如何利用Field对象去设置和获取值的,通过传入我们正在处理的类的实例和我们想要为这个字段赋的新值。
关于Field对象需要重点注意的是,当字段声明为public static时,我们不需要实例化一个包含这些字段的类,我们只需在它的位置传入一个null,仍然可以获取字段的默认值,像下面这样:
@Testpublic void givenClassField_whenGetsAndSetsWithNull_thenCorrect(){    Class birdClass = Class.forName("com.baeldung.reflection.Bird");    Field field = birdClass.getField("CATEGORY");    field.setAccessible(true);    assertEquals("domestic", field.get(null));}8.研究方法

在之前的例子中,我们利用反射仅研究了方法名。然而,java反射是比那更强大的。
利用java反射我们可以在运行时调用方法,并且传递他们需要的参数,就像我们在构造函数那所做的一样。类似地,我们也可以通过指定参数类型调用重载方法。
像字段一样,这有两个主方法用来调用类方法。getMethods方法返该类和其父类的所有公共方法的数组。
这意味着我们利用这个方法可以获取java.lang.Object类的公共方法,像toString, hashCode and notifyAll:
@Testpublic void givenClass_whenGetsAllPublicMethods_thenCorrect(){    Class birdClass = Class.forName("com.baeldung.java.reflection.Bird");    Method[] methods = birdClass.getMethods();    List methodNames = getMethodNames(methods);     assertTrue(methodNames.containsAll(Arrays      .asList("equals", "notifyAll", "hashCode",        "walks", "eats", "toString")));}为了仅获取我们需要的类的公共方法,我们利用getDeclaredMethods方法:
@Testpublic void givenClass_whenGetsOnlyDeclaredMethods_thenCorrect(){    Class birdClass = Class.forName("com.baeldung.java.reflection.Bird");    List actualMethodNames      = getMethodNames(birdClass.getDeclaredMethods());     List expectedMethodNames = Arrays      .asList("setWalks", "walks", "getSound", "eats");     assertEquals(expectedMethodNames.size(), actualMethodNames.size());    assertTrue(expectedMethodNames.containsAll(actualMethodNames));    assertTrue(actualMethodNames.containsAll(expectedMethodNames));}这些方法都有一个奇异变量(singular variation),它返回一个我们知道名称的方法对象:
@Testpublic void givenMethodName_whenGetsMethod_thenCorrect() {    Class birdClass = Class.forName("com.baeldung.reflection.Bird");    Method walksMethod = birdClass.getDeclaredMethod("walks");    Method setWalksMethod = birdClass.getDeclaredMethod("setWalks", boolean.class);     assertFalse(walksMethod.isAccessible());    assertFalse(setWalksMethod.isAccessible());     walksMethod.setAccessible(true);    setWalksMethod.setAccessible(true);     assertTrue(walksMethod.isAccessible());    assertTrue(setWalksMethod.isAccessible());}注意我们是如何获取各个方法并指定他们采用的参数类型的。那些没有传入参数类型的利用空变量参数,仅剩下一个参数——方法名。
接下来,我们将要展示如何在运行时如何调用方法。我们知道Bird类的walks属性的默认值是false,我们想要滴啊用setWalks方法并设置它为true:
@Testpublic void givenMethod_whenInvokes_thenCorrect() {    Class birdClass = Class.forName("com.baeldung.reflection.Bird");    Bird bird = (Bird) birdClass.newInstance();    Method setWalksMethod = birdClass.getDeclaredMethod("setWalks", boolean.class);    Method walksMethod = birdClass.getDeclaredMethod("walks");    boolean walks = (boolean) walksMethod.invoke(bird);     assertFalse(walks);    assertFalse(bird.walks());     setWalksMethod.invoke(bird, true);     boolean walks2 = (boolean) walksMethod.invoke(bird);    assertTrue(walks2);    assertTrue(bird.walks());}注意我们首先调用walks方法,将返回值转换为合适的数据类型并检查他的值。之后调用setWalks方法去改变他的值,再次测试。
9.总结

在这个教程中,我们覆盖了Java反射API,并且看了如何在运行时利用它获取类,接口,字段和方法,而不必预先知道他们编译时的内部结构。
完整的代码和这个教程的例子在原作者的 Github 项目中。
分享淘帖
回复

使用道具

您的回复是对作者最大的奖励

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关于作者

赵洋_cf80

新手猿

  • 主题

    4

  • 帖子

    4

  • 关注者

    0

Archiver|手机版|小黑屋|云大陆 | 赣ICP备18008958号-4|网站地图
Powered by vrarz.com!  © 2019-2020版权所有云大陆