检查构造函数
使用Java反射,我们可以检查任何类的构造函数,甚至可以在运行时创建类对象。这是由于java.lang.reflect
。构造函数类。
在前面,我们只讨论了如何获取构造函数对象的数组,从中我们可以获得构造函数的名称。
在本节中,我们将重点讨论如何检索特定的构造函数。在Java中,正如我们所知,一个类的两个构造函数没有完全相同的方法签名。所以我们将使用这个唯一性从多个构造函数中获取一个构造函数。
为了理解这个类的特性,我们将用三个构造函数创建一个Bird
的Animal
子类。我们将不实现移动,以便可以使用构造函数参数指定该行为,以增加更多的多样性:
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
类调用此方法时,我们将只获得其超类Animal
的CATEGORY
字段,因为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.Object
类toString
、hashCode
和notifyAll
:
@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
暂无评论