Java反射基础指南

Posted aspook

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java反射基础指南相关的知识,希望对你有一定的参考价值。

概述

本文是一篇入门级别的教程,旨在探索Java反射机制,反射允许在运行时操作类、接口、属性以及方法。在编译时如果不知其名称,使用反射则非常方便。另外,还可以通过反射机制实例化类、调用方法、修改和读取属性字段值。

导包

使用反射不需要额外的库或Jar,JDK在java.lang.reflect包下提供了一系列的类来支持反射,只需要导入这个包即可,如下:

import java.lang.reflect.*;

入门示例

创建一个简单的Person类,有两个简单的属性字段nameage

public class Person 
    private String name;
    private int age;

先创建一个Person的实例,然后获取其声明的属性:

private void getsFieldNamesAtRuntime() 
    Object person = new Person();
    Field[] fields = person.getClass().getDeclaredFields();
    List<String> actualFieldNames = getFieldNames(fields);
    int length = actualFieldNames.size();
    for (int i = 0; i < length; i++) 
        System.out.println(actualFieldNames.get(i));
    


private static List<String> getFieldNames(Field[] fields) 
    List<String> fieldNames = new ArrayList<>();
    for (Field field : fields) 
        fieldNames.add(field.getName());
    
    return fieldNames;

调用测试:

public static void main(String[] args) 
    Person p = new Person();
    p.getsFieldNamesAtRuntime();

上面代码输出:

name
age

这样就在未知的情况下通过反射获取了类的属性字段。

检查Java类

这里来学习一下Java反射最基础的API——java.lang.Class,通过它可以访问一个类的任何内容,如对象的类名、它们的修饰符、字段、方法以及实现的接口等。

准备工作

先准备一个示例,定义一个抽象类Animal并实现Eating接口,Eating接口定义如下:

public interface Eating 
    String eats();

Animal的定义如下:

public abstract class Animal implements Eating 
    public static String CATEGORY = "domestic";
    private String name;

    public Animal(String name) 
        this.name = name;
    

    protected abstract String getSound();

    public String getName() 
        return name;
    

    public void setName(String name) 
        this.name = name;
    

再定义另一个接口Locomotion

public interface Locomotion 
    String getLocomotion();

最后定义一个Goat类继承Animal并实现Locomotion

public class Goat extends Animal implements Locomotion 
    public Goat(String name) 
        super(name);
    

    @Override
    public String eats() 
        return "grass";
    

    @Override
    public String getLocomotion() 
        return "walks";
    

    @Override
    protected String getSound() 
        return "bleat";
    

准备好上述类和接口之后,下面就是见证反射能力的时候了。

获取类名

首先获取一个对象的类名:

public static void main(String[] args) 
    Object goat = new Goat("goat");
    Class<?> clazz = goat.getClass();

    System.out.println("SimpleName: " + clazz.getSimpleName());
    System.out.println("Name: " + clazz.getName());
    System.out.println("CanonicalName: " + clazz.getCanonicalName());

上面代码输出:

SimpleName: Goat
Name: com.aspook.jr.Goat
CanonicalName: com.aspook.jr.Goat

其中getName()getCanonicalName()会返回完整类名,即包含所在的包名。

上面是通过创建一个对象的示例,然后获取其对应的类名。如果我们知道一个类的完全限定名(如com.aspook.jr.Goat),则可以使用另一中方式来获取Class对象:

try 
    Class<?> clazz = Class.forName("com.aspook.jr.Goat");
    System.out.println("SimpleName: " + clazz.getSimpleName());
    System.out.println("Name: " + clazz.getName());
    System.out.println("CanonicalName: " + clazz.getCanonicalName());
 catch (ClassNotFoundException e) 
    e.printStackTrace();

注意Class.forName("com.aspook.jr.Goat")的参数必须是全限定名,否则会抛出ClassNotFoundException

最终输出则跟上面完全一致。

获取类的修饰符

通过Class的getModifiers方法可以获取类的修饰符,但返回值是一个整数,java.lang.reflect.Modifier提供了一些静态方法来分析和转换上述返回的结果,我们用上述示例来验证一下:

try 
    Class<?> goatClass = Class.forName("com.aspook.jr.Goat");
    Class<?> animalClass = Class.forName("com.aspook.jr.Animal");

    int goatMods = goatClass.getModifiers();
    int animalMods = animalClass.getModifiers();

    System.out.println("goatMods is Public: " + Modifier.isPublic(goatMods) + ",value is: " + Modifier.toString(goatMods));
    System.out.println("animalMods is Abstract: " + Modifier.isAbstract(animalMods) + ",value is: " + Modifier.toString(animalMods));
    System.out.println("animalMods is Public: " + Modifier.isPublic(animalMods) + ",value is: " + Modifier.toString(animalMods));


 catch (ClassNotFoundException e) 
    e.printStackTrace();

上面代码输出:

goatMods is Public: true,value is: public
animalMods is Abstract: true,value is: public abstract
animalMods is Public: true,value is: public abstract

上面示例中获取到了Animal类的修饰符为public abstract,而Goat类的修饰符为public。我们可以获取工程中任何类的修饰符,从内存消耗角度考虑通常使用Class.forName的方式,而不是实例化一个类的方式。

获取包信息

通过反射同样可以获取包信息,首先通过Class获取对应的Package类,进而获取包名:

Goat goat = new Goat("goat");
Class<?> goatClass = goat.getClass();
Package pkg = goatClass.getPackage();

System.out.println("Package Name: " + pkg.getName());

上面代码输出:

Package Name: com.aspook.jr

同样如果知道类的全限定名,使用Class.forName的方式也是一样的。

获取超类信息

下面代码以Goat类和String类为例,Goat的超类为AnimalString类的超类为Object

Goat goat = new Goat("goat");
String str = "hello world";

Class<?> goatClass = goat.getClass();
Class<?> goatSuperClass = goatClass.getSuperclass();
System.out.println("goat's super class name: " + goatSuperClass.getSimpleName());
System.out.println("str's super class name: " + str.getClass().getSuperclass().getSimpleName());

上面代码输出:

goat’s super class name: Animal
str’s super class name: Object

获取实现的接口信息

使用反射还可以获取类实现的所有接口,依然以上面准备的几个测试类为例:

try 
    Class<?> goatClass = Class.forName("com.aspook.jr.Goat");
    Class<?> animalClass = Class.forName("com.aspook.jr.Animal");

    Class<?>[] goatInterfaces = goatClass.getInterfaces();
    Class<?>[] animalInterfaces = animalClass.getInterfaces();

    System.out.println("goatInterfaces length: " + goatInterfaces.length);
    System.out.println("animalInterfaces length: " + animalInterfaces.length);

    System.out.println("goatInterfaces Name: " + goatInterfaces[0].getSimpleName());
    System.out.println("animalInterfaces Name: " + animalInterfaces[0].getSimpleName());
 catch (ClassNotFoundException e) 
    e.printStackTrace();

上面代码输出:

goatInterfaces length: 1
animalInterfaces length: 1
goatInterfaces Name: Locomotion
animalInterfaces Name: Eating

getInterfaces()返回的是一个数组,因为一个类可以实现多个接口。另外大家可能有个疑问,Goat继承自Animal,而Animal实现了Eating接口,Goat也实现了Eating接口的方法,但是goatClass.getInterfaces()返回的数组中却没有Eating接口。因为getInterfaces()只是返回使用implements显式实现的接口,而不包含其超类中实现的接口。

获取构造函数、方法及属性字段

使用反射技术,可以检查任何类的构造函数、方法及属性,这里先简单演示如何获取它们的名称。

例如获取Goat类的构造函数:

try 
    Class<?> goatClass = Class.forName("com.aspook.jr.Goat");
    Constructor<?>[] constructors = goatClass.getConstructors();

    for (int i = 0; i < constructors.length; i++) 
        System.out.println("Constructor Name: " + constructors[i].getName());
    
 catch (ClassNotFoundException e) 
    e.printStackTrace();

我们知道Goat只有一个默认的无参构造函数,上面代码输出:

Constructor Name: com.aspook.jr.Goat

获取Animal类的属性字段:

try 
    Class<?> animalClass = Class.forName("com.aspook.jr.Animal");
    Field[] fields = animalClass.getDeclaredFields();

    for (int i = 0; i < fields.length; i++) 
        System.out.println("Field Name: " + fields[i].getName());
    
 catch (ClassNotFoundException e) 
    e.printStackTrace();

上面代码输出:

Field Name: CATEGORY
Field Name: name

获取Animal类的方法:

try 
    Class<?> animalClass = Class.forName("com.aspook.jr.Animal");
    Method[] methods = animalClass.getDeclaredMethods();

    for (int i = 0; i < methods.length; i++) 
        System.out.println("Method Name: " + methods[i].getName());
    
 catch (ClassNotFoundException e) 
    e.printStackTrace();

上面代码输出:

Method Name: getName
Method Name: setName
Method Name: getSound

检查Java构造函数

通过java.lang.reflect.Constructor类,可以获取类的构造函数信息,甚至可以在运行时创建类的实例。前文中仅展示了如何获取构造函数数组(因为一个类可以有多个构造函数,因此返回数组,不同构造函数的方法签名是不同的),并从中获取其名称,这里将演示如何获取其中一个特定的构造函数。

再添加一个测试类Bird,继承自Animal

public class Bird extends Animal 
    private boolean walks;

    public Bird() 
        super("bird");
    

    public Bird(String name) 
        super(name);
    

    public Bird(String name, boolean walks) 
        super(name);
        setWalks(walks);
    

    @Override
    public String eats() 
        return "bird-xxx";
    

    @Override
    protected String getSound() 
        return "bird-yyy";
    

    public boolean isWalks() 
        return walks;
    

    public void setWalks(boolean walks) 
        this.walks = walks;
    

可以看到Bird类有3个构造函数,下面就来分别获取每一个构造函数,并通过构造函数创建一个Bird对象实例:

try 
    Class<?> birdClass = Class.forName("com.aspook.jr.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);

    System.out.println("Bird1 Name: " + bird1.getName());
    System.out.println("Bird2 Name: " + bird2.getName());
    System.out.println("Bird3 Name: " + bird3.getName());


 catch (ClassNotFoundException | NoSuchMethodException
        | IllegalAccessException | InstantiationException
        | InvocationTargetException e) 
    e.printStackTrace();

上面代码输出:

Bird1 Name: bird
Bird2 Name: Weaver bird
Bird3 Name: dove

上述代码分别使用newInstance方法,并按顺序传入对应参数(如果调用无参构造函数则不需要参数)来初始化对象。

检查属性字段

常用方法

前文中仅演示了如何读取属性字段的名称,这里将展示如何在运行时读取、设置属性字段的值。通常会用到以下几个方法:

  1. getFields()——返回所有public属性字段,包括从超类里面继承来的。在上面示例中,如果在Bird类中调用此方法,仅会返回Animal中的CATEGORY字段,因为Bird本身并没有定义public字段。代码示例如下:

    Field[] fields = birdClass.getFields();

  2. getField(fieldName)——顾名思义,传入具体的属性字段名称,仅返回对应的属性。代码示例如下:

    Field field = birdClass.getField("CATEGORY");

  3. getDeclaredFields()——只能获取本类自己声明的各种字段,包括私有字段。代码示例如下:

    Field[] fields = birdClass.getDeclaredFields();

    上述代码仅能获取Bird类中定义的walks属性字段。

  4. getDeclaredField(fieldName)——同样根据属性字段名称返回对应属性。代码示例如下:

    Field field = birdClass.getDeclaredField("walks");

如果某个属性字段不存在或名称错误,则会抛出NoSuchFieldException异常。

获取属性字段类型

代码如下:

try 
    Class<?> birdClass = Class.forName("com.aspook.jr.Bird");
    Field field = birdClass.getDeclaredField("walks");
    Class<?> fieldTypeClass = field.getType();

    System.out.println("Field Type Name: " + fieldTypeClass.getName());
 catch (ClassNotFoundException | NoSuchFieldException e) 
    e.printStackTrace();

上面代码输出:

Field Type Name: boolean

读取设置属性字段

下面来演示如何使用反射读取和设置属性字段。

try 
            Class<?> birdClass = Class.forName("com.aspook.jr.Bird");
            Field field = birdClass.getDeclaredField("walks");
            field.setAccessible(true);

            Bird bird = (Bird) birdClass.newInstance();
            System.out.println("Old Field value: " + field.get(bird));

            field.set(bird, true);
            System.out.println("New Field value: " + field.getBoolean(bird));
         catch (ClassNotFoundException | NoSuchFieldException
                | IllegalAccessException | InstantiationException e) 
            e.printStackTrace();
        

上述代码输出:

Old Field value: false
New Field value: true

可以看到walks初始为false,通过反射将其设置为true。为了访问甚至修改属性字段的值,需要先将其设置为可访问:

field.setAccessible(true);

另外需要注意的是,读取或设置属性字段时,都需要传入对象实例作为参数,其实很好理解,属性是对象所有的,如果没有对象,就如皮之不存毛将焉附。但如果对于静态变量,如Animal中的CATEGORY,则可以不需要传入对象实例,直接传入null即可(原理同不需要对象实例也可访问静态变量),如下代码所示:

try 
    Class<?> birdClass = Class.forName("com.aspook.jr.Bird");
    Field field = birdClass.getField("CATEGORY");
    field.setAccessible(true);

    System.out.println("Old CATEGORY Field value: " + field.get(null));

    field.set(null, "abcde");
    System.out.println("New CATEGORY Field value: " + field.get(null));
 catch (ClassNotFoundException | NoSuchFieldException
        | IllegalAccessException e) 
    e.printStackTrace();

上面代码输出:

Old CATEGORY Field value: domestic
New CATEGORY Field value: abcde

检查方法

获取Method的常用方式

前文示例只是获取了方法名,但反射可以做到的远远不止这些,下面来展示如何通过反射调用方法。常用的获取方法的方式有:

  1. getMethods()——返回所有公有方法,包括从父类以及接口继承来的,因此返回结果是一个数组。

    示例如下:

    try 
       Class<?> birdClass = Class.forName("com.aspook.jr.Bird");
       Method[] methods = birdClass.getMethods();
    
       for (int i = 0; i < methods.length; i++) 
           System.out.println("Method Name: " + methods[i].getName());
       
     catch (ClassNotFoundException e) 
       e.printStackTrace();
    

    上面代码输出:

    Method Name: eats
    Method Name: isWalks
    Method Name: setWalks
    Method Name: getName
    Method Name: setName
    Method Name: wait
    Method Name: wait
    Method Name: wait
    Method Name: equals
    Method Name: toString
    Method Name: hashCode
    Method Name: getClass
    Method Name: notify
    Method Name: notifyAll

    可以看到Bird本身的、Animal父类的、接口的甚至Object类中的public方法都输出了。

  2. getMethod(methodName)——通过方法名获取某个特定的public方法,如果方法有参数还需传入参数类型。

    示例如下:

    Class<?> birdClass = Class.forName("com.aspook.jr.Bird");
    Method method = birdClass.getMethod("setName", String.class);
  3. getDeclaredMethods()——仅返回到当前类中的方法,包括公有、私有、保护的等各种方法。

    示例如下:

    try 
       Class<?> birdClass = Class.forName("com.aspook.jr.Bird");
       Method[] methods = birdClass.getDeclaredMethods();
    
       for (int i = 0; i < methods.length; i++) 
           System.out.println("Method Name: " + methods[i].getName());
       
     catch (ClassNotFoundException e) 
       e.printStackTrace();
    

    上面代码输出:

    Method Name: setWalks
    Method Name: isWalks
    Method Name: getSound
    Method Name: eats

  4. getDeclaredMethod(methodName)——通过方法名称获取本类中对应的方法,如果尝试获取超类中的方法,则会报NoSuchMethodException异常,如果方法有参数还需传入参数类型。

    示例如下:

    Class<?> birdClass = Class.forName("com.aspook.jr.Bird");
    Method method = birdClass.getDeclaredMethod("getSound");

调用方法

直接上示例代码:

try 
    Class<?> birdClass = Class.forName("com.aspook.jr.Bird");
    Bird bird = (Bird) birdClass.newInstance();
    Method setWalksMethod = birdClass.getDeclaredMethod("setWalks", boolean.class);
    Method isWalksMethod = birdClass.getDeclaredMethod("isWalks");
    Method getNameMethod = birdClass.getMethod("getName");
    Method setNameMethod = birdClass.getMethod("setName", String.class);

    boolean walks = (boolean) isWalksMethod.invoke(bird);
    System.out.println("old walks value is: " + walks);

    String name = (String) getNameMethod.invoke(bird);
    System.out.println("old name value is: " + name);

    setWalksMethod.invoke(bird, true);
    boolean newWalks = (boolean) isWalksMethod.invoke(bird);
    System.out.println("new walks value is: " + newWalks);

    setNameMethod.invoke(bird, "newBirdName");
    String newName = (String) getNameMethod.invoke(bird);
    System.out.println("new name value is: " + newName);
 catch (ClassNotFoundException | NoSuchMethodException
        | IllegalAccessException | InstantiationException
        | InvocationTargetException e) 
    e.printStackTrace();

上面代码输出:

old walks value is: false
old name value is: bird
new walks value is: true
new name value is: newBirdName

可见反射是通过invoke方法来调用方法,注意也需要传入对象实例作为参数(如果是静态方法,则对象实例不是必需的,传null即可),通过输出对比可知调用生效了,修改了属性的值。

动态代理

之前总结过一篇文章 理解Java动态代理

总结

上文整体介绍了Java反射API中最基础和常用的部分,如通过反射获取类、接口、字段、方法等,设置属性字段、调用方法及动态代理的使用。

参考资料:http://www.baeldung.com/java-reflection

以上是关于Java反射基础指南的主要内容,如果未能解决你的问题,请参考以下文章

Android -- 面试复习指南之 Java 基础

爆肝五万字,写给Java零基础小白的自学路线和进阶指南,收藏能省掉几万学费。

Rust入坑指南:齐头并进(下)

2021最新Java面试笔试,详细的Java学习指南

Java反射基础

Java基础-反射