一、什么是java反射
java的反射机制是指在运行状态中,对于任意的一个类,都能获取到这个类的属性和方法,无论是private还是public。对于任意的一个对象,都能调用到它的任意一个方法和属性,无论是private还是public。这种动态获取类的信息和动态调用对象的方法,我们叫做java的反射机制。当然这种机制如果自己使用不当,在一定程度上破坏了java面向对象的封装性。
二、java反射实例和用法
下面有个person的bean对象,我们就用这个简单的例子来学习,要想使用反射,首先得获取对应类的class对象。
package com.chendsir.exercisejava;
public class Person {
private int id;
private String name;
public String age;
public Person(String age) {
this.age = age;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public String getAge() {
return age;
}
}
(1) :获取class对象。
我们给这个例子加一个man函数,在man函数里面我们有三种方式来获取这个Person类的class对象。如果通过第一种方式Class.forName没有获取到指定类会报ClassNotFoundException 异常。
public static void main(String[] args) throws ClassNotFoundException {
// 第一种方式,使用Class类的forName,传入的参数是想要获取类的完整包名加类名
Class c1 =Class.forName("com.chendsir.exercisejava.Person");
// 第二种方式,直接使用想要获取类的class属性来获取Class对象。
Class c2 =Person.class;
// 第三种方式,通过实例对象的getClass方法获取
Person person = new Person();
Class<?> c3 = person.getClass();
}
(2): 获取类的属性。
如下可通过默认获取所有属性以及指定属性的两种方式获取。如果没有获取到指定属性会报NoSuchFieldException异常。
Class c4 = Person.class;//上面说的通过类的class属性获取Class对象
// getDeclaredFields根据名字就可以知道,获取所有声明了的属性,无论什么访问权限。
Field[] allFileds = c4.getDeclaredFields(); // id ,name,age都能获取到
// getFields是访问该Class的所有public属性
Field[] pubFileds = c4.getFields(); // 只能访问到age属性,因为age属性是public
// 通过指定参数名id,访问Person类的id属性,找不到会报NoSuchFieldException异常
Field idField = c4.getDeclaredField("id");
// 通过指定参数吗age,访问Person类的公有age属性,找不到会报NoSuchFieldException异常
Field ageField = c4.getField("age");
(3):获取类方法
获取方法和获取属性一样,可以默认获取所有,以及指定形参列表的方法。如果没有获取到指定的方法会报NoSuchMethodException 异常。
public void setAge(String tempAge) { // 添加一个方法
this.age = tempAge;
}
public static void main(String[] args) throws NoSuchMethodException {
Class c5 =Class.forName("com.chendsir.exercisejava.Person");
// 获取Class对象所有声明的方法,getId,getName,getAge
Method[] methods = c5.getDeclaredMethods();
// 获取Class对象所有的public方法,假如把上例子的getId,getName改为private,这里只能获取到getAge
Method[] allpubmethods = c5.getMethods();
// 此处获得的是setAge() 没有形参的方法,且权限是public
Method method = c5.getMethod("setAge");
// 此处获得的是setAge(String tempAge) 方法,而且形参为String类型
Method method2 = c5.getMethod("setAge",String.class);
// 同上,不过无论是public还是private都能获取到
Method method3 = c5.getDeclaredMethod("setAge",String.class);
}
(4) 获取类的构造函数
获取类的构造函数也可以获取所有的构造函数,和指定的方式。和上面是一个套路。相信大家已经熟悉了。
public Person(String age) { // 添加一个带参数的构造函数
this.age = age;
}
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
Class c5 =Class.forName("com.chendsir.exercisejava.Person");
// 获取所有的构造函数
Constructor<?>[] constructors = c5.getDeclaredConstructors();
// 获取public的构造函数
Constructor<?> [] constructors1 = c5.getConstructors();
// 获取带形参String参数的构造函数
Constructor<?> constructor = c5.getDeclaredConstructor(String.class);
// 获取带形参String参数的Public构造函数
Constructor<?> constructor1 = c5.getConstructor(String.class);
}
(5) 获取类的注解
获取注解也可以直接获取类中的所有注解或者指定一个注解,同样的套路,Retrofit的自定义ConvertFactory的来定义请求格式和返回格式的时候,经常用到Annotation这个。
// 获取所有注解,retorfit自定义的话,通过遍历可以来判断你的请求或者返回格式是xml还是json
Annotation[] annotations = c5.getAnnotations();
// 获取注解为Override的注解
Annotation annotation =c5.getAnnotation(Override.class);
(6) 通过反射来生成对象
如何得到一个类的实例对象呢,在第(4) 步中我们已经知道怎么获取构造函数了,既然构造函数都有了,直接调用构造函数的newInstance方法就可以直接生成一个实例了。也可以直接通过Class对象的newInstance()来生成一个对象,调用的是无参构造函数,此处原生native方法会抛出InstantiationException, IllegalAccessException两个异常。
Class c5 =Class.forName("com.chendsir.exercisejava.Person");
// 第一种方式,直接通过Class对象的newInstance方法来生成对象
Object obj1 = c5.newInstance();
// 第二种方式,通过构造函数来生成对象
Constructor<?> constructor = c5.getDeclaredConstructor(String.class);
Object obj2 = constructor.newInstance("18");// 我们获取的是有参构造函数,这里传入一个参数。
(7) 如何调用类的方法以及修改其属性
通过以上我们已经拿到了对象以及得到了方法,那么一个方法如何取调用以及传参呢。我们一般是通过invoke的方式去调用方法并传入相应参数。
...省略
public void setAge(String tempAge) {
this.age = tempAge;
}
...省略
Class c5 =Class.forName("com.chendsir.exercisejava.Person");
Object obj1 = c5.newInstance();
// 获取setAge方法
Method method = c5.getDeclaredMethod("setAge", String.class);
// 第一个参数是生成的实例对象obj1,第二个参数是我们调用setAge传入的参数名
method.invoke(obj1,"18");
假如我们的Class中没有setAge这个方法,但是我们又想要修改age的值,该如何做呢。我们通过第(2)步可以直接获取一个类的属性,Filed类已经给我们提供了get和set方法了,如果是基本类型,用setInt、setChar、setBoolean来改,如果是引用类型直接用set方法,通过这个可以直接改,非常强大。
Object obj1 = c5.newInstance();
// 我们先获取id这个属性
Field field = c5.getField("id");
// 通过setInt的方法来修改这个对象的id值为2
field.setInt(obj1,2);
// 如果要修改age呢?因为我定义的age是String类型的,不是基本类型,而是引用。
Field field2 = c5.getField("age");
// 通过set的方法来修改这个对象的age值为19
field2.set(obj1,"19");
// 想要获取的话,用get方法
field2.get(obj1) //来获得这个String类型的age值
(8) 反射的其他扩展常用方法
//一些常用判断,为真返回true,否则false
Class c5 =Class.forName("com.chendsir.exercisejava.Person");
boolean isPrimitive = c5.isPrimitive(); //是否是基本类型,
boolean isArray = c5.isArray();// 是否是集合类
boolean isAnnotation = c5.isAnnotation();// 是否是注解类
boolean isInterface = c5.isInterface(); // 是否是接口类
boolean isEnum = c5.isEnum(); // 是否是枚举类
boolean isAnonymousClass = c5.isAnonymousClass();//isAnonymousClass; 判断是否是匿名内部类
boolean isAnonationPresent = c5.isAnnotationPresent(Deprecated.class); //判断是否被Deprecated这个注解类修饰
String className = c5.getName(); // 这个getName包含了包名的路径
Package apackage = c5.getPackage(); // 获取包的信息
String classSimpleName = c5.getSimpleName(); //获取class类名
int modfiers = c5.getModifiers();//获取class访问权限
Type genericSuperclass = c5.getGenericSuperclass();// 获得class对象的直接超类type
Type[] interfaceTypes = c5.getGenericInterfaces(); // 获取class 对象的所有接口的type集合
Class<?> [] classes = c5.getDeclaredClasses();//获取内部类
Class<?> classes1 = c5.getDeclaringClass();//获取外部类
三、java反射的一些应用场景
1: Retofit这个框架,注解很强大,也是利用反射和注解结合起来的。
2: EventBus 单纯的反射机制应用框架
3: 反编译
4:JDBC的利用反射加载数据库驱动
5: Spring框架用反射加载xml配置信息
6: 动态生成类框架Gson
7: 动态代理模式
8:某些带有@hide注解的变量或者类,我们无法直接获取,通过反射就可以拿到
四、总结
通过本篇文章学会了如何通过反射,获取一个类,获取该类的属性、方法,构造函数、注解,以及如何调用这些方法,通过反射的方式生成一个对象,赋值,修改某个属性的值。本文讲的都是反射的一些基础,希望能对正在学习java反射的童鞋们有一个很好的引导。深入学习的话得建议去了解一下反射的动态代理模式,通过反射来获取泛型、以及泛型擦除的一些原理。