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);}@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
的超类为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);}@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
方法,并按顺序传入对应参数(如果调用无参构造函数则不需要参数)来初始化对象。
检查属性字段
常用方法
前文中仅演示了如何读取属性字段的名称,这里将展示如何在运行时读取、设置属性字段的值。通常会用到以下几个方法:
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反射基础指南相关推荐
- java 反射基础知识
java 反射 基础知识 反射:reflection 反射关键类 java 反射部分应用 反射:reflection 在运行中分析类. 在运行中查看和操作对象. 基于反射自己创建对象. 调用不可以访问 ...
- java 反射基础 万字详解(Class-Constructor-Method-Field一条龙)
目录 前言 一.反射及其相关概念 1.什么是反射? 2.反射的用途: ①分析类: ②查看并使用对象: 3.反射的应用场景: 4.类加载器: 类的加载时机: 5.Class对象: 联系: 二.获取Cla ...
- 一篇文章弄懂Java反射基础和反射的应用场景
文章目录 一.Java反射定义 二.Java反射机制实现 1.Class对象获取 2.获取class对象的摘要信息 3.获取class对象的属性.方法.构造函数等 三.反射的应用场景 1.动态代理 2 ...
- java 反射基础_Java基础教程:反射基础
Java基础教程:反射基础 引入反射 反射是什么 能够动态分析类能力的程序称为反射.反射是一种很强大且复杂的机制. Class类 在程序运行期间,Java运行时系统始终为所有对象维护一个被称为运行时的 ...
- Java反射基础,代码示例
文章出处:https://blog.csdn.net/ylyang12/article/details/53469957 说明:本文,在转载时,对内容略作修改,更方便阅读,代码做了调试和格式整理,总之 ...
- java反射基础学习
1.什么是反射? JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法和属性:这种动态获取的信息以及动态调用对象的方法的功能称 ...
- Java反射基础(三)--Methods对象的使用
Method 原文地址:http://docs.oracle.com/javase/tutorial/reflect/member/method.html 1.获得方法类型信息 一个方法的声明包括方法 ...
- Java反射基础(二)--Fileds对象的使用
在说Filed之前,我们先来了解一下Member接口. 反射中定义了一个接口 java.lang.reflect.Member . java.lang.reflect.Field, java.lang ...
- Java反射基础(一)--Class对象获取
Classes Java中,任何一个对象要么是一个引用类型要么是基本数据类型.引用类型指的是那些直接或间接 Java.lang.Object的类.Classse,enum,和接口都是应用类型.基本类型 ...
- java反射基础_Java反射基础(一)--Class对象获取
ClassesJava中,任何一个对象要么是一个引用类型要么是基本数据类型.引用类型指的是那些直接或间接 Java.lang.Object的类.Classse,enum,和接口都是应用类型.基本类型是 ...
最新文章
- strcpy +memcpy实现循环右移
- 自定义Push和Pop过渡动画
- node+express+MongoDB实现小商城服务端
- linux下c中嵌套正则表达式
- 2015-2016 ACM-ICPC Southwestern Europe Regional Contest (SWERC 15)
- [转]学校的统一订书80%该烧掉——IT推荐书单
- mysql 升级 openssl_升级openssl
- MySQL sql99语法—自连接
- access数据类型百度百科_Day 7 基本数据类型
- [省选联考 2020 A/B 卷] 冰火战士(树状数组上二分)
- [译] ASP.NET 生命周期 – ASP.NET 请求生命周期(三)
- andoridstudio run图标是灰色两步解决
- 【机器学习】Pandas读取存在Github上的数据集
- web渗透测试入门01
- 专利申请--权利要求书vs说明书
- linux 变量替换字符串,shell中常用的变量处理、字符串操作(之一)
- 日常快捷键、代码快捷键
- 对一个8位(一字节)数的倒序处理
- Oracle的12c版本打补丁
- 快速排序的优化1: 选取中间值或随机值作为基准,C语言实现