反射

我们为什么需要反射?

  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%

  虽然反射降低了程序的运行的效率,但可以提高程序员的开发效率。在目前硬件设备性能充沛,因反射而提高的硬件成本远低于人力成本的情况下,使用反射总的来说是利远远大于弊的,所以反射机制被众多框架使用。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值