Java反射基础指南
Posted aspook
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java反射基础指南相关的知识,希望对你有一定的参考价值。
概述
本文是一篇入门级别的教程,旨在探索Java反射机制,反射允许在运行时操作类、接口、属性以及方法。在编译时如果不知其名称,使用反射则非常方便。另外,还可以通过反射机制实例化类、调用方法、修改和读取属性字段值。
导包
使用反射不需要额外的库或Jar,JDK在java.lang.reflect
包下提供了一系列的类来支持反射,只需要导入这个包即可,如下:
import java.lang.reflect.*;
入门示例
创建一个简单的Person
类,有两个简单的属性字段name
和age
:
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
的超类为Animal
,String
类的超类为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
方法,并按顺序传入对应参数(如果调用无参构造函数则不需要参数)来初始化对象。
检查属性字段
常用方法
前文中仅演示了如何读取属性字段的名称,这里将展示如何在运行时读取、设置属性字段的值。通常会用到以下几个方法:
getFields()
——返回所有public
属性字段,包括从超类里面继承来的。在上面示例中,如果在Bird
类中调用此方法,仅会返回Animal
中的CATEGORY
字段,因为Bird
本身并没有定义public
字段。代码示例如下:Field[] fields = birdClass.getFields();
getField(fieldName)
——顾名思义,传入具体的属性字段名称,仅返回对应的属性。代码示例如下:Field field = birdClass.getField("CATEGORY");
getDeclaredFields()
——只能获取本类自己声明的各种字段,包括私有字段。代码示例如下:Field[] fields = birdClass.getDeclaredFields();
上述代码仅能获取
Bird
类中定义的walks
属性字段。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的常用方式
前文示例只是获取了方法名,但反射可以做到的远远不止这些,下面来展示如何通过反射调用方法。常用的获取方法的方式有:
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
方法都输出了。getMethod(methodName)
——通过方法名获取某个特定的public
方法,如果方法有参数还需传入参数类型。示例如下:
Class<?> birdClass = Class.forName("com.aspook.jr.Bird"); Method method = birdClass.getMethod("setName", String.class);
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: eatsgetDeclaredMethod(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反射基础指南的主要内容,如果未能解决你的问题,请参考以下文章