概述

本文是一篇入门级别的教程,旨在探索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);}@Overridepublic String eats() {return "grass";}@Overridepublic String getLocomotion() {return "walks";}@Overrideprotected 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);}@Overridepublic String eats() {return "bird-xxx";}@Overrideprotected 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反射基础指南相关推荐

  1. java 反射基础知识

    java 反射 基础知识 反射:reflection 反射关键类 java 反射部分应用 反射:reflection 在运行中分析类. 在运行中查看和操作对象. 基于反射自己创建对象. 调用不可以访问 ...

  2. java 反射基础 万字详解(Class-Constructor-Method-Field一条龙)

    目录 前言 一.反射及其相关概念 1.什么是反射? 2.反射的用途: ①分析类: ②查看并使用对象: 3.反射的应用场景: 4.类加载器: 类的加载时机: 5.Class对象: 联系: 二.获取Cla ...

  3. 一篇文章弄懂Java反射基础和反射的应用场景

    文章目录 一.Java反射定义 二.Java反射机制实现 1.Class对象获取 2.获取class对象的摘要信息 3.获取class对象的属性.方法.构造函数等 三.反射的应用场景 1.动态代理 2 ...

  4. java 反射基础_Java基础教程:反射基础

    Java基础教程:反射基础 引入反射 反射是什么 能够动态分析类能力的程序称为反射.反射是一种很强大且复杂的机制. Class类 在程序运行期间,Java运行时系统始终为所有对象维护一个被称为运行时的 ...

  5. Java反射基础,代码示例

    文章出处:https://blog.csdn.net/ylyang12/article/details/53469957 说明:本文,在转载时,对内容略作修改,更方便阅读,代码做了调试和格式整理,总之 ...

  6. java反射基础学习

    1.什么是反射? JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法和属性:这种动态获取的信息以及动态调用对象的方法的功能称 ...

  7. Java反射基础(三)--Methods对象的使用

    Method 原文地址:http://docs.oracle.com/javase/tutorial/reflect/member/method.html 1.获得方法类型信息 一个方法的声明包括方法 ...

  8. Java反射基础(二)--Fileds对象的使用

    在说Filed之前,我们先来了解一下Member接口. 反射中定义了一个接口 java.lang.reflect.Member . java.lang.reflect.Field, java.lang ...

  9. Java反射基础(一)--Class对象获取

    Classes Java中,任何一个对象要么是一个引用类型要么是基本数据类型.引用类型指的是那些直接或间接 Java.lang.Object的类.Classse,enum,和接口都是应用类型.基本类型 ...

  10. java反射基础_Java反射基础(一)--Class对象获取

    ClassesJava中,任何一个对象要么是一个引用类型要么是基本数据类型.引用类型指的是那些直接或间接 Java.lang.Object的类.Classse,enum,和接口都是应用类型.基本类型是 ...

最新文章

  1. strcpy +memcpy实现循环右移
  2. 自定义Push和Pop过渡动画
  3. node+express+MongoDB实现小商城服务端
  4. linux下c中嵌套正则表达式
  5. 2015-2016 ACM-ICPC Southwestern Europe Regional Contest (SWERC 15)
  6. [转]学校的统一订书80%该烧掉——IT推荐书单
  7. mysql 升级 openssl_升级openssl
  8. MySQL sql99语法—自连接
  9. access数据类型百度百科_Day 7 基本数据类型
  10. [省选联考 2020 A/B 卷] 冰火战士(树状数组上二分)
  11. [译] ASP.NET 生命周期 – ASP.NET 请求生命周期(三)
  12. andoridstudio run图标是灰色两步解决
  13. 【机器学习】Pandas读取存在Github上的数据集
  14. web渗透测试入门01
  15. 专利申请--权利要求书vs说明书
  16. linux 变量替换字符串,shell中常用的变量处理、字符串操作(之一)
  17. 日常快捷键、代码快捷键
  18. 对一个8位(一字节)数的倒序处理
  19. Oracle的12c版本打补丁
  20. 快速排序的优化1: 选取中间值或随机值作为基准,C语言实现

热门文章

  1. web html div javascript 实现踩黑块游戏
  2. 电脑硬件软件相关知识
  3. mediasoup安装使用
  4. java 获取某一日期的0点0分0秒和23点59分59秒
  5. 计算机启动项在什么地方找,如何查看电脑开机启动项_系统开机启动项快捷键 - 学无忧...
  6. [深度学习概念]·非极大值抑制解析
  7. 打印纸张尺寸换算_纸张的尺寸规格对照
  8. 为什么感觉现在电脑病毒少了?
  9. 12.4 未来幸福的人生——《逆袭大学》连载
  10. IP 地址 与硬件地址