反射机制(反射Method、Constructor、Field)

85 阅读9分钟

我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第2篇文章,点击查看活动详情

一:反射Field

1. 获取Field(属性)

首先调用Class.forName("完整类名"),获取整个类;对于类也可以调用getName()方法获取完整类名,或者调用getSimpleName()方法获取简单类名

(1)调用getFields()方法,获取类中所有的public(公开的)修饰的Field(属性),返回的是一个Field数组;在调用getName()方法,得到属性的名字!

(2)调用getDeclaredFields()方法,获取所有的Field(属性),得到这个属性以后,就可以得到:属性的修饰符列表、属性的类型、属性的名字

(3)调用getModifiers()方法,返回的是一个代号是int类型;然后在调用Modifier.toString()静态方法,把int传进去,就可以转换成字符串;得到属性的修饰符列表

(4)调用getType()方法返回的是一个Class类型,然后在调用getSimpleName()方法得到String类型,得到属性的类型

(5)直接调用getName()方法,返回一个String类型,就能得到属性的名字  

package com.bjpowernode.java.reflect;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

// 反射Student类中的所有Field
public class ReflectTest06 {
    public static void main(String[] args) throws Exception{
        // 获取整个类
        Class studentClass = Class.forName("com.bjpowernode.java.reflect.Student");

        // 补充:studentClass也有getName()方法
        String studentName = studentClass.getName();
        System.out.println("完整类名:"+studentName); // com.bjpowernode.java.reflect.Student
        String simpleName = studentClass.getSimpleName();
        System.out.println("简单类名"+simpleName); // Student

        // 1、 获取类中所有的public(公开的)修饰的Field(属性)
        Field[] fields = studentClass.getFields();
        System.out.println(fields.length); //1 测试数组中只有1个元素
        // 取出这个Field
        Field field = fields[0];
        // 取出这个Field的名字
        String fieldName = field.getName();
        System.out.println(fieldName); // no(只有这个是public修饰的)

        // 2、获取所有的Field
        Field[] fields1 = studentClass.getDeclaredFields();
        System.out.println(fields1.length); // 4
        // 遍历
        for(Field field1 :fields1){
            // 2.1 获取属性的修饰符列表
            // 用Modifiers()而不是Modifier,因为修饰符可能有多个
            // 返回的是一个整型数字,每个数字是修饰符的代号
            // 然后调用静态方法Modifier.toString(int) 把数字准换成对应的修饰符字符串
            int i = field1.getModifiers();
            String modifier = Modifier.toString(i);
            System.out.println(modifier);

            //2.2 获取属性的类型,field1.getType()返回的是一个Class
            Class fieldType = field1.getType();
            String fName = fieldType.getSimpleName();
            System.out.println(fName); // int  String int boolean

            //2.3 获取属性的名字, field1.getName()返回的是一个String
            System.out.println(field1.getName()); //no name  age  sex

        }

    }
}

// 反射属性
class Student{
    // 4个Field,分别采用了不同的访问控制权限修饰符
    public int no; // Field对象
    private String name; // Field对象
    protected int age;
    boolean sex;
    public static final double MATH_PI = 3.1415926; // 修饰符有多个
}


2. 反编译Field(了解)

通过反射机制,反编译一个类的属性Field;知道类的完整类名,就可以反编译该类的信息

package com.bjpowernode.java.reflect;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

// 反编译Field
public class ReflectTest07 {
    public static void main(String[] args) throws Exception {
       // 创建字符串拼接
       StringBuilder s = new StringBuilder();

       // 获取整个类
        Class studentClass = Class.forName("com.bjpowernode.java.reflect.Student");

        // 字符串拼接
        //s.append("public class Student{");
        // 采用动态获取的方式
        s.append(Modifier.toString(studentClass.getModifiers()) + 
        " class " + studentClass.getSimpleName() + "{\n");

        // 获取所有的属性
        Field[] fields = studentClass.getDeclaredFields();
        for (Field field :fields){
            // 追加修饰符
            s.append("\t");
            s.append(Modifier.toString(field.getModifiers()));
            s.append(" ");
            // 追加类型
            s.append(field.getType().getSimpleName());
            s.append(" ");
            // 追加名字
            s.append(field.getName());
            s.append(";\n");

        }

        s.append("}");

        System.out.println(s);

    }
}

3. 通过反射机制访问对象的属性(重点)

(1)怎么通过反射机制访问一个java对象的属性?
给属性赋值使用set,获取属性的值使用get

(2)调用c.getDeclaredFields()方法,返回的是一个Field[] 数组

          调用c.getDeclaredField(String name)方法,根据属性的名称拿到Field,返回的是一个指定字符串的Field属性;有了指定的属性,就可以调用get和set方法进行修改和取出值了

(3)对于private私有的属性也是可以取到并修改的,调用nameField.setAccessible(true);就可以打破封装

(4)反射机制的缺点: 打破封装,在外部也是可以访问私有private修饰的变量;造成数据不安全

package com.bjpowernode.java.reflect;

import java.lang.reflect.Field;

public class ReflectTest08 {
    public static void main(String[] args) throws Exception {
        // 1. 不使用反射机制,怎么去访问一个对象的属性呢?
        Student s = new Student();
        // 2. 给属性赋值
        s.no = 1111;
        //三要素:给s对象的no属性赋值1111
        // 要素1:对象s    要素2:no属性    要素3:值1111
        // 3.  读属性值
        // 两个要素:获取s对象的no属性的值。
        System.out.println(s.no); // 对象.属性


        // 1.使用反射机制(set get)
            // 获取类
        Class studentClass = Class.forName("com.bjpowernode.java.reflect.Student");
            // 创建对象
        Object obj = studentClass.newInstance(); // Student对象

        // 2.获取no属性(根据属性的名称来获取Field)
        Field noField = studentClass.getDeclaredField("no");
        // 3.给obj对象(Student对象)的no属性赋值
         /*
        虽然使用了反射机制,但是三要素还是缺一不可:
            要素1:obj对象
            要素2:no属性
            要素3:2222值
        注意:反射机制让代码复杂了,但是为了一个“灵活”,这也是值得的。
         */
        noField.set(obj,222);// 给obj对象的no属性赋值2222
        // 4. 读取属性的值
        // 两个要素:获取obj对象的no属性的值。
        System.out.println(noField.get(obj)); // 属性.get(对象)


        // 2.可以反问私有的属性name吗?(需要打破封装才可以访问)
        Field nameField = studentClass.getDeclaredField("name");

        // 打破封装(反射机制的缺点:打破封装,造成数据不安全)
        // 这样设置完之后,在外部也是可以访问私有private的。
        nameField.setAccessible(true);

        // 给name属性赋值
        nameField.set(obj,"zhangsan");
        // 获取name属性的值
        System.out.println(nameField.get(obj)); // zhangsan

    }
}

二:反射Method

1. 可变长度参数

可变长度参数
int... args 这就是可变长度参数
语法是:类型...  (注意:一定是3个点。)

    1、可变长度参数要求的参数个数是:0~N个。
2、可变长度参数在参数列表中必须在最后一个位置上,而且可变长度参数只能有1个
3、可变长度参数可以当做一个数组来看待

package com.bjpowernode.java.reflect;

public class ArgsTest {
    public static void main(String[] args) {
       // 1.不带参数 一个参数 两个参数...都可以正常执行(参数可变)
       m();
       m(100);
       m(10,20);

       m2(100);
       m2(10,"abc");

       m3("abc","def","dd");
       // 里面也可以传一个数组进去
       String[] str = {"aaa","bb","c"};
       m3(str);

    }
    // 带有可变长度的方法
    public static void m(int... args){
        System.out.println("m方法执行了");
    }

    // 2.可变长度参数在参数列表中必须在最后一个位置上,而且可变长度参数只能有1个
    //public static void m2(String... args1,int... args2){}
    public static void m2(int a,String... args){
        System.out.println("m2方法执行");
    }

    // 3.可变长度参数可以当做一个数组来看待
    public static void m3(String... args){
        // args有length属性,说明args是一个数组
        for (int i = 0; i < args.length; i++) {
            System.out.println(args[i]);
        }
    }

}

2. 反射Method(了解)

UserServic类

package com.bjpowernode.java.reflect;
// 用户登录类
public class UserService {
    /**
     * 登录方法
     * @param name 用户名
     * @param password  用户密码
     * @return 表示登录成功,false表示登录失败
     */
    public boolean login(String name,String password){
        if("admin".equals(name) && "123".equals(password)){
            return true;
        }
        return false;
    }

    /**
     *  退出系统的方法
     */
    public void logout(){
        System.out.println("退出系统");
    }
}

反射Method代码

和反射Field的方式很相似,有个别的区别:

(1)调用的是getDeclaredMethods()方法,而不是getDeclaredFields()

(2)返回的是一个Method数组,而不是Field数组

(3)获取类型,这里是返回值类型,调用的是getReturnType方法,而不是getType方法

(4)最终多了一项参数类型,调用的是getParameterTypes(),返回的是一个Class数组,然后在调用getSimpleName()方法,就可以拿到对应的参数类型

package com.bjpowernode.java.reflect;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

public class ReflectMethod {
    public static void main(String[] args) throws Exception{
        // 获取类
        Class userServiceClass = Class.forName("com.bjpowernode.java.reflect.UserService");
        // 获取所有的Method
        Method[] methods =  userServiceClass.getDeclaredMethods();
        for(Method method :methods){
            // 1.获取修饰符列表
            int i = method.getModifiers();
            System.out.println(Modifier.toString(i));

            // 2.获取方法的返回值类型
            Class classMethod = method.getReturnType();
            System.out.println(classMethod.getSimpleName());

            // 3.获取方法名
            System.out.println(method.getName()); //获取方法名:login logout

            // 4.获取方法的参数类型(方法的参数可能有多个)
            Class[] parameterTypes = method.getParameterTypes();
            for(Class parameterType : parameterTypes){
                System.out.println(parameterType.getSimpleName());
            }

        }
    }

}

3. 反编译Method(了解)

通过这种方式,可以反编译一个类的方法

package com.bjpowernode.java.reflect;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

public class ReflectMethodTest02 {
    public static void main(String[] args) throws Exception {
        StringBuilder s = new StringBuilder();
        // 获取类
        Class userServiceClass = Class.forName("java.lang.String");

        // 追加
        //s.append("public class UserService{");
        // 采用动态获取的方式
        s.append(Modifier.toString(userServiceClass.getModifiers())+" class "+userServiceClass.getSimpleName()+ "{\n");

        // 获取方法
        Method[] methods = userServiceClass.getDeclaredMethods();
        for(Method method : methods){
            s.append("\t");
            // 追加修饰符
            s.append(Modifier.toString(method.getModifiers()));
            s.append(" ");
            // 追加类型
            s.append(method.getReturnType().getSimpleName());
            s.append(" ");
            // 追加方法名
            s.append(method.getName());
            // 追加参数类型
            s.append("(");
            Class[] parameterTypes = method.getParameterTypes();
            for (Class parameterType :parameterTypes){
                s.append(parameterType.getSimpleName());
                s.append(",");
            }

            // 最后会多一个 , 怎么删除?
            // 方法1:删除指定下标位置上的字符
            s.deleteCharAt(s.length()-1);

            s.append("){}");
            s.append("\n");
        }

        s.append("}");

        System.out.println(s);
    }
}

4. 通过反射机制调用方法(重点)

重点:必须掌握,通过反射机制怎么调用一个对象的方法?

(1)和通过反射机制访问对象的属性很相似,那是通过set和get方法进行修改和读取数据的;而通过反射机制调用方法,是通过invoke方法进行数据填入,用返回值作为数据的读出

(2)反射机制,让代码很具有通用性,可变化的内容都是写到配置文件当中,将来修改配置文件之后,创建的对象不一样了,调用的方法也不同了,但是java代码不需要做任何改动。这就是反射机制的魅力!

package com.bjpowernode.java.reflect;

import java.lang.reflect.Method;

public class ReflectMethodTest03 {
    public static void main(String[] args) throws Exception {
        // 1.不使用反射机制调用方法
        UserService userService = new UserService();
        boolean login = userService.login("admin","123");
        System.out.println(login?"登录成功":"登录失败");

        // 2.使用反射机制来调用一个对象的方法
        // 获取Class
        Class userServiceClass = Class.forName("com.bjpowernode.java.reflect.UserService");
        // 创建对象
        Object obj = userServiceClass.newInstance();
        // 获取方法(通过方法名和参数区分方法)
        Method loginMethod = userServiceClass.
        getDeclaredMethod("login",String.class,String.class);
        // 调用方法
         /*
        四要素:
        loginMethod方法
        obj对象
        "admin","123" 实参
        retValue 返回值
         */
        // invoke调用的意思
         Object retValue = loginMethod.invoke(obj,"admin","123");
         // obj这个对象invoke调用loginMethod方法,传参数
        System.out.println(retValue);
        
    }
}

三:反射Constructor

1. 反射Constructor

构造方法

package com.bjpowernode.java.reflect;

public class Vip {

    int no;
    String name;
    String birth;
    boolean sex;

    public Vip() {
    }

    public Vip(int no) {
        this.no = no;
    }

    public Vip(int no, String name) {
        this.no = no;
        this.name = name;
    }

    public Vip(int no, String name, String birth) {
        this.no = no;
        this.name = name;
        this.birth = birth;
    }

    public Vip(int no, String name, String birth, boolean sex) {
        this.no = no;
        this.name = name;
        this.birth = birth;
        this.sex = sex;
    }

    // 重写一下toString
    @Override
    public String toString() {
        return "Vip{" +
                "no=" + no +
                ", name='" + name + ''' +
                ", birth='" + birth + ''' +
                ", sex=" + sex +
                '}';
    }
}

反编译一个类的Constructor构造方法

package com.bjpowernode.java.reflect;


import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;

public class ReflectConstructorTest01 {
    public static void main(String[] args) throws Exception{
        StringBuilder s = new StringBuilder();

        Class vipClass = Class.forName("com.bjpowernode.java.reflect.Vip");
        //s.append("public class Vip {");
        // 采用动态方式获取
        s.append(Modifier.toString(vipClass.getModifiers())+" class "+vipClass.getSimpleName()+" {\n");

        // 拼接构造方法
        Constructor[] constructors = vipClass.getConstructors();
        for (Constructor constructor :constructors){
            s.append("\t");
            // 追加修饰符
            s.append(Modifier.toString(constructor.getModifiers()));
            s.append(" ");
            // 追加类名
            s.append(vipClass.getSimpleName());
            // 追加参数
            s.append("(");
            Class[] parameterTypes = constructor.getParameterTypes();
            for (Class paramaterType: parameterTypes){
                s.append(paramaterType.getSimpleName());
                s.append(",");
            }
            // 删除最后元素下标位置上的字符
            if(parameterTypes.length>0){
                s.deleteCharAt(s.length()-1);
            }
            s.append("){}\n");
        }

        s.append("}");

        System.out.println(s);
    }
}

/*
public class Vip {
    public Vip(int,String,String,boolean){}
    public Vip(int,String,String){}
    public Vip(int,String){}
    public Vip(int){}
    public Vip(){}
}*/

2. 反射机制调用构造方法

通过反射机制调用构造方法实例化java对象。(这个不是重点)

package com.bjpowernode.java.reflect;

import java.lang.reflect.Constructor;

// 比上一个例子重要
public class ReflectConstructorTest02 {
    public static void main(String[] args) throws Exception {
        //1. 不使用反射机制怎么创建对象
        Vip v1 = new Vip();
        Vip v2 = new Vip(110,"zhangsan","2022-10-11",true);

        //2. 使用法反射机制
        Class c = Class.forName("com.bjpowernode.java.reflect.Vip");
        // 调用无参构造方法
        Object obj = c.newInstance();
        System.out.println(obj);

        // 调用有参构造方法( 区分构造方法只需要形参)
        // 第一步:先获取到这个有参数的构造方法
        Constructor con = c.getDeclaredConstructor(int.class,String.class,String.class,boolean.class);
        // 第二步:调用构造方法new对象、
        Object newobj = con.newInstance(220,"lisi","2022-10-10",true);
        System.out.println(newobj);

        // 调用构造方法,也可以先拿到构造方法
        Constructor con1 = c.getDeclaredConstructor(); // 什么都不传
        System.out.println(con1.newInstance());

    }
}

3. 补充:获取父类和父类的接口(重点)

一个类,怎么获取这个类的父类,已经实现了哪些接口?

package com.bjpowernode.java.reflect;
/*
重点:给你一个类,怎么获取这个类的父类,已经实现了哪些接口?
 */
public class ReflectTest09 {
    public static void main(String[] args) throws Exception{
        // String类举例
        Class stringClass = Class.forName("java.lang.String");

        // 获取String的父类
        Class superClass = stringClass.getSuperclass();
        System.out.println(superClass.getName()); // java.lang.Object

        // 获取String类实现的所有接口(一个类可以实现多个接口)
        Class[] interfaces = stringClass.getInterfaces();
        for (Class inter : interfaces){
            System.out.println(inter.getName());
        }
    }
}