Java 反射机制
前言
在本文中,我们将探讨java反射,它允许我们检查或/修改classes
,接口,字段和方法的运行时属性。当我们在编译时不知道它们的names
时,这特别有用。此外,我们可以使用反射实例化新对象、方法调用和获取或设置字段值。
构建项目
要使用java反射,我们不需要包含任何特殊的jar
、任何特殊的配置或Maven
依赖。出于这样的原因,JDK
附带了一组类,这些类位于java.lang.reflect
包中。因此,我们需要在我们的代码中导入下面的包
import java.lang.reflect.*;
要访问实例的类、方法和字段信息,我们需要调用getClass
方法,它将返回对象运行时的类,返回的类对象提供了访问类信息的方法。
案例
首先,我们将看一个非常基本的案例。它在运行时检查一个简单java对象的字段。
让我们创建一个简单的Person
类,这个类只有name
和age
字段,且没有任何方法。以下就是Person
类。
public class Person {
private String name;
private int age;
}
现在,我们将使用java反射来探索这个类的所有字段名。为了展示反射的力量,我们将构造一个Person
对象并使用object
作为引用类型。
@Test
public void givenObject_whenGetsFieldNamesAtRuntime_thenCorrect() {
Object person = new Person();
Field[] fields = person.getClass().getDeclaredFields();
List<String> actualFieldNames = getFieldNames(fields);
assertTrue(Arrays.asList("name", "age")
.containsAll(actualFieldNames));
}
这个测试向我们展示了我们能够从person
对象中获得一个字段对象的数组,即使对该对象的引用是该对象的父类。
在上面的例子中,我们只对那些字段名感兴趣,但是还有更多的事情可以做,我们将在后面的小节中看到更多的例子。
注意我们是如何使用helper
方法来提取实际的字段名的,这是非常基本的代码:
private static List<String> getFieldNames(Field[] fields) {
List<String> fieldNames = new ArrayList<>();
for (Field field : fields)
fieldNames.add(field.getName());
return fieldNames;
}
使用Java反射的案例
在继续介绍java反射的不同特性之前,我们将讨论它的一些常见用法。Java反射是非常强大的,在很多方面都非常方便。
例如,在许多情况下,我们对数据库表有一个命名约定。我们可以选择通过在表名前面加上tbl
来增加一致性,这样带有学生数据的表就称为tbl_student_data
。
在这种情况下,我们可以将保存学生数据的java对象命名为student
或StudentData
。然后使用CRUD
范式,我们为每个操作提供一个入口点,这样创建操作只接收一个对象参数。然后我们使用反射来检索对象名和字段名。此时,我们可以将此数据映射到一个DB
表,并将对象字段值赋值给适当的DB
字段名。
探索 Java Classes
在本节中,我们将探讨java反射API中最基本的组件。如前所述,java类对象允许我们访问任何对象的内部细节。我们将检查内部细节,如对象的类名、它们的修饰符、字段、方法、实现的接口等。
Getting Ready
为了更好地理解reflection API
,我们将创建一个抽象的Animal class
,它实现了Eating
接口。这个接口定义了常见动物对象的饮食行为。
首先,这是Eating
接口
public interface Eating {
String eats();
}
其次我们实现了Animal
接口
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
接口。因为Animal
类实现了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对象的各个方面:
Class Names
让我们从类中获取对象的名称开始:
@Test
public 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());
}
注意,类的getSimpleName
方法将返回该对象类的基本名称。然后,其他两个方法返回包名。
让我们看看,如果我们只知道它是完全限定的类名时,我们如何创建Goat
类的对象:
@Test
public 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
的名称时应该包含包的信息,否则我们将得到一个ClassNotFoundException
异常。