4年前 (2020-10-22)  Java系列 |   抢沙发  181 
文章评分 0 次,平均分 0.0

Java反射的作用

检查构造函数

使用Java反射,我们可以检查任何类的构造函数,甚至可以在运行时创建类对象。这是由于java.lang.reflect。构造函数类。

在前面,我们只讨论了如何获取构造函数对象的数组,从中我们可以获得构造函数的名称。

在本节中,我们将重点讨论如何检索特定的构造函数。在Java中,正如我们所知,一个类的两个构造函数没有完全相同的方法签名。所以我们将使用这个唯一性从多个构造函数中获取一个构造函数。

为了理解这个类的特性,我们将用三个构造函数创建一个BirdAnimal子类。我们将不实现移动,以便可以使用构造函数参数指定该行为,以增加更多的多样性:

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
}

让我们使用反射来确认这个类有三个构造函数:

@Test
public void givenClass_whenGetsAllConstructors_thenCorrect() {
    Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
    Constructor<?>[] constructors = birdClass.getConstructors();
 
    assertEquals(3, constructors.length);
}

接下来,我们将通过按声明顺序传递构造函数的参数类类型来检索Bird类的每个构造函数:

@Test
public 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,测试将自动失败。

在上一个测试中,我们将了解如何在运行时实例化对象,同时提供它们的参数:

@Test
public 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());
}

我们通过调用构造函数类的newInstance方法并按声明的顺序传递所需参数来实例化类对象。然后将结果强制转换为所需的类型。

也可以使用Class.newInstance()方法。然而,自从java9以来,这个方法就被弃用了,我们不应该在现代Java项目中使用它。

对于bird1,我们使用来自Bird代码的默认构造函数,它自动将名称设置为Bird,并通过测试进行确认。

然后我们只使用一个名称和test实例化bird2,记住当我们没有将moving行为设置为false时,如前两个断言所示。

检查字段

之前,我们只检查了字段的名称,在本节中,我们将展示如何在运行时获取和设置它们的值。

有两种主要方法用于在运行时检查类的字段:getFields()getField(fieldName)

方法的作用是:返回相关类的所有可访问公共字段。它将返回类和所有超类中的所有公共字段。

例如,当我们对Bird类调用此方法时,我们将只获得其超类AnimalCATEGORY字段,因为Bird本身没有声明任何公共字段:

@Test
public 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对象:

@Test
public void givenClass_whenGetsPublicFieldByName_thenCorrect() {
    Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
    Field field = birdClass.getField("CATEGORY");
 
    assertEquals("CATEGORY", field.getName());
}

我们无法访问在超类中声明且未在子类中声明的私有字段。这就是为什么我们不能访问name字段。

但是,我们可以通过调用getDeclaredFields方法来检查我们正在处理的类中声明的私有字段:

@Test
public 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());
}

如果我们知道字段的名称,我们也可以使用它的其他变体:

@Test
public void givenClass_whenGetsFieldsByName_thenCorrect() {
    Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
    Field field = birdClass.getDeclaredField("walks");
 
    assertEquals("walks", field.getName());
}

如果字段名称输入错误或在现有字段中键入,则会出现NoSuchFieldException

字段类型如下:

@Test
public void givenClassField_whenGetsType_thenCorrect() {
    Field field = Class.forName("com.baeldung.reflection.Bird")
      .getDeclaredField("walks");
    Class<?> fieldClass = field.getType();
 
    assertEquals("boolean", fieldClass.getSimpleName());
}

接下来,我们将研究如何访问字段值并修改它们。为了能够获得字段的值,更不用说设置它了,我们必须首先通过调用field对象上的setAccessible方法来设置它的可访问性,并将boolean true传递给它:

@Test
public void givenClassField_whenSetsAndGetsValue_thenCorrect() {
    Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
    Bird bird = (Bird) birdClass.getConstructor().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,然后仍然获得字段的默认值,如下所示:

@Test
public 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));
}

检查方法

在前面的示例中,我们只使用反射来检查方法名。然而,Java反射比这更强大。

使用Java反射,我们可以在运行时调用方法并传递它们所需的参数,就像我们对构造函数所做的那样。类似地,我们也可以通过指定每个方法的参数类型来调用重载方法。

与字段一样,有两种主要方法可用于检索类方法。getMethods方法返回类和超类的所有公共方法的数组。

这意味着使用此方法,我们可以获得java.lang.ObjecttoStringhashCodenotifyAll

@Test
public void givenClass_whenGetsAllPublicMethods_thenCorrect(){
    Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
    Method[] methods = birdClass.getMethods();
    List<String> methodNames = getMethodNames(methods);
 
    assertTrue(methodNames.containsAll(Arrays
      .asList("equals", "notifyAll", "hashCode",
        "walks", "eats", "toString")));
}

要只获取我们感兴趣的类的公共方法,我们必须使用getDeclaredMethods方法:

@Test
public void givenClass_whenGetsOnlyDeclaredMethods_thenCorrect(){
    Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
    List<String> actualMethodNames
      = getMethodNames(birdClass.getDeclaredMethods());
 
    List<String> expectedMethodNames = Arrays
      .asList("setWalks", "walks", "getSound", "eats");
 
    assertEquals(expectedMethodNames.size(), actualMethodNames.size());
    assertTrue(expectedMethodNames.containsAll(actualMethodNames));
    assertTrue(actualMethodNames.containsAll(expectedMethodNames));
}

这些方法中的每一个都有一个单一的变量,它返回一个我们知道其名称的方法对象:

@Test
public void givenMethodName_whenGetsMethod_thenCorrect() throws Exception {
    Bird bird = new Bird();
    Method walksMethod = bird.getClass().getDeclaredMethod("walks");
    Method setWalksMethod = bird.getClass().getDeclaredMethod("setWalks", boolean.class);
 
    assertTrue(walksMethod.canAccess(bird));
    assertTrue(setWalksMethod.canAccess(bird));
}

请注意我们是如何检索各个方法并指定它们所采用的参数类型的。那些不采用参数类型的函数将使用空变量参数进行检索,只剩下一个参数,即方法名。

接下来,我们将展示如何在运行时调用方法。我们知道Bird类的walks属性默认为false,我们希望调用其setWalks方法并将其设置为true

@Test
public void givenMethod_whenInvokes_thenCorrect() {
    Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
    Bird bird = (Bird) birdClass.getConstructor().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方法来更改该值并再次测试。

反射结论

在本教程中,我们介绍了java reflection api,并研究了如何在运行时使用它来检查类、接口、字段和方法,而不必事先知道它们在编译时的内部结构。

 

除特别注明外,本站所有文章均为老K的Java博客原创,转载请注明出处来自https://javakk.com/690.html

关于

发表评论

表情 格式

暂无评论

登录

忘记密码 ?

切换登录

注册