最近知识比较零碎,不适合整理成文,来一篇以前的关于反射的学习笔记,主要内容如下
- 反射机制
- 反射获取类的信息
- 反射操作类的信息
- 反射获取泛型
- 反射获取注解信息
反射机制#
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 在一定程度上可以提高反射的运行速度。