Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。
反射机制的优点与缺点
为什么要用反射机制?直接创建对象不就可以了吗,这就涉及到了动态与静态的概念
- 静态编译:在编译时确定类型,绑定对象,即通过。
- 动态编译:运行时确定类型,绑定对象。动态编译最大限度发挥了java的灵活性,体现了多态的应用,可以降低类之间的藕合性。
反射机制的优点:
可以实现动态创建对象和编译,体现出很大的灵活性,特别是在J2EE的开发中
它的灵活性就表现的十分明显。比如,一个大型的软件,不可能一次就把把它设计的很完美,当这个程序编 译后,发布了,当发现需要更新某些功能时,我们不可能要用户把以前的卸载,再重新安装新的版本,假如 这样的话,这个软件肯定是没有多少人用的。采用静态的话,需要把整个程序重新编译一次才可以实现功能 的更新,而采用反射机制的话,它就可以不用卸载,只需要在运行时才动态的创建和编译,就可以实现该功 能。
反射的缺点
它的缺点是对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于只直接执行相同的操作。
关于Class
说反射我们就不得不说一下Class
- 1、Class是一个类,一个描述类的类(也就是描述类本身),封装了描述方法的Method,描述字段的Filed,描述构造器的Constructor等属性
- 2、对象照镜子后(反射)可以得到的信息:某个类的数据成员名、方法和构造器、某个类到底实现了哪些接口。
- 3、对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含了特定某个类的有关信息。
- 4、Class 对象只能由系统建立对象
- 5、一个类在 JVM 中只会有一个Class实例
获取Class的三种方法
- 1、Class cl=A.class;
JVM将使用类A的类装载器, 将类A装入内存(前提是:类A还没有装入内存),不对类A做类的初始化工作.返回类A的Class的对象。 -
2、Class clazz=对象引用obj.getClass();
返回引用obj运行时真正所指的对象(因为:子对象的引用可能会赋给父对象的引用变量中)所属的类的Class的对象 。 -
3、Class.forName(类名字符串);
(注:类名字符串是包名+类名)装入类,并做类的静态初始化,返回Class的对象
.getClass()是动态的,其余是静态的。
.class和Class.forName()只能返回类内field的默认值,getClass可以返回当前对象中field的最新值
Class.forName() 返回的是一个类,.newInstance() 后才创建一个对象,Class.forName()的作用是要求JVM查找并加载指定的类,也就是说JVM会执行该类的加载
举例:
package anno;
public class GetClass {
public static void main(String[] args) {
Class clazz = null;
// 1 直接通过类名.Class的方式得到
clazz = GetClass.class;
System.out.println("通过类名: " + clazz);
// 2 通过对象的getClass()方法获取,这个使用的少(一般是传的是Object,不知道是什么类型的时候才用)
Object obj = new GetClass();
clazz = obj.getClass();
System.out.println("通过getClass(): " + clazz);
// 3 通过全类名获取,用的比较多,但可能抛出ClassNotFoundException异常
try {
clazz = Class.forName("anno.GetClass");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println("通过全类名获取: " + clazz);
}
}
输出
通过类名: class anno.GetClass
通过getClass(): class anno.GetClass
通过全类名获取: class anno.GetClass
1、利用newInstance创建对象:调用的类必须有无参的构造器
package reflect;
public class Instance {
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
Class clazz;
try {
//使用Class类的newInstance()方法创建类的一个对象
//实际调用的类的那个 无参数的构造器(这就是为什么写的类的时候,要写一个无参数的构造器,就是给反射用的)
//一般的,一个类若声明了带参数的构造器,也要声明一个无参数的构造器
clazz = Class.forName("reflect.Instance");
Instance obj = (Instance) clazz.newInstance();
System.out.println(obj);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
2、获取构造器方法Constructor及参数Parameter
代码
package reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
public class Constructors {
public Constructors() {
System.out.println("无参构造函数");
}
public Constructors( String s) {
System.out.println("有1个参构造函数");
System.out.println(s);
}
public Constructors(final String s, int num) {
System.out.println("有2个参构造函数");
System.out.println(s);
}
public static void main(String[] args) {
try {
// 1、获取指定构造方法
Class clazz = Class.forName("reflect.Constructors");
// 无参构造函数生成对象
clazz.newInstance();
//Constructor con = clazz.getConstructor(String.class, int.class);
//Constructors cons = (Constructors) con.newInstance("获取构造器实例化对象", 4);
// 2、 获取全部构造方法
Constructor[] conarr = clazz.getConstructors();
for (Constructor constructor : conarr) {
// 获取每个构造函数的参数字节码对象
Class[] parameterTypes = constructor.getParameterTypes();
for (Class pclass : parameterTypes) {
System.out.print(pclass.getName() + ", ");
}
System.out.println();
// 获取构造参数对象
Parameter[] parameters = constructor.getParameters();
for (Parameter parameter : parameters) {
System.out.println("通过parameter.getModifiers()获取参数修饰符:" + Modifier.toString(parameter.getModifiers()));
System.out.println("通过parameter.getName()获取参数名:" + parameter.getName());
System.out.println("通过parameter.getParameterizedType()获取参数化类型(泛型):" + parameter.getParameterizedType());
System.out.println("通过parameter.toString()获取参数的字符串描述:" + parameter.toString());
System.out.println("通过parameter.isSynthetic()判断参数是否是合成的:" + parameter.isSynthetic());
System.out.println("通过parameter.isImplicit()判断参数是否是隐式的:" + parameter.isImplicit());
System.out.println("通过parameter.isNamePresent()判断参数是否存在可用:" + parameter.isNamePresent());
System.out.println("通过parameter.isVarArgs()判断参数是否是可变的:" + parameter.isVarArgs() + "\n");
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
输出
无参构造函数
有2个参构造函数
获取构造器实例化对象
java.lang.String, int,
通过parameter.getModifiers()获取参数修饰符:final
通过parameter.getName()获取参数名:s
通过parameter.getParameterizedType()获取参数化类型(泛型):class java.lang.String
通过parameter.toString()获取参数的字符串描述:final java.lang.String s
通过parameter.isSynthetic()判断参数是否是合成的:false
通过parameter.isImplicit()判断参数是否是隐式的:false
通过parameter.isNamePresent()判断参数是否存在可用:true
通过parameter.isVarArgs()判断参数是否是参数列表:false
通过parameter.getModifiers()获取参数修饰符:
通过parameter.getName()获取参数名:num
通过parameter.getParameterizedType()获取参数化类型(泛型):int
通过parameter.toString()获取参数的字符串描述:int num
通过parameter.isSynthetic()判断参数是否是合成的:false
通过parameter.isImplicit()判断参数是否是隐式的:false
通过parameter.isNamePresent()判断参数是否存在可用:true
通过parameter.isVarArgs()判断参数是否是参数列表:false
java.lang.String,
通过parameter.getModifiers()获取参数修饰符:
通过parameter.getName()获取参数名:s
通过parameter.getParameterizedType()获取参数化类型(泛型):class java.lang.String
通过parameter.toString()获取参数的字符串描述:java.lang.String s
通过parameter.isSynthetic()判断参数是否是合成的:false
通过parameter.isImplicit()判断参数是否是隐式的:false
通过parameter.isNamePresent()判断参数是否存在可用:true
通过parameter.isVarArgs()判断参数是否是参数列表:false
4、获取Method
java中get与getDeclared方法的区别,get包括自身public + 继承public的字段,方法或者自身public构造函数或者继承注解,getDeclared只包括自身所有字段,方法,构造函数,注解
package reflect;
import java.lang.reflect.Method;
public class TMethod {
private void test1(){
System.out.println("私有测试方法1");
}
private void test2(String s){
System.out.println("私有测试方法2,参数String="+s);
}
public void test3(String s,int num){
System.out.println("公有测试方法3,参数String="+s+":num="+num);
}
public static void main(String[] args) {
try {
Class clazz = Class.forName("reflect.TMethod");
//1、得到clazz 对应的类中有哪些方法,不能获取private方法
Method[] methods =clazz.getMethods();
System.out.print("自身及继承Object类的公有方法: ");
for (Method method : methods){
System.out.print(method.getName() + ", ");
}
//2、获取所有的方法(且只获取当着类声明的方法,包括private方法)
Method[] methods2 = clazz.getDeclaredMethods();
System.out.print("\n自身所有方法(公有,私有): ");
for (Method method : methods2){
System.out.print(method.getName() + ", ");
}
//3、获取指定的方法
Method method = clazz.getDeclaredMethod("test2",String.class);//第一个参数是方法名,后面的是方法里的参数
System.out.println("\nmethod2 : " + method);
Method method2 = clazz.getDeclaredMethod("test3",String.class ,int.class);//第一个参数是方法名,后面的是方法里的参数
System.out.println("method3: " + method2);
//4、执行方法!
Object obj = clazz.newInstance();
method2.invoke(obj, "changwen", 22);
//这是本类中调用,不必设置 setAccessible(true);如果是其他类中必须设置,不然method是私有的,无法在其他类中执行
method.setAccessible(true);
method.invoke(obj,"dddd");
} catch (Exception e) {
e.printStackTrace();
}
}
}
输出
自身及Object类的公有方法: main, test3, wait, wait, wait, equals, toString, hashCode, getClass, notify, notifyAll,
自身所有方法(公有,私有): main, test1, test2, test3,
method2 : private void reflect.TMethod.test2(java.lang.String)
method3: public void reflect.TMethod.test3(java.lang.String,int)
公有测试方法3,参数String=changwen:num=22
私有测试方法2,参数String=dddd
5、Invoke巧妙使用
从上一个例子中,我们看到可以使用meithod对象直接通过对象和参数来执行方法
下面我们定义2个方法来通过直接传入类方法名和参数以及类的Class对象或者类对象来直接运行类的方法
package reflect;
import java.lang.reflect.Method;
public class TInvoke {
/**
* 获取clazz 的methodName 方法, 该方法可能是私有方法
*/
public Method getMethod(Class clazz, String methodName, Class ... parameterTypes) {
//注意这个循环里的内容!!!
for (; clazz != Object.class; clazz = clazz.getSuperclass()){
try {
return clazz.getDeclaredMethod(methodName, parameterTypes);
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
/**
* @param className 某个类的全类名
* @param methodName 类的一个方法的方法名,该方法也可能是私有方法
* @param args 调用该方法需要传入的参数 ...可变参数的意思
* @return 调用方法后的返回值
*/
public Object invoke(String className, String methodName,Class<?>[] parameterTypes,Object ... args) {
Object obj = null;
try {
obj = Class.forName(className).newInstance();
return invoke(obj, methodName, parameterTypes,args);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return invoke(null, methodName,parameterTypes, args);
}
/**
* @param obj 方法执行的那个对象
* @param methodName 类的一个方法的方法名,该方法也可能是私有方法,还可能是该方法在父类中定义的私有方法
* @param args 调用该方法需要传入的参数 ...可变参数的意思
* @return 调用方法后的返回值
*/
public Object invoke(Object obj, String methodName,Class<?>[] parameterTypes, Object ... args) {
try {
//2、执行Method方法
Method method = getMethod(obj.getClass(), methodName,parameterTypes);
//通过反射执行private方法
method.setAccessible(true);
//3、返回方法的返回值
return method.invoke(obj,args);
} catch (Exception e) {
}
return null;
}
public void testInvoke(){
Object obj = new TMethod();
invoke(obj, "test3",new Class<?>[] {String.class,int.class},"test3",10);
Object result = invoke(obj, "test2",new Class<?>[] {String.class},"test2");
System.out.println(result);
}
public static void main(String[] args) {
try {
TInvoke obj = new TInvoke();
obj.testInvoke();
} catch (Exception e) {
e.printStackTrace();
}
}
}
输出
公有测试方法3,参数String=test3:num=10
私有测试方法2,参数String=test2
null
6、获取Field
package reflect;
import java.lang.reflect.Field;
class Person {
public String name;
private Integer age;
public Person() {
}
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
public Integer getAge() {
return age;
}
}
class Student extends Person{
}
public class TField {
/**
* Field: 封装了字段的信息
*/
public void testField() throws
ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
Class clazz = Class.forName("reflect.Person");
//1、获取字段
//1.1 获取Field的数组,私有字段也能获取
Field[] fields = clazz.getDeclaredFields();
for (Field field: fields) {
System.out.print(field.getName() + ", ");
}
//1.2 获取指定名字的Field(如果是私有的,见下面的4)
Field field = clazz.getDeclaredField("name");
System.out.println("\n获取指定Field名=: " + field.getName());
Person person = new Person("ABC", 12);
//2、获取指定对象的Field的值
Object val = field.get(person);
System.out.println("获取指定对象字段'name'的Field的值=: " + val);
//3、设置指定对象的Field的值
field.set(person, "changwen2");
System.out.println("设置指定对象字段'name'的Field的值=: " + person.name);
//4、若该字段是私有的,需要调用setAccessible(true)方法
Field field2 = clazz.getDeclaredField("age");
field2.setAccessible(true);
System.out.println("获取指定私有字段名=: " + field2.getName());
}
/**
* 一个实例:
* 反射获取一个继承Person2的Student类
* 设置字段"age"=20(该字段可能为私有,可能在其父类中)
*/
public void testClassField() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
String className = "reflect.Student";
String fieldName = "age"; //可能为私有,可能在其父类中
Object val = 20;
//创建className 对应类的对象,并为其fieldName赋值为val
Class clazz = Class.forName(className);
Field field = null;
for (Class clazz2 = clazz; clazz2 != Object.class; clazz2 = clazz2.getSuperclass()){
try {
field = clazz2.getDeclaredField(fieldName);
} catch (Exception e) {
}
}
Object obj = clazz.newInstance();
assert field != null;
field.setAccessible(true);
field.set(obj, val);
Student stu = (Student) obj;
System.out.println("age = " + stu.getAge());
}
public static void main(String[] args) {
TField field=new TField();
try {
field.testField();
System.out.println("===============================s");
field.testClassField();
} catch (Exception e) {
e.printStackTrace();
}
}
}
输出
name, age,
获取指定Field名=: name
获取指定对象字段'name'的Field的值=: ABC
设置指定对象字段'name'的Field的值=: changwen2
获取指定私有字段名=: age
===============================
age = 20
7、反射获取注解
JDK5.0 在 java.lang.reflect包下新增了 AnnotatedElement接口,该接口代表程序中可以接受注释的程序元素
- 当一个 Annotation类型被定义为运行时Annotation后,该注释才是运行时可见,当 class文件被载入时保存在 class文件中的 Annotation才会被虚拟机读取
- 程序可以调用AnnotationElement对象的如下方法来访问 Annotation信息
- 获取 Annotation实例:
- getAnnotation(Class
annotationClass) - getDeclaredAnnotations()
- getParameterAnnotations()
- getAnnotation(Class
关于注解详解,请看我之前的文章 https://www.shadowwu.club/2018/10/21/java_annotation/index.html
参考
java反射之包装类和基础数据类型的坑(分享个反射工具方法) https://blog.csdn.net/qq_20641565/article/details/78797975
Java中反射机制详解 https://www.cnblogs.com/whgk/p/6122036.html
Java反射机制详解 https://www.cnblogs.com/bojuetech/p/5896551.html