45.反射

Class

除了基本数据类型外,Java的其他类型全部都是class。例如:StringObject

class是由JVM在执行过程中动态加载的。JVM在第一次读取到一种class类型时,将其加载进内存。每加载一种class,JVM就为其创建一个Class类型的对象,并关联起来。

如:当JVM加载String类时,它首先读取String.class文件到内存,然后,为String类创建一个Class对象并关联起来。这个Class对象是JVM内部创建的,如果我们查看JDK源码,可以发现Class类的构造方法是private,只有JVM能创建Class对象,我们自己的Java程序是无法创建Class对象的。

一个Class对象包含了该class的所有完整信息:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iIPvRl2a-1679666874703)(F:\文档\Notes\Java\images\image-20230119155204714.png)]

由于JVM为每个加载的class创建了对应的Class对象,并在对象中保存了该class的所有信息,包括类名、包名、父类、实现的接口、所有方法、字段等,因此,如果获取了某个Class对象,我们就可以通过这个Class对象获取到该对象对应的class的所有信息。

这种通过Class对象获取class信息的方法称为反射(Reflection)。

反射是指程序在运行期可以获取一个对象的所有信息。

反射的作用

  • 在运行期获取一个类的全部成分,可读可改。
  • 解除封装性。即使被private修饰的成员也可以进行使用。
  • 解除泛型的约束。如一个集合被约束为<String>,可以利用反射强制填入其他类型的数据。
  • 做Java高级框架

反射的第一步就是获取Class类对象,Class类不能直接通过构造获取对象:

// 名为 Class 的 class
public final class Class<T> {
    private Class(...) { ... }
}

那么应该如何获取Class对象呢?

1、类名.class

通过一个class的静态变量class获取:

Class cl = String.class;

2、对象名.getClass

final native Class<?> getClass() //通过类对象获取Class对象。方法来自Object

String s = "aaa";
Class c2 = s.getClass();

3、Class.forName

static Class<?> forName(String className) //获取指定全限定名的类的Class对象

Class c3 = Class.forName("java.lang.String");

访问构造方法

获取构造器对象

Class API说明
Constructor<?>[] getConstructors()返回全部公共权限构造器对象的数组
Constructor<?>[] getDeclaredConstructors()返回全部任意权限构造器对象的数组
Constructor getConstructor(Class<?>… parameterTypes)返回单个公共权限构造器对象
Constructor getDeclaredConstructor(Class<?>… parameterTypes)返回单个任意权限构造器对象

Student类

public class Student {
    private String name;
    private int age;

    //私有的无参构造
    private Student() {
    }

    //公共的带参构造
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    ...
}
    //返回全部公共权限构造器对象的数组
    @Test
    public void testGetConstructors() {
        Class cl = Student.class;
        Constructor[] constructors = cl.getConstructors();
        for (Constructor c : constructors) {
            // Constructor.getName :获取构造器名;
            // Constructor.getParameterCount :获取构造器中参数个数
            System.out.println(c.getName() + " -> " + c.getParameterCount());
            // Reflection.Student -> 2
        }
    }

    //返回全部任意权限构造器对象的数组
    @Test
    public void testGetDeclaredConstructors() {
        Class cl = Student.class;
        Constructor[] constructors = cl.getDeclaredConstructors();
        for (Constructor c : constructors) {
            System.out.println(c.getName() + " -> " + c.getParameterCount());
            // Reflection.Student -> 0
            // Reflection.Student -> 2
        }
    }

    //获取公共权限的带参构造器对象
    @Test
    public void testGetConstructor() throws NoSuchMethodException {
        Class cl = Student.class;
        Constructor c = cl.getConstructor(String.class, int.class);
        System.out.println(c.getName() + " -> " + c.getParameterCount());
        // Reflection.Student -> 2
    }

    //获取私有权限的无参构造器对象
    @Test
    public void testGetDeclaredConstructor() throws NoSuchMethodException {
        Class cl = Student.class;
        Constructor c = cl.getDeclaredConstructor();
        System.out.println(c.getName() + " -> " + c.getParameterCount());
        // Reflection.Student -> 0
    }

使用构造器对象

使用反射获取构造器对象的目的是创建对象

Constructor API说明
T newInstance(Object… initargs)根据指定的构造器创建对象
public void setAccessible(boolean flag)true表示解除访问权限(暴力反射)

setAccessible 置为 true 后,即使是私有构造器也能构造对象。但该操作可能会失败。

还是上述Student类

public static void main(String[] args) throws Exception {
        Class cl = Student.class;
        Constructor con = cl.getDeclaredConstructor();
        Student s = (Student) con.newInstance();
        System.out.println(s);  // IllegalAccessException 非法访问异常。
    }

Student 类的无参构造是私有的,不能访问,但是可以通过 setAccessible 解除权限。

public static void main(String[] args) throws Exception {
        Class cl = Student.class;
        Constructor con = cl.getDeclaredConstructor();
        //解除权限
        con.setAccessible(true);
        Student s = (Student) con.newInstance();
        System.out.println(s); // Student{name = null, age = 0}
    }

访问字段

字段(Field),是 Java 编程语言中类的一个成员,主要用来存储对象的状态,所以有时也可称为成员字段或成员变量。

获取字段对象

Class AIP说明
Field[] getFields()返回全部公共权限成员变量对象的数组
Field[] getDeclaredFields()返回全部任意权限成员变量对象的数组
Field getField(String name)返回单个公共权限成员变量对象
Field `getDeclaredField(String name)返回单个任意权限成员变量对象

Student类

public class Student {
    private String name;
    private int age;
    private static String TeacherName = "zhangSan";
    public final String SchoolName = "laJi";
    ...
}
    public static void main(String[] args) throws NoSuchFieldException {
        //反射获取Student类的Class对象
        Class cl = Student.class;
        //获取全部任意权限字段对象的数组
        Field[] fields = cl.getDeclaredFields();
        for (Field field : fields) {
            // Field.getName :获取变量名;Field.getType :获取变量类型
            System.out.println(field.getName() + " - " + field.getType());
            /* name - class java.lang.String
               age - int
               TeacherName - class java.lang.String
               SchoolName - class java.lang.String   */
        }

        //获取单一任意权限字段对象
        Field field = cl.getDeclaredField("TeacherName");
        System.out.println(field.getName() + " - " + field.getType());// TeacherName - class java.lang.String
    }

使用字段对象

使用反射获取字段对象的目的是赋值和取值

Field API说明
void set(Object obj, Object value)给指定对象注入指定值
Object get(Object obj)获取指定对象的成员变量值
void setAccessible(boolean flag)true表示解除访问权限(暴力反射)
public static void main(String[] args) throws Exception {
        //获取指定字段对象
        Class cl = Student.class;
        Field name = cl.getDeclaredField("name");
        //创建Student对象
        Student s = new Student("liSi", 23);
        //解除字段权限
        name.setAccessible(true);

        //修改字段值
        name.set(s, "wangWu");
        System.out.println(s);	// Student{name = wangWu, age = 23}

        //获取字段值
        String str = (String) name.get(s);
        System.out.println(str);	// wangWu
    }

访问成员方法

获取方法对象

Method AIP说明
Method[] getMethods()返回全部公共权限成员方法对象的数组
Method[] getDeclaredMethods()返回全部任意权限成员方法对象的数组
Method getMethod(String name, Class<?>… parameterTypes)返回单个公共权限成员方法对象
Method getDeclaredMethod(String name, Class<?>… parameterTypes)返回单个任意权限成员方法对象

Play类

public class Play {
    private void play() {
        System.out.println("玩...");
    }

    private void play(String name) {
        System.out.println("玩" + name);
    }
}
public static void main(String[] args) throws NoSuchMethodException {
        Class cl = Student.class;

        //返回全部任意权限成员方法对象的数组
        Method[] methods = cl.getDeclaredMethods();
        for (Method method : methods) {
            // getName:获取方法名; getParameterTypes:获取参数类型; getAnnotatedReturnType:获取返回值类型;
            System.out.println(method.getName() + " - " + Arrays.toString(method.getParameterTypes()) + " - " + method.getAnnotatedReturnType());
            /* 运行结果
            		play - [] - void
				   play - [class java.lang.String] - void
            */
        }

        //返回单个任意权限成员方法对象
        Method method = cl.getDeclaredMethod("setName", String.class);
        System.out.println(method.getName() + " - " + Arrays.toString(method.getParameterTypes()) + " - " + method.getAnnotatedReturnType());
    // play - [class java.lang.String] - void
    }

使用方法对象

使用反射获取方法对象的目的是执行

Method API说明
Object invoke(Object obj, Object… args)触发方法对象对应的方法
void setAccessible(boolean flag)true表示解除访问权限(暴力反射)

参数说明

Object obj : 用于调取方法的对象

Object... args : 调用方法时传递的参数(无参不写)

Object(返回值类型) : 方法的返回值类型,没有返回值返回 null

public static void main(String[] args) throws Exception {
        Class cl = Play.class;
        //获取两种方法
        Method m1 = cl.getDeclaredMethod("play");
        Method m2 = cl.getDeclaredMethod("play", String.class);

        //解除权限
        m1.setAccessible(true);
        m2.setAccessible(true);

        //触发方法
        Play p = new Play();
        Object ret1 = m1.invoke(p);     // 玩...
        Object ret2 = m2.invoke(p, "游戏"); // 玩游戏
        System.out.println(ret1);   // null
        System.out.println(ret2);   // null
    }

通用框架

反射的最大作用就是做通用框架。

需求: 在不清楚对象字段的情况下,可以将对象的字段名称和对应值储存到文件中。

学生类:

public class Student {
    private String name;
    private int age;
    private char sex;
    private String className;
    ...
}

教师类:

public class Teacher {
    private String name;
    private int age;
    private double wages;
    ...
}

框架:

public class Universal {
    /**
     * 可以将不同类型的对象写入文件
     *
     * @param obj 任意对象
     */
    public static void save(Object obj) {
        try (PrintStream ps = new PrintStream(new FileOutputStream("D:\\test\\info.txt", true));) {
            //1.关联对象的class文件
            Class cl = obj.getClass();
            ps.println("-------------" + cl.getSimpleName() + "-------------");
            //2.获取对象的全部字段(成员变量)
            Field[] fields = cl.getDeclaredFields();
            for (Field field : fields) {
                try {
                    //3.获取字段在对象中对应的信息
                    String fieldName = field.getName(); //字段名
                    field.setAccessible(true);
                    String fieldValue = field.get(obj) + ""; //字段值
                    //4.写入文件
                    ps.println(fieldName + " = " + fieldValue);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

测试:

public class Demo {
    public static void main(String[] args) {
        Student s = new Student("小明", 19, '男', "一班");
        Teacher t = new Teacher("张三", 30, 8000);
        Universal.save(s);
        Universal.save(t);
    }
}

运行结果 info.txt文件中:

-------------Student-------------
name = 小明
age = 19
sex = 男
className = 一班
-------------Teacher-------------
name = 张三
age = 30
wages = 8000.0
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值