(ps: 本文章适用于超级萌新,所以会说一些基础的东西,不喜勿喷)
目录
为什么叫反射??
Java反射基础常用代码的有以下三个方法:
让我们举一个示例:
输出:
反射有什么用?
通过反射优化代码的示例:
运行结果:
反射的所有常用方法
注意
练习
答案
为什么叫反射??
? ? ? ? reflect 可以把某一对象中的变量、方法等等内容映射成一个个的变量对象、方法对象等。打个不恰当的比方,就好比一个 Object (对象)?在照镜子,因为光线反射致使镜中呈现了一个和这个 Object?一模一样的镜像,而镜中的成像便是我们可以操作的对象。因为我们操作的是镜中的对象,并非是原先存在在照镜子的那个对象,我们便可以通过让任意一个对象照镜子,但是我们操作镜中对象的方式却可以不变。
Java反射基础常用代码的有以下三个方法:
// 1.获取 某一对象 的 Class, 有以下两种方法:
Class<?> c = Object.class;
Class<?> c = new Object().getClass();
// 2.获取 某一对象 的 Method (方法)[需要抛出异常 NoSuchMethodException]:
Object.getMethod(String: 方法名, Class<?>[]: 方法参数列表);
// 3.invoke (调用) 某一对象 的 Method (方法)
// [需要抛出异常 InvocationTargetException, IllegalAccessException]:
Method.invoke(Object: 对象, Object[]: 参数值);
让我们举一个示例:
[ Student.java?]
public class Student {
private String name;
private Integer age;
public Student(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
心细的同学可以发现,年龄字段 age 是 Integer 而并非是 int ,因为反射操作的是类对象 (Integer 是 int 的封装类),如果使用 int 类型,会出现一些一些奇奇怪怪的错误,但也不是不可以使用 int,只是初学者不建议这么做。
[ Reflect.java?]
import java.lang.reflect.Method;
public class Reflect {
public static void main(String[] args) {
Student s = new Student("渣渣辉", 21);
System.out.println(s.toString());
try {
Class<?> c = s.getClass();
Method m = c.getMethod("setAge", Integer.class);
m.invoke(s, 16);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(s.toString());
}
}
输出:
Student{name='渣渣辉', age=21}
Student{name='渣渣辉', age=16}
可以看到,已经成功调用了 Student class 中的 setAge 方法。
反射有什么用?
? ? ? ? 很多同学会好奇,反射这么麻烦我为什么不直接调用原对象的方法呢?
? ? ? ? 反射可以优化代码中的大量 if-else 语句,使得代码更加优雅,而且反射最重要的是可以动态加载,目前市面上热门的框架(如Spring,Mybatis)几乎都需要反射来完成。反射还能实现各种骚操作,这些操作就需要你自己去探索了。
通过反射优化代码的示例:
[ Action.java?]
public class Actions {
public void login(String username, String password){ // 登录
System.out.printf("登录: username: %s, password: %s\n", username, password);
}
public void regist(String username, String password){ // 注册
System.out.printf("注册: username: %s, password: %s\n", username, password);
}
}
[ Reflect.java ]
import java.lang.reflect.Method;
public class Reflect {
public static void Login(String action, String username, String password){
try{
Method c = Actions.class.getMethod(action, String.class, String.class);
c.invoke(new Actions(), username, password);
}catch (Exception e){
e.printStackTrace();
}
}
}
[ Login.java?]
public class Login {
public static void main(String[] args) {
Reflect.Login("login", "account", "123");
Reflect.Login("regist", "test", "testPass");
}
}
运行结果:
登录: username: account, password: 123
注册: username: test, password: testPass
从上面可看出,主类(Login.java)的代码量大大缩减,一行即可实现登录或者注册的操作。而且在实际开发的项目中,我们可以在不修改主类源码的情况下进行更新,只需要在 Action.java 中更新代码即可。比如需要加入一个注销账号的功能,只需要在 Action.java 中加入 public void 注销(String 账号, String 密码){}。
????????在此示例情况下的反射操作可以避免代码臃肿,使得代码更好被维护,但是缺点是反射会比 if-else 语句的速度慢,在小型项目上这个缺点可以忽略不计。
反射的所有常用方法
// Class (类) 的获取
Class<?> cl = Object.class; // 获取对象的字节码文件对象
Class<?> cl2 = 基础数据类型.class; // 类型包含基本数据类型和引用数据类型
Class<?> cl3 = Class.forName(String: 字节码全路径); // 通过字符串获取一个字节码文件,字符串必须是全路径名
// Constructor (构造) 的相关操作
Object obj = cl.newInstance(); // 无参构造对象, 等效于 new Object [已弃用]
Object obj2 = cl.getDeclaredConstructor().newInstance(); // 与上一样 [JAVA 1.9+]
Constructor con = cl.getDeclaredConstructor(class<?>[]: 参数类型); // 有参构造
Object obj3 = con.newInstance(Object[]: 参数内容); // 有参构造实例化对象
Constructor[] cons = cl.getConstrutors(); // 获取所有构造器
con.getName(); // 获取构造方法的名称
con.getParameterCount(); // 获取构造方法的参数数量
con.getParameterTypes(); // 获取构造方法的参数类型
// Field (属性) 的相关操作
Field field = cl.getField(String: 属性名称); // 获取 public 属性
Field field2 = cl.getDeclaredField(String: 属性名称); // 获取任意属性
Field[] fieldPublic = cl.getFields(); // 获取所有 public 属性
Field[] fields = cl.getDeclaredFields(); // 获取所有属性
String name = field.getName(); // 获取属性名称
Class fieldClass = field.getType() 获取属性类型的字节码文件
field.setAccessible(Boolean: 是否强制); // 为 true 时可以强制对该属性进行修改等操作
field.set(Object: 对象, Object: 值); // 设置该对象的 field 的值, 等效于 Object.fieldName = value;
Object obj4 = field.get(); // 获取该属性的值
// Method (方法) 的相关操作
Method m = cl.getMethod(String: 方法名, Class<?>[]: 方法参数列表); // 获取 public 方法, getMethod(String methodName, Class<?>... classes)
Method m2 = cl.getDeclaredMethod(String: 方法名, Class<?>[]: 方法参数列表); // 获取任意方法
注意
????????在大多数情况下 getDeclaredXXX 与 getXXX 是通用的,但是在属性或者方法的访问修饰符为 protected 的时候,只能使用?getDeclaredXXX。
练习
? ? ? ? 尝试自己写一个反射工具类 ReflectUtils,用来获取或设置一个包含 Getter 与 Setter 方法的类中的属性的值,并且还可以任意调用某一个类中的方法。
答案
[ ReflectUtils ]
import java.lang.reflect.Method;
public class ReflectUtils {
// 反射调用 GET
public static Object invokeGetter(Object obj, String fieldName){
System.out.println(joinString("get", fieldName));
return invokeMethod(obj, joinString("get", fieldName), null, null);
}
// 反射调用 SET
public static void invokeSetter(Object obj, String fieldName, Object value){
invokeMethod(obj, joinString("set", fieldName), new Class<?>[]{value.getClass()}, new Object[]{value});
}
/**
* 调用对象的方法
* @param obj 对象
* @param methodName 方法名
* @param parameterTypes 参数列表
* @param args 参数
* @return 调用的方法返回值
*/
public static Object invokeMethod(Object obj, String methodName, Class<?>[] parameterTypes, Object[] args){
try {
Class<?> c = obj.getClass();
Method me = c.getDeclaredMethod(methodName, parameterTypes);
return me.invoke(obj, args);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 获取一个标准的 getter 或者 setter 方法名称
* @param prefix 前缀, get / set
* @param fieldName 属性名
* @return 前缀 + 开头大写的属性名
*/
public static String joinString(String prefix, String fieldName){
char[] chars = fieldName.toCharArray();
chars[0] = chars[0] >= 97 ? (char) (chars[0] - 32) : chars[0]; // a 是 97
return prefix + String.valueOf(chars);
}
}
|