J2SE(15)之反射(Reflection)

1、概述

1.1、什么是动态语言?

程序运行时,可以改变程序的结构或变量类型,典型的动态语言有:Python,ruby和JavaScript等,比如如下的js代码:

function test(){
    var s = "var a=3; var b=5;alert(a+b)";
    eval(s);
}

C、C++和Java都不是动态语言,但是Java有一定的动态性,我们可以利用Java的反射机制,字节码操作获得类似动态语言的特性。Java的动态性让编程的时候更加灵活。

1.2 反射机制

反射机制:指的是可以在运行时加载、探知和使用 编译期间完全未知的类;

程序在运行状态中,可以动态的加载一个只知道名称的类,对于任意一个已加载的类,都能够知道这个类的所有属性和方法;对于任意一个对象,都可以调用它的任意一个方法和属性。

加载完类之后,在堆内存中就存在一个Class类型的对象(一个类只有一个Class对象),这个对象包含该类的完整的类的结构信息。我们可以通过这个对象看到类的结构,就像一面镜子一样,透过这个镜子看到类的结构,所以称之为反射。

2、反射

2.1 先创建一个类

public class User {
	private int id;
	private int age;
	private String name;
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public User(int id, int age, String name) {
		super();
		this.id = id;
		this.age = age;
		this.name = name;
	}
	public User() {
	}
}

该类中定义了三个私有属性并提供了对应的set和get方法,并且提供了满参和无参构造(记住一定要提供)。

2.2 Class对象的获取方式

Class对象,也称之为字节码对象,

1、被加载的每一个类都会生成一个Class对象;

2、枚举会被看成类,注解会看成接口;

3、同等维数的相同数据类型的数组会共享一个Class对象,比如:int[] a1 = new int[10]和int[] a2 = new int[30]两个数组,他们的:a1.getClass() 和 a2.getClass() 得到的是同一个对象。

4、基本数据类型也会对应一个Class对象。

Class对象的三种获取方式:

public class ReflectionTest {

	public static void main(String[] args) throws ClassNotFoundException {
		//类的Class对象 的三种获取方式
		//1 通过类名类获取
		Class clazz1 = User.class;
		
		//2 通过对象的getClass()方法获取
		User user = new User();
		Class clazz2 = user.getClass();
		
		//3 通过类的包名+类名获取
		Class clazz3 = Class.forName("com.chinacreator.dzzz.dzzzkinterface.User");//会抛一个异常:ClassNotFoundException
		System.out.println(clazz1==clazz2 && clazz2==clazz3);//结果是:true
	}
}

一个类只有一个Class对象,所以我们通过以上三种方式获取的是同一个Class对象。

2.3 反射机制的常见作用

1、动态加载类,动态获取类的信息(属性、方法和构造器);

2、动态构造对象;

3、动态调用类和对象的任意方法、构造器和属性;

4、获取泛型信息;

5、处理注解。

3、反射操作:获取类、构造器、方法和属性

注意:

        1、通过Class对象的newInstance() 创建对象时,调用的是目标类的无参构造,所以我们要养成一个好的习惯:每个类都要提供无参构造。(很多开源框架都使用了反射技术,都会用newInstance() 创建对象,如果我们没有提供 无参构造,那么就会报错);

        2、使用Class对象去操作私有的方法(构造器)和属性的时候,必须先调用该Field(或Method、Constructor)的setAccessible(true),这个意思是忽略安全检查,可以访问,同时它还能提高效率。

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;


public class ReflectionTest {

	@SuppressWarnings("all")
	public static void main(String[] args) throws  Exception {
		Class clazz = Class.forName("com.chinacreator.dzzz.dzzzkinterface.User");//会抛一个异常:ClassNotFoundException
		//1 获取类信息
		String name = clazz.getName();//获取包名+类名
		String simpleName = clazz.getSimpleName();//获取类名
		System.out.println("name:"+name+"========simpleName:"+simpleName);
		
		//根据Class对象创建对象
		User user = (User)clazz.newInstance();//这个方法调用的是无参构造创建对象,很多框架都是这么用的,所以切记要保留无参构造
		
		System.out.println("------------------------------");
		
		//2 属性操作
		//2.1 获取属性信息
		//Field[] fields = clazz.getFields();//获取所有的public的属性的数组
		//Field field = clazz.getField("id");//根据名称获取 public的属性,如果找不到会抛异常
		Field[] declaredFields = clazz.getDeclaredFields();//获取所有的属性,包括私有属性
		Field declaredField = clazz.getDeclaredField("id");//根据属性名称获取属性
		for (Field f : declaredFields) {
			System.out.println(f.getName());
		}
		//2.2 属性操作:赋值等
		declaredField.setAccessible(true);//设置为true,忽略检查,也可以提升效率
		declaredField.set(user, 1001);
		System.out.println(user.getId());
		
		System.out.println("===============================================");
		//3 方法操作
		//3.1 获取方法信息
		//Method[] methods = clazz.getMethods();//获取所有的public修饰的方法
		//获取指定的方法:第一个参数:方法名,第二个参数:可变参数,是对应的方法的参数类型的class文件
		//Method method1 = clazz.getMethod("setId", int.class);//获取指定的public修饰的方法,找不到会抛异常
		//Method method2 = clazz.getMethod("getId", null);//获取指定的public修饰的方法
		
		//获取所有的方法,包括私有方法
		Method[] declaredMethods = clazz.getDeclaredMethods();
		for (Method method : declaredMethods) {
			System.out.println(method.getName());
		}
		Method setId = clazz.getDeclaredMethod("setId", int.class);
		//运行方法
		setId.setAccessible(true);//必须设置为true才能操作private方法
		setId.invoke(user, 2002);//运行方法,进行赋值
		
		Method getId = clazz.getDeclaredMethod("getId");
		getId.setAccessible(true);
		int id = (int)getId.invoke(user);
		System.out.println(id);
		
		System.out.println("--------------------------------------------------------");
		
		//4 构造方法操作
		//4.1 获取public构造信息
		//Constructor[] constructors = clazz.getConstructors();//获取所有的public修饰的构造器的数组
		//Constructor constructor = clazz.getConstructor(int.class,int.class,String.class);//根据参数类型获取构造方法
		
		//4.2 获取包含private修饰的构造
		Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
		for (Constructor constructor : declaredConstructors) {
			System.out.println(constructor);
		}
		Constructor declaredConstructor = clazz.getDeclaredConstructor(int.class,int.class,String.class);
		//根据构造创建对象
		User userNew = (User)declaredConstructor.newInstance(3003,23,"酱油哥");
		System.out.println("userNew:"+userNew.getId()+"--"+userNew.getAge()+"--"+userNew.getName());
	}
}

4、反射机制的性能、反射操作泛型和反射操作注解

4.1 反射机制的性能问题

          setAccessible():我们在使用反射技术操作private修饰的方法和属性的时候,需要使用setAccessible()方法。该方法接收一个boolean参数:true则指示反射的对象在使用时取消Java语言的访问检查;false则指示应该进行Java语言的访问检查。一般设置为true,而且为true的时候可以提高反射的运行速度。

import java.lang.reflect.Method;

import junit.framework.Test;

public class ReflectionTest2 {

	//测试反射机制对性能的影响
	public static void main(String[] args) throws Exception {
		test1();
		test2();
		test3();
	}
	
	//方法1:不使用反射操作方法20亿次
	public static void test1(){
		User user = new User();
		long startTime = System.currentTimeMillis();
		
		for(int i=0; i<2000000000L; i++){
			user.getName();
		}
		long endTime = System.currentTimeMillis();
		System.out.println("不使用反射操作1000000000次耗费时间:"+(endTime-startTime)+" 毫秒");
	}
	
	//方法2:使用反射,不跳过安全检查 操作方法20亿次
	public static void test2() throws Exception{
		User user = new User();
		Method method = user.getClass().getDeclaredMethod("getName", null);
		long startTime = System.currentTimeMillis();
		for(int i=0; i<2000000000L; i++){
			method.invoke(user, null);
		}
		long endTime = System.currentTimeMillis();
		System.out.println("使用反射,不跳过安全检查,操作1000000000次耗费时间:"+(endTime-startTime)+" 毫秒");
	}
	
	//方法3:使用反射,跳过安全检查 操作方法20亿次
	public static void test3() throws Exception{
		User user = new User();
		Method method = user.getClass().getDeclaredMethod("getName", null);
		method.setAccessible(true);//跳过安全检查
		
		long startTime = System.currentTimeMillis();
		for(int i=0; i<2000000000L; i++){
			method.invoke(user, null);
		}
		long endTime = System.currentTimeMillis();
		System.out.println("使用反射,跳过安全检查,操作1000000000次耗费时间:"+(endTime-startTime)+" 毫秒");
	}
}

相对来说:使用反射会降低速度,当取消安全检查后,会提升部分速度。

4.2 反射操作泛型

Java中的泛型仅仅是给编译器javac使用的,泛型是为了 确保数据的安全性和强制类型转换的麻烦,一旦编译完成,所有的和泛型有关的类型全部擦除。而我们的反射是运行时期操作的,所以反射是获取不到泛型的。

为了通过反射操作这些泛型以满足开发的需要,Java就新增了ParameterizedType、GenericArrayType、TypeVariable和WildcardType 四种类型来代表 不能被归一到Class类中的类型但是又和原始类型齐名的类型。

类型 说明
ParameterizedType 表示一种参数化的类型,比如Collection<String>
GenericArrayType 表示一种元素类型是参数化类型或类型变量的数组类型
TypeVariable 表示各种类型变量的公共父接口
WildcardType 代表一种通配符类型表达式,比如:? ,  ? extends Number , ? super Integer 。wildcard单词就是通配符的意思。

接下来是ParameterizedType的使用示例:

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;


public class ReflectionTest2 {

	public void test1(Map<String, User> map, List<User> list){
		System.out.println("这是参数有泛型的test1方法");
	}
	
	public Map<Integer, User> test2(){
		System.out.println("这是返回值有泛型的test2方法");
		return null;
	}
	
	//测试反射机制对性能的影响
	public static void main(String[] args) throws Exception {
		try{
			//获取指定方法的参数的泛型信息
			Method method = ReflectionTest2.class.getMethod("test1", Map.class,List.class);
			Type[] types = method.getGenericParameterTypes();//获取参数的所有类型
			//遍历获取每个参数类型的泛型信息
			for(Type type : types){
				System.out.println("参数类型:"+type);
				if(type instanceof ParameterizedType){
					ParameterizedType pType = (ParameterizedType)type;//强制转型
					Type[] typeArguments = pType.getActualTypeArguments();
					for (Type type2 : typeArguments) {
						System.out.println(type+"参数的泛型类型:"+type2);
					}
				}
			}
			System.out.println("-----------------------------------------------------------");
			//获取指定方法的返回值的泛型信息
			Method method2 = ReflectionTest2.class.getMethod("test2", null);
			Type returnType = method2.getGenericReturnType();
			if(returnType instanceof ParameterizedType){
				ParameterizedType rType = (ParameterizedType)returnType;//强制转型
				Type[] typeArguments = rType.getActualTypeArguments();
				for (Type type : typeArguments) {
					System.out.println(returnType+"返回值的参数类型:"+type);
				}
			}
			
		}catch(Exception e){
			e.printStackTrace();
		}
	}
}

控制台打印:

参数类型:java.util.Map<java.lang.String, com.chinacreator.dzzz.dzzzkinterface.User>
java.util.Map<java.lang.String, com.chinacreator.dzzz.dzzzkinterface.User>参数的泛型类型:class java.lang.String
java.util.Map<java.lang.String, com.chinacreator.dzzz.dzzzkinterface.User>参数的泛型类型:class com.chinacreator.dzzz.dzzzkinterface.User
参数类型:java.util.List<com.chinacreator.dzzz.dzzzkinterface.User>
java.util.List<com.chinacreator.dzzz.dzzzkinterface.User>参数的泛型类型:class com.chinacreator.dzzz.dzzzkinterface.User
-----------------------------------------------------------
java.util.Map<java.lang.Integer, com.chinacreator.dzzz.dzzzkinterface.User>返回值的参数类型:class java.lang.Integer
java.util.Map<java.lang.Integer, com.chinacreator.dzzz.dzzzkinterface.User>返回值的参数类型:class com.chinacreator.dzzz.dzzzkinterface.User

4.3 反射操作注解

4.3.1 定义3个注解

自定义3个注解,分别用到类、方法和属性上:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)//只能用在类上
@Retention(RetentionPolicy.RUNTIME)//运行时有效
public @interface MyTable {
	String value() default "";
	String desc() default "";
}
@Target(ElementType.METHOD)//只能用在属性上
@Retention(RetentionPolicy.RUNTIME)
public @interface MyMethod {

	String value();
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)//只能用在属性上
@Retention(RetentionPolicy.RUNTIME)
public @interface MyField {
	
	String value();
	String type() default "varchar";
	int length() default 255;
}

4.3.2 定义实体类,加上自定义注解

@MyTable(value="t_person",desc="人员表")
public class Person {
	
	@MyField(value="t_id",type="varchar",length=50)
	private String id;
	@MyField(value="t_name",type="varchar",length=200)
	private String name;
	@MyField(value="t_age",type="int",length=3)
	private int age;
	
	public Person() {
		
	}
	
	public Person(String id, String name, int age) {
		super();
		this.id = id;
		this.name = name;
		this.age = age;
	}

	@MyMethod("获取用户id的方法")
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
}

4.3.3 使用反射操作注解

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class ReflectionAnnotationTest {

	@SuppressWarnings("all")
	public static void main(String[] args) throws Exception {
		/**
		 * 使用反射操作注解的步骤:
		 * 		1 获取类的Class对象
		 * 		2 通过Class对象获取对应的构造,方法和属性
		 * 		3 通过方法和属性的对象获取其对应的注解,根据主键获取主键的参数值
		 */
		Class clazz = Class.forName("com.chinacreator.dzzz.dzzzkinterface.Person");
		// 1 获取类上面注解
		Annotation[] annotations = clazz.getAnnotations();//获取类上的所有注解(类上可以指定多个注解)
		MyTable myTable = (MyTable)clazz.getAnnotation(MyTable.class);//获取类上指定名称的注解对象
		System.out.println("注解的value值:"+myTable.value()+"  注解的desc值:"+myTable.desc());
		
		// 2 获取方法上的注解
		Method method = clazz.getMethod("getId", null);
		MyMethod myMethod = method.getAnnotation(MyMethod.class);
		System.out.println("方法getId的注解内容是:"+myMethod.value());
		
		System.out.println("---------------------------------------------");
		
		// 3 获取属性上的注解
		Field[] fields = clazz.getDeclaredFields();
		for (Field field : fields) {
			MyField myField = field.getAnnotation(MyField.class);
			System.out.println("注解 "+myField+" 的描述:"+myField.value() +"  "+myField.type()+"  "+myField.length());
		}
	}
}
发布了82 篇原创文章 · 获赞 15 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/zengdongwen/article/details/103827895