Java 反射机制

2020-04-21  本文已影响0人  AD刘涛

前言

在本文中,我们将探讨java反射,它允许我们检查或/修改classes,接口,字段和方法的运行时属性。当我们在编译时不知道它们的names时,这特别有用。此外,我们可以使用反射实例化新对象、方法调用和获取或设置字段值。

构建项目

要使用java反射,我们不需要包含任何特殊的jar、任何特殊的配置或Maven依赖。出于这样的原因,JDK附带了一组类,这些类位于java.lang.reflect包中。因此,我们需要在我们的代码中导入下面的包

import java.lang.reflect.*;

要访问实例的类、方法和字段信息,我们需要调用getClass方法,它将返回对象运行时的类,返回的类对象提供了访问类信息的方法。

案例

首先,我们将看一个非常基本的案例。它在运行时检查一个简单java对象的字段。

让我们创建一个简单的Person类,这个类只有nameage字段,且没有任何方法。以下就是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对象命名为studentStudentData。然后使用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异常。

参考链接1
参考链接2
原文出处

上一篇下一篇

猜你喜欢

热点阅读