我们为什么需要反射?
Java代码编写完成后,运行时是不变的。不像Python、JavaScript等动态语言,具有动态性。
Java虽然不是动态语言,但我们可以通过使用反射机制,让Java具有一定的动态性,让Java编程更加灵活。
Class类
说起Java的反射,首先需要了解的就是java.lang包下的Class类。想要使用反射机制,首先就得获得相应的Class对象。
Java中万物皆对象的思想,class类可以实例化出一个对象,那么class类它本事是不是一个对象呢?那必然是的,我们用Class类,来表示class/interface/enum/annotation/primitive type/void的本身,这个对象。
使用实例:
//方法一,通过Class.forName(),最常用
String path = "com.hhm.test.Student";//类的全路径,带上包名
Class clazz1 = Class.forName(path);//获取类的本身对象,Class
//方法二,通过.class
Class clazz2 = Student.class;
//方法三,通过getClass()
Class clazz3 = new Student().getClass();
通过反射获取信息
获取注解信息
可以结合我的这篇博客:《注解》来看。通过反射机制,可以读取注解中的信息,实例:
自定义注解及使用的代码:
//文件AnnField.java 自定义注解
@Target(value={ElementType.FIELD}) //该注解只能描述字段
@Retention(RetentionPolicy.RUNTIME) //运行时有效,可以被反射读取
public @interface AnnField {
String columnName(); //使用时,需要传入三个参数
String type();
int length();
}
//文件AnnTable.java 自定义注解
@Target(value={ElementType.TYPE}) //该注解能描述类、接口、枚举、注解
@Retention(RetentionPolicy.RUNTIME) //运行时有效,可以被反射读取
public @interface AnnTable {
String value(); //使用时,需要传入一个参数
}
//文件Student.java
@Getter
@Setter //使用lombok,注解生成get、set方法(参考文章:https://blog.csdn.net/HhmFighting/article/details/104945023)
@AnnTable(value="t_student") //使用自定义注解
public class Student {
@AnnField(columnName="id",type="int",length=10)
private int id;
@AnnField(columnName="sname",type="varchar",length=10)
private String studentName;
@AnnField(columnName="age",type="int",length=3)
private int age;
public Student(int id, String studentName, int age) {//有参构造器
super();
this.id = id;
this.studentName = studentName;
this.age = age;
}
public Student() {//无参构造器,有了有参构造器后不会自动添加无参构造器,需自行添加
}
@Override
public String toString() {
return "Student [id=" + id + ", studentName=" + studentName + ", age=" + age + "]";
}
}
通过反射读取注解信息的代码:
public class Test {
public static void main(String[] args) throws Exception { //异常均抛出处理
Class clazz = Class.forName("com.hhm.test.Student");//获取Class对象
//获得类的所有有效注解
Annotation[] classAnnotations = clazz.getAnnotations();//所有有效注解
System.out.print("获得类的所有有效注解:");
for (Annotation a : classAnnotations) {
System.out.println(a);
}
//获得类的指定的注解
AnnTable classAnnotation = (AnnTable) clazz.getAnnotation(AnnTable.class);//指定注解的类型,若不存在该注解,会报空指针异常
System.out.println("获得类的指定注解:"+classAnnotation.value()); //通过调用注解的参数方法名,获取对应的值
//通过Class获得单个字段(属性)
Field field = clazz.getDeclaredField("studentName");//通过Class获得字段信息
//获得单个字段(属性)的所有有效注解
System.out.print("获得单个字段的所有有效注解:");
Annotation[] fieldAnnotations = field.getAnnotations();
for (Annotation a : fieldAnnotations) {
System.out.println(a);
}
//获得单个字段(属性)的指定有效注解
AnnField fieldAnnotation = field.getAnnotation(AnnField.class);//指定注解的类型,若不存在该注解,会报空指针异常
System.out.println("获得单个字段的指定注解:"+fieldAnnotation.columnName()+"--"+fieldAnnotation.type()+"--"+fieldAnnotation.length());
//获得所有字段(属性)的指定有效注解
Field[] declaredFields = clazz.getDeclaredFields();
System.out.println("获得单个字段的指定有效注解:");
for (Field f : declaredFields) {
AnnField fAnnotation = f.getAnnotation(AnnField.class);
System.out.println(fAnnotation.columnName()+"--"+fAnnotation.type()+"--"+fAnnotation.length());
}
}
}
-------------------------------------------------------------
输出结果:
获得类的所有有效注解:@com.hhm.test.AnnTable(value=t_student)
获得类的指定注解:t_student
获得单个字段的所有有效注解:@com.hhm.test.AnnField(columnName=sname, type=varchar, length=10)
获得单个字段的指定注解:sname--varchar--10
获得单个字段的指定有效注解:
id--int--10
sname--varchar--10
age--int--3
获取类的信息
可以通过Class对象,获取类的类名、属性、方法、构造器的信息。代码实例:
public class Test {
public static void main(String[] args) throws Exception { //异常均抛出处理
Class clazz = Class.forName("com.hhm.test.Student");//获取Class对象
//获取类的名字
System.out.println("---------------类---------------");
System.out.println("获得类的全名:"+clazz.getName());//包名+类名
System.out.println("获得类的简名:"+clazz.getSimpleName()); //类名
//获取属性信息
System.out.println("---------------属性---------------");
// Field[] fields = clazz.getFields(); //只能获得public的field
Field[] fields = clazz.getDeclaredFields();//获得所有的field
Field f = clazz.getDeclaredField("studentName");//获取指定属性
System.out.println("获取到的属性共有"+fields.length+"个");
for(Field temp:fields){
System.out.println("属性:"+temp);
}
//获取方法信息
System.out.println("---------------方法---------------");
Method[] methods = clazz.getDeclaredMethods();
//根据方法名,获取指定方法
//如果方法有参,则必须传递参数类型对应的class对象,否则出错,找不到
// Method method1 = clazz.getDeclaredMethod("getStudentName");
// Method method2 = clazz.getDeclaredMethod("setStudentName", String.class);
for(Method m:methods){
System.out.println("方法:"+m);
}
//获得构造器信息
System.out.println("---------------构造器---------------");
Constructor[] constructors = clazz.getDeclaredConstructors();
Constructor c = clazz.getDeclaredConstructor(int.class,String.class,int.class);
System.out.println("获得构造器:"+c);
for(Constructor temp:constructors){
System.out.println("构造器:"+temp);
}
}
}
---------------------------------------------------------------------
输出结果为:
---------------类---------------
获得类的全名:com.hhm.test.Student
获得类的简名:Student
---------------属性---------------
获取到的属性共有3个
属性:private int com.hhm.test.Student.id
属性:private java.lang.String com.hhm.test.Student.studentName
属性:private int com.hhm.test.Student.age
---------------方法---------------
方法:public int com.hhm.test.Student.getId()
方法:public int com.hhm.test.Student.getAge()
方法:public void com.hhm.test.Student.setId(int)
方法:public void com.hhm.test.Student.setAge(int)
方法:public java.lang.String com.hhm.test.Student.getStudentName()
方法:public void com.hhm.test.Student.setStudentName(java.lang.String)
---------------构造器---------------
获得构造器:public com.hhm.test.Student(int,java.lang.String,int)
构造器:public com.hhm.test.Student(int,java.lang.String,int)
构造器:public com.hhm.test.Student()
获取泛型信息
可以通过Class对象,还可以获得泛型的信息:
public class Test {
// 泛型方法,返回值和输入参数,均带有泛型
public Map<Integer, Student> test01(Map<String, Student> map, List<Student> list) {
return null;
}
public static void main(String[] args) throws Exception { // 异常均抛出处理
Class clazz = Class.forName("com.hhm.test.Test");// 获取Class对象
Method m = clazz.getMethod("test01", Map.class, List.class);// 获得指定方法
// 获得输入参数的泛型
System.out.println("---------------输入参数的泛型---------------");
Type[] t = m.getGenericParameterTypes();// ParameterTypes,输入参数
for (Type paramType : t) {
System.out.println(paramType);
if (paramType instanceof ParameterizedType) {
Type[] genericTypes = ((ParameterizedType) paramType).getActualTypeArguments();
for (Type genericType : genericTypes) {
System.out.println("输入参数泛型类型:" + genericType);
}
}
}
// 获得返回值的泛型
System.out.println("---------------返回值的泛型---------------");
Type returnType = m.getGenericReturnType();// ReturnType,返回值
System.out.println(returnType);
if (returnType instanceof ParameterizedType) {
Type[] genericTypes = ((ParameterizedType) returnType).getActualTypeArguments();
for (Type genericType : genericTypes) {
System.out.println("返回值泛型类型:" + genericType);
}
}
}
}
------------------------------------------------------
输出结果为:
---------------输入参数的泛型---------------
java.util.Map<java.lang.String, com.hhm.test.Student>
输入参数泛型类型:class java.lang.String
输入参数泛型类型:class com.hhm.test.Student
java.util.List<com.hhm.test.Student>
输入参数泛型类型:class com.hhm.test.Student
---------------返回值的泛型---------------
java.util.Map<java.lang.Integer, com.hhm.test.Student>
返回值泛型类型:class java.lang.Integer
返回值泛型类型:class com.hhm.test.Student
通过反射操作类
我们已经可以通过反射获取类的相关信息了,那么能不能在此基础上创建对象,操作属性呢?那当然是可以的,一切尽在代码中,来个实例:
public class Test {
public static void main(String[] args) throws Exception { //异常均抛出处理
Class clazz = Class.forName("com.hhm.test.Student");//获取Class对象
//通过反射调用构造方法,构造对象
System.out.println("---------------创建对象---------------");
Student stu1 = (Student) clazz.newInstance(); //其实是调用了无参构造方法
System.out.println(stu1);
//获取有参构造器
Constructor<Student> consStu = clazz.getDeclaredConstructor(int.class,String.class,int.class);
Student stu2 = consStu.newInstance(1001,"小猪",18);//使用有参构造器创建对象
System.out.println(stu2);
//通过反射调用方法
System.out.println("---------------调用方法---------------");
Method method1 = clazz.getDeclaredMethod("setId", int.class);
Method method2 = clazz.getDeclaredMethod("setStudentName", String.class);
Method method3 = clazz.getDeclaredMethod("setAge", int.class);
method1.invoke(stu1, 1002);
method2.invoke(stu1, "小虎");
method3.invoke(stu1, 20);
System.out.println(stu1);
//通过反射操作属性
System.out.println("---------------操作属性---------------");
Field f = clazz.getDeclaredField("age");
f.setAccessible(true); //设置这个属性不需要做安全检查了,可以直接访问
f.set(stu1, 18); //通过反射直接写属性
System.out.println(stu1);
System.out.println(f.get(stu1));//通过反射直接读属性
}
}
------------------------------------------------------------
输出结果:
---------------创建对象---------------
Student [id=0, studentName=null, age=0]
Student [id=1001, studentName=小猪, age=18]
---------------调用方法---------------
Student [id=1002, studentName=小虎, age=20]
---------------操作属性---------------
Student [id=1002, studentName=小虎, age=18]
18
反射的效率问题
使用反射,感觉特别BUG特别爽,从字节码开始操作,连private都可以忽略掉。但仔细想想,用反射的方式创建对象并使用方法,是不是有点走弯路了呢?所有,使用反射的方式,效率会降低很多,在JDK1.8的环境下,我测试通过反射调用方法花费时间大约正常调用的3倍。
我们可以通过设置setAccessible(true);跳过安全性检查,来提高效率。我通过测试,设置跳过安全检查相比于未设置,执行效率大约可以提高40%
虽然反射降低了程序的运行的效率,但可以提高程序员的开发效率。在目前硬件设备性能充沛,因反射而提高的硬件成本远低于人力成本的情况下,使用反射总的来说是利远远大于弊的,所以反射机制被众多框架使用。