Java—反射
1.反射概述
- JAVA反射机制实在运行状态中,对于任意一个类,都能知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及调用对象的方法的功能称为java的反射机制
- 要想解剖一个类,必须要先获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法,所以要先获取到每一个字节码文件所对应的Class类型的对象
那么,反射就是把java类中的各种成分映射成一个个的java对象,如下图所示
再说到Class类,Class类的实例表示正在运行的java应用程序中的类和接口。也就是jvm中有N多的实例每个类都有该Class对象(包括基本数据类型)
Class没有公共构造方法。Class对象是在加载类时由java虚拟机以及通过调用类加载器中的defineClass方法自动构造的,也就是不需要我们去创建,jvm已经创建好了
2.反射的使用
这里我们先写一个User类
public class User {
private int id;
private int age;
private String name;
public User() {
}
public User(int id, int age, String name) {
this.id = id;
this.age = age;
this.name = name;
}
public int getId() {
return id;
}
public int getAge() {
return age;
}
public String getName() {
return name;
}
public void setId(int id) {
this.id = id;
}
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
}
1> 获取Class对象的三种方式
- Object –> getClass();
- 任何数据类型(包括基本数据类型)都有一个 “静态的”class属性
- 通过Class类的静态方法:forName(String className)(常用)
注意:运行期间,一个类只有一个Class对象产生。
- 2> 获取User的类名,属性,方法,构造器
public class Demo2 {
public static void main(String[] args) {
String path = "anno.reflection.User";
try {
Class<?> clazz = Class.forName(path);
//类的名字
System.out.println(clazz.getName()); //获得包名+类名
System.out.println(clazz.getSimpleName()); //获得类名:User
//获得属性信息
// Field[] fields = clazz.getFields(); //只能返回public属性
Field[] fields = clazz.getDeclaredFields();//获得所有field
try {
Field f = clazz.getDeclaredField("name");
System.out.println(fields.length);
for (Field temp:fields){
System.out.println("属性" + temp);
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
//获得方法信息
Method[] method = clazz.getDeclaredMethods();
Method m = clazz.getDeclaredMethod("getName",null);
Method m2 = clazz.getDeclaredMethod("setName", String.class);
for (Method temp:method){
System.out.println("方法" + temp);
}
//获得构造器信息
Constructor[] constructors = clazz.getDeclaredConstructors();
System.out.println();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
首先,path指定要进行反射的类的路径,然后通过Class.forName()获取到Class对象,该对象调用getName()返回该类所在的包名+类名,getSimpleName()将光返回一个类名。
- 通过getFields()可获得所有属性信息,但只能获得声明为public的属性,要想获得所有属性就要调用getDeclaredFields()方法,若要获得某个单独的属性,就要调用getDeclaredField(String name)方法,name为该属性的名字
- 通过getDeclaredMethods()方法可以返回该类的所有方法,但若要获得某个特定的方法,getDeclaredMethod()参数中除了方法名,还有该方法的参数的class对象
- 构造器信息可以通过getConstructors方法获得。
- 3> 创建类的实例,调用类的属性与方法
public class Demo3 {
public static void main(String[] args) throws IllegalAccessException, InstantiationException, InvocationTargetException {
String path = "anno.reflection.User";
try {
Class<?> clazz = Class.forName(path);
//动态调用构造方法,构造对象
User u = (User) clazz.newInstance();//其实是调用了user的无参构造器
//javabean必须有无参构造器
System.out.println(u);
Constructor<User> c = (Constructor<User>) clazz.getDeclaredConstructor(int.class,int.class,String.class);
User u2 = c.newInstance(1001,18,"winter");
System.out.println(u2.getName());
//通过反射API调用普通方法
User u3 = (User) clazz.newInstance();
Method method = clazz.getDeclaredMethod("setName", String.class);
method.invoke(u3,"wtf"); //u3.setName("wtf");
System.out.println(u3.getName());
//通过反射API操作属性
User u4 = (User) clazz.newInstance();
Field f = clazz.getDeclaredField("name");
f.setAccessible(true); //这个属性不用做安全检查了
f.set(u4,"zhang"); //无法访问私有属性,通过反射直接写属性
System.out.println(u4.getName()); //通过反射直接读属性
System.out.println(f.get(u4));
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
}
在获取到User类的Class对象后,让该对象调用newInstance()方法即可返回一个User类的对象,但是,这个方法创建的对象是通过调用无参构造器创建的,所以User类中必须要有一个无参构造器,否则调用该方法就会报错。
- 接下来可以调用getDeclaredConstructor()方法获得对应的构造器,方法的参数为对应构造器参数的Class对象,获取之后就可以利用该对象进行构造新的User对象(注意强制类型转换)
- 同理,我们可以通过getDeclaredMethod()方法获得相对应的方法,该方法的第一个参数是该方法的方法名,之后的参数是要获取的方法的参数Class对象。在获取到方法后,该让哪个User对象调用该方法呢?这个时候该方法对象需要调用invoke()方法指定执行该方法的对象,并且指定传入参数(如上面的代码所示)
- 调用getDeclaredField()获得相应的属性之后,要想对某对象中的该属性进行修改,可以使用set()方法,但如果该属性为私有属性,就必须先使用setAccessible()方法来避开安全检查,这样就可以随意修改对象中的属性值了
- 4> 反射获取泛型信息
public class Demo4 {
public void test01(Map<String,User> map, List<User> list){
System.out.println("Demo04.test01()");
}
public Map<Integer,User> test02(){
System.out.println("Demo.test02()");
return null;
}
public static void main(String[] args) throws NoSuchMethodException {
//获得指定方法的参数泛型信息
Method m = Demo4.class.getMethod("test01",Map.class,List.class);
Type[] t = m.getGenericParameterTypes();//获得参数类型
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);
}
}
}
//获得指定方法返回值泛型信息
Method m2 = Demo4.class.getMethod("test02",null);
Type returnType = m2.getGenericReturnType();
System.out.println(returnType);
if (returnType instanceof ParameterizedType){
Type[] genericTypes = ((ParameterizedType) returnType).getActualTypeArguments();
for (Type genericType:genericTypes){
System.out.println("返回值,泛型类型" + genericType);
}
}
}
}
接下来是使用反射来获取泛型信息,事实上泛型是java编译器的内容,在编译时经过泛型擦除,jvm中不存在泛型。但在类的Class对象加载到内存中后,我们任然可以获取其中的泛型信息。上面的代码分别是获取参数中的泛型信息和方法返回值的泛型信息
参数中的泛型信息:在获取到相应的方法对象后,调用getGenericParameterTypes()方法获得参数类型,如果该参数对象 符合 instanceof ParameterizedType,即说明该参数是一个泛型参数,然后再调用getActualTypeArguments()方法来获得泛型类型(先将其强制转型为ParameterizedType类型)
方法返回值中的泛型信息:再获取到相应的方法对象后,调用getGenericReturnType()拿到返回值的类型,再重复之前的步骤判断其是否是带有泛型的类型,再获得泛型类型。
3.反射获得注解信息
首先我们先定义两个自定义注解
@Target(value = ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyField {
String columnName();
String type();
int length();
}
@Target(value = ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTable {
String value();
}
然后定义一个javabean类,并未其类和属性添加注解
@MyTable("tb_student")
public class MyStudent {
@MyField(columnName = "id",type = "int",length = 10)
private int id;
@MyField(columnName = "sname",type = "varchar" ,length = 10)
private String name;
@MyField(columnName = "age",type = "int",length = 3)
private int age;
public int getId() {
return id;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public void setId(int id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
}
然后,我们通过反射来获取其注解中的信息。其主要目的是为了获取注解中的信息,拼出DDL语句,连接数据库,然后用JDBC执行sql语句,在数据库中生成相应的表。
public class AnnoDemo {
public static void main(String[] args) {
try {
Class clazz = Class.forName("anno.MyStudent");
Annotation[] annotations = clazz.getAnnotations();
for (Annotation a: annotations){
System.out.println(a);
}
MyTable mt = (MyTable) clazz.getAnnotation(MyTable.class);
System.out.println(mt.value());
//获得类的属性的注解
Field f = clazz.getDeclaredField("name");
MyField mf = f.getAnnotation(MyField.class);
System.out.println(mf.columnName());
//根据获得的表名,字段的信息,拼出DDL语句,然后使用JDBC执行这个SQL,在数据库中生成相关的表
} catch (Exception e) {
e.printStackTrace();
}
}
}
- 其中最重要的方法就是getAnnotations()和getAnnotation(),获取到注解信息之后,可以进一步得到注解中的相应信息(表名,列名,类型,长度等)