Java系列之反射

最近知识比较零碎,不适合整理成文,来一篇以前的关于反射的学习笔记,主要内容如下:

  1. 反射机制
  2. 反射获取类的信息
  3. 反射操作类的信息
  4. 反射获取泛型
  5. 反射获取注解信息

反射机制

Java 的反射机制是指在运行状态下,对于任意一个类,都能够知道这个类的所有属性和方法,反射是一种可在代码运行时动态获取类的信息的一种机制,可通过反射获取在编译期不可能获取到的类的信息,当一个任意类被类加载器(ClassLoader)首次加载之后会自动生成一个该类对应的 Class 对象,这个 Class 对象保存了对应类的所有信息。这种当一个任意类被类加载器加载之后,动态获取 Class 对象的信息以及动态操作 Class 对象的属性和方法的功能称之为 Java 的反射机制。

Java 反射机制的关键是获取某个任意类的 Class 对象,下面的内容就是如何通过这个 Class 对象获取和动态操作类的相关信息,为了便于后文中对反射的使用,这里先创建一个 User 类供下文中使用,具体如下:

package com.manu.reflection.bean;

/**
 * 反射测试类
 */
public class User {
    
    
	
	private int id;
	private String name;
	private String password;
	
	public User() {
    
    
		super();
	}
	public User(int id, String name, String password) {
    
    
		super();
		this.id = id;
		this.name = name;
		this.password = password;
	}
	public int getId() {
    
    
		return id;
	}
	public void setId(int id) {
    
    
		this.id = id;
	}
	public String getName() {
    
    
		return name;
	}
	public void setName(String name) {
    
    
		this.name = name;
	}
	public String getPassword() {
    
    
		return password;
	}
	public void setPassword(String password) {
    
    
		this.password = password;
	}
	@Override
	public String toString() {
    
    
		return "User [id=" + id + ", name=" + name + ", password=" + password + "]";
	}
}

反射获取类的信息

这里总结一下如何获取类的基本信息,如类的构造方法、属性、方法等,可通过某个类对应的 Class 对象对应的 getter 方法获取某个类的名称、构造方法、属性、方法等,下面以获取某个类的构造方法为例说明获取方式不同:

  • clazz.getConstructors():表示获取某个类中修饰符为 public 的所有构造方法,如 getFields()、getMethods() 还包括从父类继承来的 public 修饰的属性和方法。
  • clazz.getDeclaredConstructors():表示获取某个类中所有声明的构造方法,只限定于本类中。

此外,还可获取指定的构造方法、属性、方法等,下面的代码主要以获取已定义(Declared)的构造方法、属性、方法为例,参考如下:

/**
 * 反射获取类的信息
 */
private static void getReflectClassInfo() {
    
    
	try {
    
    
		//获取某个类的Class对象
		String name = "com.manu.reflection.bean.User";
		Class clazz = Class.forName(name);
		
		//反射获取类的名称
		System.out.println("----------反射获取类的名称----------");
		String n1 = clazz.getName();//完整路径:包名+类名 (com.manu.reflection.bean.User)
		String n2 = clazz.getSimpleName();//类名(User)
		System.out.println("获取类的名称n1:"+n1);
		System.out.println("获取类的名称n2:"+n2);
		
		//反射获取类的构造方法
		System.out.println("----------反射获取类的构造方法----------");
		Constructor<User> c1 = clazz.getDeclaredConstructor(null);
		System.out.println("获取无参构造方法:"+c1);
		Constructor<User> c2 = clazz.getDeclaredConstructor(int.class,String.class,String.class);
		System.out.println("获取有参构造方法:"+c2);
		Constructor[] constructors = clazz.getDeclaredConstructors();
		for(Constructor c: constructors) {
    
    
			System.out.println("获取所有的构造方法:"+c);
		}
		
		//反射获取类的属性
		System.out.println("----------反射获取类的属性----------");
		Field f1 = clazz.getDeclaredField("name");
		System.out.println("获取名称为name的属性:"+f1);
		Field[] fields = clazz.getDeclaredFields();
		for(Field f : fields) {
    
    
			System.out.println("获取所有的属性:"+f);
		}
		
		//反射获取类的方法
		System.out.println("----------反射获取类的方法----------");
		Method m1 = clazz.getDeclaredMethod("getName", null);//获取无参方法
		Method m2 = clazz.getDeclaredMethod("setName", String.class);//获取有参方法
		System.out.println("获取方法名为getName的方法m1:"+m1);
		System.out.println("获取方法名为setName的方法m2:"+m2);
		Method[] mathods = clazz.getDeclaredMethods();
		for(Method m: mathods) {
    
    
			System.out.println("获取所有方法:"+m);
		}
	
	} catch (Exception e) {
    
    
		e.printStackTrace();
	}
}

通过反射获取某个类的相关信息主要如上,来上述代码的执行结果如下:

----------反射获取类的名称----------
获取类的名称n1:com.manu.reflection.bean.User
获取类的名称n2:User
----------反射获取类的构造方法----------
获取无参构造方法:public com.manu.reflection.bean.User()
获取有参构造方法:public com.manu.reflection.bean.User(int,java.lang.String,java.lang.String)
获取所有的构造方法:public com.manu.reflection.bean.User()
获取所有的构造方法:public com.manu.reflection.bean.User(int,java.lang.String,java.lang.String)
----------反射获取类的属性----------
获取名称为name的属性:private java.lang.String com.manu.reflection.bean.User.name
获取所有的属性:private int com.manu.reflection.bean.User.id
获取所有的属性:private java.lang.String com.manu.reflection.bean.User.name
获取所有的属性:private java.lang.String com.manu.reflection.bean.User.password
----------反射获取类的方法----------
获取方法名为getName的方法m1:public java.lang.String com.manu.reflection.bean.User.getName()
获取方法名为setName的方法m2:public void com.manu.reflection.bean.User.setName(java.lang.String)
获取所有方法:public java.lang.String com.manu.reflection.bean.User.toString()
获取所有方法:public java.lang.String com.manu.reflection.bean.User.getName()
获取所有方法:public int com.manu.reflection.bean.User.getId()
获取所有方法:public void com.manu.reflection.bean.User.setName(java.lang.String)
获取所有方法:public java.lang.String com.manu.reflection.bean.User.getPassword()
获取所有方法:public void com.manu.reflection.bean.User.setId(int)
获取所有方法:public void com.manu.reflection.bean.User.setPassword(java.lang.String)

反射操作类的信息

通过 Java 的反射机制可以获取的某个类的构造方法、属性以及方法,然后就可以对该类进行相关操作了,下面是将通过 Java 的反射机制构建该类的对象、操作该类对象的属性以及调用该类对象的方法,具体参考如下:

/**
 * 反射操作类的信息
 */
private static void setReflectClassInfo() {
    
    
	try {
    
    
		//获取某个类的Class对象
		String name = "com.manu.reflection.bean.User";
		Class clazz = Class.forName(name);
		
		//反射操作类的构造方法
		System.out.println("----------反射操作类的构造方法----------");
		Constructor<User> c1 = clazz.getDeclaredConstructor(null);//获取无参构造方法
		Constructor<User> c2 = clazz.getDeclaredConstructor(int.class,String.class,String.class);//获取带参构造方法
		User u1 = c1.newInstance();
		User u2 = c2.newInstance(1000,"jzman-blog","111111");
		System.out.println("u1:"+u1);
		System.out.println("u2:"+u2);
		
		//反射操作类的属性
		System.out.println("----------反射操作类的属性----------");
		User u3 = c1.newInstance();
		Field field = clazz.getDeclaredField("name");
		field.setAccessible(true);//设置该属性不需要安全检查,可直接放访问
		field.set(u3, "jzman");//反射设置User对象的name属性值
		System.out.println("u3:"+u3);
		System.out.println("获取User对象u3的name属性值:"+field.get(u3));
		
		//反射操作类的方法
		System.out.println("----------反射操作类的方法----------");
		User u4 = c1.newInstance();
		Method method = clazz.getDeclaredMethod("setPassword", String.class);
		method.invoke(u4, "222222");//设置User对象u4的password属性,等同于u4.setPassword("222222);
		System.out.println("u4:"+u4);
		
	} catch (Exception e) {
    
    
		e.printStackTrace();
	}
}

上述代码的执行结果如下:

----------反射操作类的构造方法----------
u1:User [id=0, name=null, password=null]
u2:User [id=1000, name=jzman-blog, password=111111]
----------反射操作类的属性----------
u3:User [id=0, name=jzman, password=null]
获取User对象u3的name属性值:jzman
----------反射操作类的方法----------
u4:User [id=0, name=null, password=222222]

实际开发中肯定会遇到某个组件中需要某个属性,但是该属性又是私有的,这时候就可以使用 Java 的反射机制了。

反射操作泛型

Java 采用泛型擦除的机制,Java 中的泛型仅仅是给编译器 javac 使用的,这样可确保数据的安全性的免去强制类型转换的麻烦,当编译完成之后,所有和泛型相关的类型将会被全部擦除,为了能够使用反射操作这些类型,新增了四种类型 GenericArrayType、ParameterizedType、TypeVariable 和 WildcardType 来表示不能被归一到 Class 类中的类型但又是和原始类型齐名的类型,也就是说正常的能够获取对应的 Class 对象的 Type 还是 Class 对象,如基本数据类型。

反射操作泛型就要涉及到 Java 中的 Type, 在 Java 中 Type 表示所有类型的公共接口,这些类型包括原始类型、参数化类型、数组类型、类型变量和基本类型,其声明如下:

/**
 * Type 是 Java 语言中所有类型的公共接口
 * 这些类型包括原始类型、参数化类型、数组类型、类型变量和基本类型
 */
public interface Type {
    
    
    default String getTypeName() {
    
    
        return toString();
    }
}

Type 有四个直接子接口,具体含义如下:

GenericArrayType:表示泛型数组类型,如 ArraryList<String>[] listArrary
ParameterizedType:表示参数化类型(泛型),如 ArrayList<String> list
TypeVariable<T>:表示类型变量,如T
WildcardType:表示一个通配符类型,如 ?? extends Number? super Integer

下面以 GenericArrayType 和 ParameterizedType 为例来说明如何使用反射来操作泛型类型,具体如下:

//用来测试获取泛型的属性
private String[] array;
private List<String>[] listArray;
//用来测试获取泛型的方法
private String testGenericType(Map<String,Integer> map, String[] array, int name,User user) {
    
    
    System.out.println("testGenericType");
    return null;
}

//通过反射获取泛型
private static void refectGenericType() {
    
    
	try {
    
    		
		System.out.println("---------GenericArrayType---------");
		
		//获取泛型数组(GenericArrayType)
		Field field = ReflectTest02.class.getDeclaredField("listArray");//获取属性listArray
		GenericArrayType type = (GenericArrayType) field.getGenericType();
		System.out.println("获取泛型数组:"+type);
		
		System.out.println("---------ParameterizedType---------");
		
		//获取参数化类型(泛型)(ParameterizedType)
		Method method = ReflectTest02.class.getDeclaredMethod("testGenericType", Map.class,String[].class,int.class,User.class);
		Type[] types = method.getGenericParameterTypes();//获得方法参数类型
		for(Type type1: types) {
    
    
			System.out.println("方法参数类型:"+type1);
			if(type1 instanceof ParameterizedType) {
    
    
				System.out.println("ParameterizedType:"+type1);
			}
		}
		
	} catch (Exception e) {
    
    
		e.printStackTrace();
	}
}

上述代码中方法 refectGenericType 的执行结果如下:

---------GenericArrayType---------
获取泛型数组:java.util.List<java.lang.String>[]
---------ParameterizedType---------
方法参数类型:java.util.Map<java.lang.String, java.lang.Integer>
ParameterizedType:java.util.Map<java.lang.String, java.lang.Integer>
方法参数类型:class [Ljava.lang.String;
方法参数类型:int
方法参数类型:class com.manu.reflection.bean.User

可参照代码查看对应的输出结果。

反射获取注解信息

通过反射还可以获取注解信息,记如果对注解比较陌生可以参考之前分享的一篇文章 Java 系列之注解,这里简单模仿数据库表字段与 Java 对象的属性是如何通过注解信息一一对应的,为什么我们使用一些数据库框架的时候,通过一些表注解、字段注解就能够创建表,这里就涉及到使用反射来获取注解信息来生成 SQL,进而生成对应的表。

创建两个注解分别作为表注解和字段注解,参考如下:

//表注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TableAnnotation {
    
    
	String value();
}

//字段注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FieldAnnotation {
    
    
	String column();
	int length();
	String type();
}

然后,创建一个类并使用该注解,参考如下:

/**
 * 反射读取注解信息测试Bean
 * @author jzman
 */

@TableAnnotation(value = "student_table")
public class Student {
    
    
	@FieldAnnotation(column = "uid", length = 20, type = "int")
	private int sId;
	@FieldAnnotation(column = "name", length = 10, type = "varchar")
	private String sName;
	@FieldAnnotation(column = "uiaged", length = 3, type = "varchar")
	private int sAge;
	
	//setter、getter方法
	//...
}

最后,获取注解信息,参考如下:

/**
 * 反射获取注解信息
 * @author jzman
 */
public class ReflectTest03 {
    
    
	public static void main(String[] args) {
    
    
		try {
    
    
			Class clazz = Class.forName("com.manu.reflection.bean.Student");
			
			//反射获取类的注解信息
			TableAnnotation tableAnnotation = (TableAnnotation) clazz.getAnnotation(TableAnnotation.class);
			System.out.println("反射获取类的注解信息:"+tableAnnotation);
			
			//反射获取属性的注解信息
			Field field = clazz.getDeclaredField("sName");
			FieldAnnotation fieldAnnotation = field.getAnnotation(FieldAnnotation.class);
			System.out.println("反射获取属性的注解信息:"+fieldAnnotation);
			
			//获取其他注解信息使用方式类似
			//...
		} catch (Exception e) {
    
    
			e.printStackTrace();
		}
	}
}

上述代码的执行结果如下:

反射获取类的注解信息:@com.manu.reflection.TableAnnotation(value=student_table)
反射获取属性的注解信息:@com.manu.reflection.FieldAnnotation(column=name, length=10, type=varchar)

显然,通过反射获取到了相应的注解信息,这些注解信息标注了该类对应数据库表的一些关键信息,然后就可以生成对应的 SQL 语句,这样就不难理解数据库表字段与Java对象属性的映射关系了。

使用反射获取私有属性或私有方法是必须设置 setAccessible 为 true 才能跳过 Java 安全检查,从而获取私有的属性、方法等,同时设置 setAccessible 为 true 在一定程度上可以提高反射的运行速度。

更多内容关注公众号躬行之

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/jzman/article/details/114857994
今日推荐