一、前言
类其实也是一种对象,是java.lang.Class类的实例。编译阶段类信息会被编译成.class文件,在类加载阶段,类加载器把.class文件读入JVM内存,生成java.lang.Class对象(这时已经可以通过Class对象查看类信息了),再经过类的连接(验证、准备、解析)与类的初始化阶段后即可使用类对象。我们通常使用的new关键字与反射创建实例时都会触发类初始化。
二、Java反射的常规使用步骤
1、 获取目标类的java.lang.Class对象;
2、获取目标类的信息(字段、方法、构造器、注解等);
3、生成并操作对象
三、获取目标类的java.lang.Class对象
这个是搞Java必会的基础,面试也常问,主要有三种方法:
注:为方便测试,定义一个User类,类定义如下:
public class User {
private String user_id;
public String user_name;
//公用无参构造器
public User(){
}
//公用有参构造器
public User(String user_id, String user_name){
this.user_id = user_id;
this.user_name = user_name;
}
//getter
public String getUser_id() {
return user_id;
}
//setter
public void setUser_id(String user_id) {
this.user_id = user_id;
}
//test method
public void say(){
System.out.println(this.user_name + "Hello world!");
}
}
测试如下:
public class TestReflect {
public static void main(String[] args) throws ClassNotFoundException {
// 获取java.lang.Class对象的三组方式
// 第一种,使用Class的静态方法forName(类全称),可能抛出ClassNotFound异常
Class c1 = Class.forName("com.javaReflect.User");
// 第二种,使用类的class属性
Class c2 = User.class;
// 第三周,使用对象的getClass()方法
Class c3 = new User().getClass();
System.out.println(c1.getName());
System.out.println(c2.getName());
System.out.println(c3.getName());
}
}
测试结果:其中,第一种和第二种方法都是直接根据类名获取Class对象,第二种方法具有如下两种优势:
1、编译阶段检查目标类是否存在,更安全;
2、无需调用方法,效率更高;
所以,我们通常推荐使用第二种方式获取Class对象,但是如果在编写程序的时候我们不知道这里需要产生哪个Class对象,只知道有个类名会传入,这种情况我们就只能使用第一种方式去获取Class对象,但这也是反射的精髓,动态而灵活。
四、获取目标类信息
1、获取成员变量信息
getFields()——获取所有公有成员变量
getField(String name)——获取指定名称的公有成员变量,会抛出NoSuchFieldException异常
getDeclaredFields()——获取所有成员变量,不关乎权限
getDeclaredFields()——获取指定名称的成员变量,不关乎权限,会抛出NoSuchFieldException异常
测试如下:
public class TestReflect {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, SecurityException {
String className = "com.javaReflect.User";
Class c = Class.forName(className);
// 1、getFields()获取所有公有成员变量
for (Field field : c.getFields()) {
print(field);
}
System.out.println("-------------------------");
// 2、getField(String name)获取指定名称的公有成员变量,会抛出异常
print(c.getField("user_name"));
System.out.println("-------------------------");
// 3、getDeclaredFields()获取所有成员变量
for (Field field : c.getDeclaredFields()) {
print(field);
}
System.out.println("-------------------------");
// 4、getDeclaredFields()获取指定名称的成员变量,会抛出异常
print(c.getDeclaredField("user_id"));
}
public static void print(Field field) {
// 输出变量类型
System.out.println(field.getType().getName());
// 输出变量名
System.out.println(field.getName());
}
}
测试结果:2、获取方法以及参数信息
getMethods()——获取所有公有方法
getMethod(String methodName, Class<?> parameterTypes)——根据方法及形参列表获取公有方法,会抛出NoSuchMethodException异常
getDeclaredMethods()——获取所有方法
getDeclaredMethod(String methodName, Class<?> parameterTypes)——根据方法及形参列表获取方法,会抛出NoSuchMethodException异常
getParameters()——获取所有形参
测试如下:
public class TestReflect {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
String className = "com.javaReflect.User";
Class c = Class.forName(className);
// 1、获取所有公有方法
for (Method method : c.getMethods()) {
print(method);
System.out.println("--------------------------");
}
// 2、获取某一方法
print(c.getMethod("setUser_id", String.class));
System.out.println("--------------------------");
// 3、getDeclaredMethods()获取所有方法
// 4、getDeclaredMethod(String name, Class<?> parameterTypes);获取某一方法
// 3、4与1、2使用方式类似,区别在于访问权限,3、4可以访问任意权限
}
public static void print(Method method) {
// 输出方法名
System.out.println(method.getName());
// 输出方法返回值类型名
System.out.println(method.getReturnType().getName());
// 输出形参相关信息
Parameter[] parameters = method.getParameters();
int i = 0;
for (Parameter parameter : parameters) {
System.out.println("第" + ++i + "个形参信息:");
// 输出形参名
System.out.println(parameter.getName());
// 输出形参类型名
System.out.println(parameter.getType().getName());
}
}
}
测试结果:3、获取构造方法信息(与上面一个套路……)
getConstructors()——获取所有公有构造器
getConstructor(Class<?> parameterTypes)——根据形参列表获取某一公有构造器
getDeclaredConstructors()——获取所有构造器
getDeclaredConstructor(Class<?> parameterTypes)——根据形参列表获取某一构造器
4、获取注解,关键字为annotation,一样一样的~~~~(>_<)~~~~
五、使用反射生成并操作对象
1、创建对象,两种方式:
Class对象的newInstance()方法——调用默认构造创建实例
Class对象获取指定Constructor对象,再调用该对象的newInstance()方法
测试如下:
public class TestReflect {
public static void main(String[] args) throws Exception {
String className = "com.javaReflect.User";
Class c = Class.forName(className);
// 第一种方式
User user1 = (User) c.newInstance();
user1.user_name = "第一种方式";
user1.say();
// 第二种方式
User user2 = (User) c.getConstructor(String.class, String.class).newInstance("2", "第二种方式");
user2.say();
}
}
测试结果:2、调用方法
Method对象的invoke(Object obj, Object args)——其中,第一个参数obj是执行该方法的主调(也就是一个拥有这个方法的对象),后面的arg是调用该方法的实参。
测试如下:
public class TestReflect {
public static void main(String[] args) throws Exception {
String className = "com.javaReflect.User";
Class c = Class.forName(className);
User user = (User) c.getConstructor(String.class, String.class).newInstance("1", "调用方法");
Method sayMethod = c.getMethod("say", null);
sayMethod.invoke(user, null);
}
}
测试结果:3、修改成员变量值
Field对象的getXXX(Object obj)——obj为拥有该属性的对象,XXX为8种基本类型,代表着成员变量类型,如果成员变量为引用类型,则去掉XXX。
Field对象的setXXX(Object obj, XXX val)——obj同上,val为要设置的值
测试如下:
public class TestReflect {
public static void main(String[] args) throws Exception {
String className = "com.javaReflect.User";
Class c = Class.forName(className);
User user = (User) c.getConstructor(String.class, String.class).newInstance("1", "调用方法");
Method sayMethod = c.getMethod("say", null);
sayMethod.invoke(user, null);
Field user_name = c.getField("user_name");
user_name.set(user, "修改后的用户名");
sayMethod.invoke(user, null);
}
}
测试结果: