【学习笔记】反射(三)获取并操作对象及方法
-
通过反射获取运行时类的完整结构
Field、Method、Constructor、Superclass、Interface、Annotation...
我们以User类为例:
class User{
private String name;
private int age;
public User() {
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
-
获得类的名字
//获取User类的class对象
Class c1 = Class.forName("com.reflection.User");
//获得类的名字
System.out.println(c1.getName()); //类名 + 包名
System.out.println(c1.getSimpleName()); //类名
-
获得类的属性
//获得类的属性
Field[] fields = c1.getFields(); //只能找到public的属性
for (Field field : fields) {
System.out.println(field);
}
fields = c1.getDeclaredFields(); //找到全部的属性
for (Field field : fields) {
System.out.println(field);
}
//获得指定属性,与上面相同getField()只能找到public的属性
Field name = c1.getDeclaredField("name");
System.out.println(name);
-
获得类的方法
//获得类的方法
Method[] methods = c1.getMethods(); //获得本类以及父类的public方法
for (Method method : methods) {
System.out.println("getMethods:"+method);
}
methods = c1.getDeclaredMethods(); //获得本类的所有方法,包括私有的方法
for (Method method : methods) {
System.out.println("getDeclaredMethods:"+method);
}
获得指定的方法:需要传入方法名和参数的类型
//获得指定的方法
Method method = c1.getMethod("getName",null);
Method method1 = c1.getMethod("setName",String.class);
System.out.println(method);
System.out.println(method1);
-
获得类的构造器
//获得类的属性
Field[] fields = c1.getConstructor();
for (Field field : fields) {
System.out.println(field);
}
获得指定的构造器:需要传入参数的类型
//获得指定的构造器
Constructor declaredConstructor = c1.getDeclaredConstructor(String.class, int.class);
System.out.println("指定构造器"+declaredConstructor);
动态创建对象执行方法
有了Class对象,能做什么?
-
创建类的对象:调用Class对象的newInstance()方法
-
类必须有一个无参数的构造器
-
类的构造器的访问权限需要足够
-
package com.reflection;
public class Demo08 {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
//获得Class对象
Class c1 = Class.forName("com.reflection.User");
//构造一个对象
User user = (User) c1.newInstance();
System.out.println(user);
}
}
调用newInstance()方法,本质上是调用了类的无参构造器 ,如果没有无参构造就会报错
如果没有无参构造并且想要创建对象,应该怎么办呢?
-
通过Class类的getDeclaredConstructor(Class...parameterTypes)方法 取得本类的指定参数的构造器
-
然后向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数
-
通过Constructor 实例化对象
//通过构造器创建对象
Constructor constructor = c1.getDeclaredConstructor(String.class, int.class);
User user2 = (User) constructor.newInstance();
System.out.println(user2);
-
通过反射调用普通方法,通过Method类完成
-
通过Class类的getMethod(String name,Class...parameterTypes)方法取得一个Method 对象,并设置此方法操作时所需要的参数类型
-
之后使用Object invoke(Object obj,Object[] args) 进行调用,并向方法中传递要设置的obj对象的参数信息
-
//通过反射调用普通方法
User user3 = (User) c1.newInstance();
//获取方法
Method setName = c1.getDeclaredMethod("setName", String.class);
//激活执行,要传入两个参数,一个是获取方法所对应的对象,一个是方法本身需要的参数
//setName方法是user3的方法,它需要传入一个字符串的参数即“李四”
setName.invoke(user3,"李四");
System.out.println(user3.getName());
-
通过反射操作属性
//通过反射操作属性
User user4 = (User) c1.newInstance();
//获取属性
Field name = c1.getDeclaredField("name");
//调用set方法,传入对象和需要给属性赋的值
name.set(user4,"王五");
System.out.println(user4.getName());
我们执行上面的代码,发现报错了,原因就是name这个属性在User中是private,我们没有权限直接操作,但是我们可以调用setAccessible(true)方法关闭安全检测,然后再去操作私有属性就可以了
//通过反射操作属性
User user4 = (User) c1.newInstance();
//获取属性
Field name = c1.getDeclaredField("name");
//关闭安全检测
name.setAccessible(true);
//调用set方法,传入对象和需要给属性赋的值
name.set(user4,"王五");
System.out.println(user4.getName());
调用指定方法:Object invoke(Object obj,Object...args)
-
Object 对应原方法的返回值,若原方法无返回值,此时返回null
-
若原方法若为静态方法,此时形参Object obj 可为null
-
若原方法形参列表为空,则Object[] args 为null
-
若原方法声明为private,则需要在调用此 invoke() 方法之前,显示调用方法对象的setAccessible(true)方法,将可访问private方法
setAccessible:
-
Method 和 Field、Constructor 对象都有setAccessible()方法
-
setAccessible的作用是启动和禁用访问安全检查的开关
-
参数值为true则指示反射的对象在使用时应该取消Java语言访问检查
-
提高反射的效率,如果代码中必须用反射,而该句代码需要频繁的被调用,那么请设置为true
-
使得原本无法访问的私有成员也可以访问
-
-
参数值为false则指示反射的对象应该实施Java语言访问检查
性能对比分析
我们分别用 普通方式调用、反射方式调用、关闭安全检测的反射方式调用 User类的getName()方法,然后执行十亿次,计算执行的时间
package com.reflection;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Demo09 {
//普通方式调用
public static void test01(){
User user = new User();
long startTime = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
user.getName();
}
long endTime = System.currentTimeMillis();
System.out.println("普通方式调用:"+(endTime-startTime) + "ms");
}
//反射方式调用
public static void test02() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
User user = new User();
Class c1 = user.getClass();
Method getName = c1.getDeclaredMethod("getName", null);
long startTime = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
getName.invoke(user,null);
}
long endTime = System.currentTimeMillis();
System.out.println("反射方式调用:"+(endTime-startTime) + "ms");
}
//关闭安全检测方式调用
public static void test03() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
User user = new User();
Class c1 = user.getClass();
Method getName = c1.getDeclaredMethod("getName", null);
getName.setAccessible(true);
long startTime = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
getName.invoke(user,null);
}
long endTime = System.currentTimeMillis();
System.out.println("关闭检测调用:"+(endTime-startTime) + "ms");
}
public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
test01();
test02();
test03();
}
}