反射:其实就是通过类的全限定类名,获取该类的字节码文件,然后通过该字节码文件获取字段、构造方法、普通方法等信息
创建对象过程:加载Class文件—匹配对应参数的构造方法—通过构造方法创建实例
那反射与new 对象的区别就是第一步的加载时期,前者是运行期,后者是编译期
那有什么意义呢?
反射属于比较底层的了
就是在某个类还没有设计好的时候,可以保证继续向后开发。
但只是保证功能开发完成,是运行不了的,因为这个类里的某些字段,或构造方法,或普通方法还没写。
而项目真正运行的时候,肯定所有的项目中的类都已经设计完毕,这时候,就可以保证代码正常运行了
作用:
- 提高开发灵活度,提高程序的扩展性
- 框架(提高开发效率的别人封装好的代码)底层都是使用反射技术。例如:JDBC加载驱动、Tomcat、Spring、BeanUtils.copyProperties()…
- 缺点:破坏封装性,性能低下(以后,能不用反射技术就不用)
有Declared获取时,不受权限修饰符的限制,但不能获得父类的。非public的必须setAccessible(true)
直接get获取时,只能获取public的,但可以获得父类的
1、获取构造器,创建对象
public class ReflectConstructor {
public static void main(String[] args) throws Exception {
//使用反射机制:通过字节码文件获取源文件,从而使用其内部的字段/构造方法/普通方法
Class<?> user = Class.forName("cn.itsource.day023.reflect.User");
//getConstructors():获取该类的所有public的构造方法
Constructor<?>[] constructors = user.getConstructors();
System.out.println(Arrays.toString(constructors));
//getConstructor(可变参数):根据形参列表获取对应的public的构造方法
Constructor<?> constructor = user.getConstructor();
Constructor<?> constructor2 = user.getConstructor(String.class,String.class);
System.out.println(constructor +"\n"+constructor2);
//getDeclaredConstructors():获取所有的构造方法
Constructor<?>[] declaredConstructors = user.getDeclaredConstructors();
System.out.println(Arrays.toString(declaredConstructors));
//getDeclaredConstructor(可变参数):根据形参列表获取对应的构造方法
Constructor<?> declaredConstructor = user.getDeclaredConstructor();
Constructor<?> declaredConstructor2 = user.getDeclaredConstructor(String.class);
System.out.println(declaredConstructor +"\n"+declaredConstructor2);
//对于public的构造方法,前面获取了之后,通过newInstance(...)来创建该类的对象
Object user1 = constructor2.newInstance("shp","666");
System.out.println(user1);
//对于private的构造方法,需要先破坏该类的封装,然后再通过newInstance(...)来创建该类的对象
declaredConstructor2.setAccessible(true);//要用哪个构造方法,就是哪个构造方法来破坏
Object user2 = declaredConstructor2.newInstance("shiheping");
System.out.println(user2);
//上面破坏了该类的封装,但是只是对于那个构造器来说是破坏了的,但是这个类的构造方法仍然是private
//Constructor<?> constructor3 = user.getConstructor(String.class);//不可以
//1. Class类中方法newInstance():创建当前字节码对象(只能调用无参且是public修饰的构造方法)
//通过字节码clazz也可以获取对象,但是只能获取一个public修饰的无参构造创建的对象
Object user3 = user.newInstance();
System.out.println(user3);
}
}
2、使用普通方法
public class ReflectMethod {
public static void main(String[] args) throws Exception {
//使用反射机制:通过字节码文件获取源文件,从而使用其内部的字段/构造方法/普通方法
Class<?> user = Class.forName("cn.itsource.day023.reflect.User");
//getMethods():获取该类的所有public的普通方法(包括从父类继承过来的)
Method[] methods = user.getMethods();
System.out.println(Arrays.toString(methods));
//getMethod(方法名,可变参数):根据形参列表获取对应的public的普通方法
Method method1 = user.getMethod("testStatic");
Method method3 = user.getMethod("test2",String.class);
System.out.println(method1 + "\n"+method3);
//getDeclaredMethods():获取该类自己所有的普通方法
Method[] declaredMethods = user.getDeclaredMethods();
System.out.println(Arrays.toString(declaredMethods));
//getDeclaredMethod(方法名,可变参数):根据形参列表获取对应的普通方法
Method declaredMethod = user.getDeclaredMethod("testPrivate");
//=========================执行方法=========================
//对于类中的非静态方法,只能通过创建对象去调用,所以要先创建对象
Object user1 = user.newInstance();
Object invoke = method3.invoke(user1, "shp");
declaredMethod.invoke(user1);//若方法是无参的,后面就不用写了
//对于类中的静态方法,不用创建对象再去调用,直接传入null
method1.invoke(null);
}
}
3、获得字段
public class ReflectTest3 {
public static void main(String[] args) throws Exception {
/*
* 1. 获取字节码文件(通过反射)
*/
//1.1 Class clazz = Class.forName(全限定路径名) (最多使用)默认就是调用下面的方法
Class<?> clazz = Class.forName("cn.itsource.day023.User");
/*
* 2. 字段 获取字段 的目的就是为了被 对象或者 类 赋值取值
Field[] fields = clazz.getFields() 获取public修饰的字段
Field[] fields = clazz.getDeclaredFields() 获取任意权限所有字段
Field field = clazz.getDeclaredField(String fieldName) 根据字段名获取任意访问权限的指定字段
Field field = clazz.Field(String fieldName)根据字段名获取public的指定字段
*/
//Field[] fields = clazz.getFields() 获取public修饰的字段
Field[] fields = clazz.getFields();
for (Field field : fields) {
System.out.println(field);
}
//Field[] fields = clazz.getDeclaredFields() 获取任意权限所有字段
Field[] fields2 = clazz.getDeclaredFields();
for (Field field : fields2) {
System.err.println(field);
}
/*
* Field field = clazz.getField(String fieldName)根据字段名获取public的指定字段
*/
//获取public int age成员变量
Field field = clazz.getField("age");
System.out.println("age : " + field);
/*
* Field field = clazz.getDeclaredField(String fieldName) 根据字段名获取任意访问权限的指定字段
*/
//获取private String name;成员变量
Field field2 = clazz.getDeclaredField("name");
System.out.println("name : " + field2);
//获取public static String country;成员变量
Field field3 = clazz.getDeclaredField("country");
System.out.println("country : " + field3);
/*
* 通过当前的字段对象:
* Field中的方法
给某一个字段取值
field.get(Object obj);//如果是属于非static,就传入一个对象,如果是静态的,就传入null
obj:对象
给某一个字段赋值
field.set(Object obj, Object value);//如果是属于非static,就传入一个对象,如果是静态的,就传入null
obj:对象
value:值
*/
//给public int age成员变量赋值,因为age是实例变量,所以需要创建一个User对象
Object user = clazz.newInstance();
//field.set(Object obj, Object value);
field.set(user, 17);//赋值必须根据变量是否有static修饰决定,age没有static修饰,传入对象。必须传入相同数据类型实参
//获取public int age成员变量值: field.get(Object obj);
Object age = field.get(user);
System.out.println("age = " + age);
//给 public static String country;成员变量赋值 给某一个字段赋值:field.set(Object obj, Object value);
//field.set(Object obj, Object value);
field3.set(null, "中国");//静态字段不用传入对象,只要传入一个null即可;"中国"是field3的值
//给 public static String country;成员变量取值 给某一个字段取值:field.get(Object obj);
Object country = field3.get(null);//静态字段不用传入对象,只要传入一个null即可
System.out.println("country = " + country);
}
}
4、BeanUtils.copyProperties()
这个功能也可以通过序列化与反序列化实现,就是对象转json再转对象
在拷贝的二者对象之间的很多个字段名有差异时,推荐MapStruct这个对象映射工具