Java-Day-36( 通过反射获取类的结构信息 + 通过反射访问类中的成员 + 章节练习 )

发布时间 2023-07-24 21:56:10作者: 朱呀朱~

Java-Day-36

通过反射获取类的结构信息

第一组:java.lang.Class 类

  • 以下说的包含本类和父类 —— 也包括超类等
    • 方法属性之类的若是输出时不加 .getName,则都是输出:com.zyz.Zyz()
public class test {
    public static void main(String[] args) {

    }
    @Test
    public void api() throws ClassNotFoundException {
        //        获取Class对象
        Class<?> zyzClass = Class.forName("com.zyz.Zyz");
        //        getName:获取全类名
        System.out.println(zyzClass.getName()); // com.zyz.Zyz
        //        getSimpleName:获取简单类名
        System.out.println(zyzClass.getSimpleName()); // Zyz
        //        getFields:获取所有public修饰的属性,包括本类以及父类
        Field[] fields = zyzClass.getFields();
        for (Field field : fields) {
            System.out.println("本类即父类的public属性: " + field.getName());
        }
        //        getDeclaredFields:获取本类中所有的属性,无视修饰符
        Field[] declaredFields = zyzClass.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            System.out.println("获取本类中所有的属性(无视修饰符): " + declaredField.getName());
        }
        //        getMethods:获取所有public修饰的方法,包含本类以及父类的
        Method[] methods = zyzClass.getMethods();
        for (Method method : methods) {
            System.out.println("所有的public修饰的方法: " + method.getName()); 
        }
        //        getDeclaredMethods:获取本类中所有方法
        Method[] declaredMethods = zyzClass.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            System.out.println("本类中所有方法: " + declaredMethod.getName());
        }
        //        getConstructors:获取所有public修饰的构造器,只限本类
        Constructor<?>[] constructors = zyzClass.getConstructors();
        for (Constructor<?> constructor : constructors) {
            System.out.println("本类所有public修饰的构造器: " + constructor.getName()); // com.zyz.Zyz
            // 这里只是输出名字,因为是构造器,所以就是类名路径
        }
        //        getDeclaredConstructors:获取本类的任何修饰符的构造器
        Constructor<?>[] declaredConstructors = zyzClass.getDeclaredConstructors();
        for (Constructor<?> declaredConstructor : declaredConstructors) {
            System.out.println("本类所有构造器: " + declaredConstructor.getName()); // com.zyz.Zyz
            // 这里只是输出名字,因为是构造器,所以就是类名路径
        }
        //        getPackage:以Package形式返回包全路径
        System.out.println(zyzClass.getPackage()); // package com.zyz
        //        getSuperclass:以Class形式返回父类的全路径
        System.out.println(zyzClass.getSuperclass()); // class com.zyz.Person
        //        getInterfaces:以Class[]形式返回接口信息
        Class<?>[] interfaces = zyzClass.getInterfaces();
        for (Class<?> anInterface : interfaces) {
            System.out.println("接口信息: " + anInterface); // interface 接口路径
        }
        //        getAnnotations:以Annotation[]形式返回注解信息
        Annotation[] annotations = zyzClass.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println("注解信息: " + annotation);
        }
    }
}



class Person {
    public String country;
    public void hi() {}
}

interface ABC {}
interface CBA {}

class Zyz extends Person implements ABC, CBA{
//    四种不同访问权限的测试
    public String name;
    protected int age;
    String job;
    private double sal;

//    方法
    public void m1(){}
    protected void m2(){}
    void m3(){}
    private void m4(){}

    public Zyz() {
    }
}

第二组:java.lang.reflect.Field 类

  • getModifiers:以 int 形式返回修饰符
    • 默认修饰符是 0,public 是 1, private 是 2, protected 是 4,static 是 8,final 是 16
    • 类似 public static 这样多个的话就是相加,也就是 9
  • getType:以 Class 形式返回类型
  • getName:返回属性名
//        getDeclaredFields:获取本类中所有的属性,无视修饰符
Field[] declaredFields = zyzClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
    System.out.println("获取本类中所有的属性(无视修饰符): " + declaredField.getName()
                       + "该属性修饰符值为:"  + declaredField.getModifiers()
                       + "该属性的类型为:" + declaredField.getType());
}

第三组:java.lang.reflect.Method 类

  • getModifiers:以 int 形式返回修饰符
    • 默认修饰符是 0,public 是 1, private 是 2, protected 是 4,static 是 8,final 是 16
    • 类似 public static 这样多个的话就是相加,也就是 9
  • getReturnType:以 Class 形式获取返回类型
  • getName:返回方法名
  • getParameterTypes:以 Class[] 返回参数类型数组
//        getDeclaredMethods:获取本类中所有方法
Method[] declaredMethods = zyzClass.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
    System.out.println("本类中所有方法: " + declaredMethod.getName()
                       + "该方法的访问修饰符为:" + declaredMethod.getModifiers()
                       + "该方法返回的类型为:" + declaredMethod.getReturnType());

    //            输出当前这个方法的形参数组情况
    Class<?>[] parameterTypes = declaredMethod.getParameterTypes();
    for (Class<?> parameterType : parameterTypes) {
        System.out.println("该方法的形参类型为:" + parameterType);
    }
}

第四组:java.lang.reflect.Constructor 类

  • getModifiers:以 int 形式返回修饰符

  • getName:返回构造器名 ( 全类名 )

  • getParameterTypes:以 Class[] 返回参数类型数组

  • 此处三个方法在上面都有类似例子

通过反射创建对象

  • 方式一:调用类中的 public 修饰的无参构造器

  • 方式二:调用类中的指定构造器

  • Class 类相关方法

    • newInstance:调用类中的无参构造器,获取对应类的对象
    • getConstructor(Class ... clazz): 根据参数列表,获取对应的 public 构造器对象
    • getDecalaredConstructor(Class ... clazz):根据参数列表,获取所有的对应的构造器对象
  • Constructor 类相关方法

    • setAccessible:暴破
    • newInstance(Object ... obj):调用构造器

案例

class User {
    private String name = "zyz";
    private int age = 100;
    
    public User() { // public的无参构造器
    }
    public User(String name) { // public的有参构造器
        this.name = name;
    }
    private User(String name, int age) { // private的有参构造器
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
            "name='" + name + '\'' +
            ", age=" + age +
            '}';
    }
}
  • 通过反射创建某类的对象,要求该类中必须有 public 的无参构造器

    //        1. 先获取到User类的Class对象
            Class<?> userClass = Class.forName("com.zyz.User");
    
    //        2. 通过public的无参构造器创建实例
            Object o = userClass.newInstance();
            System.out.println("无参的赋最初值的构造器 ——> " + o); // 无参的赋最初值的构造器 ——> User{name='zyz', age=100}
    
    
  • 通过调用某个特定构造器的方式,实现创建某类的对象

    • public 的有参构造器
    • private 的有参构造器
    //        3. 通过public的有参构造器创建实例
            /*
                此时的constructor就是:
                    public User(String name) { // public的参数列表为一个String的有参构造器
                        this.name = name;
                    }
             */
    //        3.1. 先获取对应的构造器
            Constructor<?> constructor = userClass.getConstructor(String.class); // 所有String类型的class对象
    //        3.2. 再创建实例并传入形参
            Object babaName = constructor.newInstance("霸霸");
            System.out.println("构造器传入新name ——> " + babaName); // 构造器传入新name ——> User{name='霸霸', age=100}
    
    
    //        4. 通过非public的有参构造器创建实例 ——> 传统方法不行,但反射可以
    //        4.1. 得到private的构造器对象
            Constructor<?> constructor1 = userClass.getDeclaredConstructor(String.class, int.class);
    //        4.2. 如果是私有的private,就需要暴破 —— 暴力破解,使用反射可以访问private构造器/方法/属性
            constructor1.setAccessible(true);
    //        4.3. 创建实例
            Object zyz = constructor1.newInstance("老朱", 125);
            System.out.println("private的构造器 ——> " + zyz); // private的构造器 ——> User{name='老朱', age=125}
    

通过反射访问类中的成员

  • 静态可不指明对象,Declared 拿所有成员,私有还是要暴破

属性

  • 根据属性名获取 Field 对象
  • 暴破,私有属性也可以访问到
  • 访问
    • f.set(o, 值); // o表示对象
    • f.get(o); // o表示对象
  • 注意:如果是静态属性,则 set 和 get 中的参数 o 可以写成 null
public class test {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
//        1. 得到User类对应的Class对象
        Class<?> userClass = Class.forName("com.zyz.User");
//        2. 创建对象
        Object o = userClass.newInstance(); // o 的运行类型就是 User
//        3. 使用反射得到属性对象 ———— public
        Field pubName = userClass.getField("name");
        System.out.println(pubName);
        pubName.set(o, "zyz");
        System.out.println("加上共有属性的名称" + o);
        System.out.println(pubName.get(o));
//        4. 使用反射操作age属性 ———— private static
        Field priAge = userClass.getDeclaredField("age");
//        4.1 对私有的只能用Declared才能包含在内,但想使用或者更改,还是需要暴破,否则无法进行操作
        priAge.setAccessible(true);
//        priAge.set(o, 100);
        priAge.set(null, 250); // 对象位置写null也可以,因为age是静态属性
//        ———— 静态是属于所有对象的,在类加载的时候就已经有了,所以null也无妨,写上 o 的写法是因为对象也是可以直接操作静态的
        System.out.println("再加上私有属性的年龄" + o);
        System.out.println("通过 o 获取:" + priAge.get(o));
        System.out.println("因为静态,所以对象部分写null也可获取:" + priAge.get(null));
    }
}

class User {
    public String name;
    private static int age;

    public User() {
    }
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

方法

  • 根据方法名和参数列表获取 Method 方法对象:Method m = clazz.getDeclaredMethod(方法名, XX.class);
    • 加上 Declared 可以访问所有的,去掉就只能是 public 共有的了
  • 获取对象:Object o = clazz.newInstance();
  • 暴破:m.setAccessible(true);
  • 访问:Object returnValue = m.invoke(o, 实参列表);
    • 就算方法有指定返回值也无用,都是统一用 Object 接收的
  • 注意:如果是静态方法,则 invoke 的参数 o 可以写成 null
public class test {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
//        1. 得到User类对应的Class对象
        Class<?> userClass = Class.forName("com.zyz.User");
//        2. 创建对象
        Object o = userClass.newInstance(); // o 的运行类型就是 User
//        3. 调用public的hello方法
//        Method hello = userClass.getMethod("zyz, String.class");
//        3.1 得到hello方法对象
        Method hello = userClass.getDeclaredMethod("hello", String.class); // 有参的话就要加参数
//        3.2 调用
        hello.invoke(o, "zhuYaZhu");
//        4. 调用私有的静态方法
//        4.1 得到say方法对象
        Method say = userClass.getDeclaredMethod("say", String.class, int.class, char.class);
//        4.2 私有 —— 暴破
        say.setAccessible(true);
//        4.3 调用
        System.out.println(say.invoke(o, "zyz", 250, '男'));
//        因为静态所以可以用null
        Object reValue = say.invoke(null, "zhuYaZhu", 100, '男');
//        运行类型还是String(与方法定义的一致),但返回时还是编译类型Object接收
        System.out.println("运行类型:" + reValue.getClass()); // 
    }
}

class User {
    public String name;
    private static int age;

    public User() {
    }

    private static String say(String n, int a, char c) { // 私有的静态方法
        return n + "的年龄:" + a + "————" + c;
    }

    public void hello(String name) { // 普通方法
        System.out.println("hello" + name);
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

章节练习

  • 通过反射修改私有成员变量,前提:

    class PrivateTest {
        private String name = "zyz";
        public String getName(){
            return name;
        }
    }
    
    • 解:
    public class test {
        public static void main(String[] args) throws Exception {
            Class<?> cls = Class.forName("com.zyz.PrivateTest");
            Object o = cls.newInstance();
            Method getName = cls.getDeclaredMethod("getName");
            System.out.println(getName.invoke(o));
    
            Field name = cls.getDeclaredField("name");
            name.setAccessible(true);
            name.set(o, "gaiZyz");
            System.out.println(getName.invoke(o));
        }
    }
    
  • 利用反射和 File 完成以下功能

    • 利用 Class 类的 forName 方法得到 File 类的 class 对象
    • 在控制台打印 File 类的所有构造器
    • 通过 newInstance 的方法创建 File 对象,并创建 E:\mynew.txt 文件
    public class test {
        public static void main(String[] args) throws Exception {
            //        1. 得class对象
            Class<?> cls = Class.forName("java.io.File");
            Constructor<?>[] declaredConstructors = cls.getDeclaredConstructors();
            for (Constructor<?> declaredConstructor : declaredConstructors) {
                System.out.println("File 的构造器:" + declaredConstructor);
                //            getName 后就都是 java.io.File 了
            }
            //        单独的得到自己要用的构造器:public java.io.File(java.lang.String)
            //        2. 拿要用的构造器
            Constructor<?> declaredConstructor = cls.getDeclaredConstructor(String.class);
            String filePath = "E:\\mynew.txt"; // 内存
            //        3. 构造器拿到File对象
            Object file = declaredConstructor.newInstance(filePath); // 通过有参构造器进行实例化 —— 创建File对象
            //        4. 拿对象中要用的方法
            Method createNewFile = cls.getDeclaredMethod("createNewFile");
            //        5. 调用方法
            createNewFile.invoke(file);
    
            //        注意创建文件的正常写法:
            //        File file = new File("E:\\mynew.txt"); // 内存
            //        file.createNewFile(); // 真正创建了文件的方法
        }
    }