Java反射的具体应用(实现对象属性拷贝功能)

目录

 

什么是反射

如何获取字节码对象

反射API学习

实战:完成对象拷贝功能


什么是反射

反射就是在程序运行的过程中,我们可以通过一个类的字节码对象(Class对象),剖析出这个类的一切细节,知道他有哪些属性、方法、构造函数,甚至还可以动态的实例化一个对象。

Class对象是什么?

Java在经过编译后,会生成字节码文件(.class文件)。例如,Student.java编译生成了Student.class。

我们在用到这个类时,比如new一个Student(或者用到了它的静态方法),就会把Student.class加载到内存,读取他的信息,完成对象的实例化。

同时,java会把类的具体信息封装成一个Class类型的对象。一个类它能有啥信息?

属性、构造函数、方法、访问修饰符、也可能会被加上注解。。。。。。等等。

点进Class类,找到了一个内部类,确实是上述的那些东西。只不过字段、方法、构造函数这些比较复杂,想要描述他们只靠简单的字符串是不够的,还需要进一步封装成对象。

这些信息都被抽象出来,封装进了Class类,并提供了一套api供我们使用。

这个api就是反射api,使用这个api来获取类信息、创建实例的过程就叫做反射。

所以说,往简单点想,反射也没那么复杂,说到底还是调用api。只不过我们能用这个api做很多有趣的事,完成一些通过常规手段无法完成的任务。

如何获取字节码对象

三种方式

    public static void main(String[] args) {
        //通过类获取
        Class<Student> studentClass1 = Student.class;
        
        //通过对象获取
        Student student = new Student();
        Class<? extends Student> studentClass2 = student.getClass();
        
        //Class.forName加载
        try {
            Class<?> studentClass3 = Class.forName("com.dayrain.entity.Student");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

 

springframework包下有一个工具类叫BeanUtils,里面有一个实际项目中经常用到的方法:

BeanUtils.copyProperties(obj1, obj2);

该方法可以完成任何对象之间的属性拷贝。因为这个方法太常用了,久而久之就容易忽略其中的原理。

我们点进源码

发现参数的类型是Object,也就是说它不关心对象的具体类型。在无视类型的条件下实现这一功能,正是因为用了反射技术。

今天我们通过反射来实现一个简易的copyProperties。

反射API学习

反射本身并不复杂,我们可简单学习一下常用的api。

除此之外,有一些方法的作用容易混淆

getMethod():获取自身能用所有的公共方法。1.类本身的public 2.继承父类的public 3.实现接口的public

getDeclaredMethod():获取类自身声明的所有方法。
public static void main(String[] args) throws Exception {
        Class<Student> studentClass = Student.class;
        Student student = new Student();
        student.setName("小明");
        student.setAge(18);
        student.setAddress("地址");
        student.setIdCard("1234567123xyz");
        student.setGender(1);
        student.number = "1000001";

        //1、获取属性

        //获取公有属性
        Field[] fields = studentClass.getFields();
        for (Field field : fields) {
            //输出所有的变量名
            System.out.println(field.getName());
        }

        //获取所有的属性(公有、私有)
        Field[] declaredFields = studentClass.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            //获取属性名
            System.out.println(declaredField.getName());
            //获取属性类型的字节码对象,例如 class java.lang.String、
            System.out.println(declaredField.getType());
            //获取属性的访问修饰符,返回的是数字,0表示default,1表示public, 2表示private
            System.out.println(declaredField.getModifiers());
            //获取泛型信息
            System.out.println(declaredField.getGenericType());
            //获取注解类型
            System.out.println(Arrays.toString(declaredField.getAnnotations()));
            System.out.println("----------------------分割线--------------------");
        }

        //改变私有属性的值
        Field age = studentClass.getDeclaredField("age");
        //将访问权限改为可访问
        age.setAccessible(true);
        age.set(student, 20);
        System.out.println("现在的学生信息为: " + student.getAge());


        System.out.println("---------------------下面是类的方法信息----------------------");

        //2、获取方法信息
        Method[] methods = studentClass.getMethods();
        for (Method method : methods) {
            //方法名
            System.out.println(method.getName());
            //返回值类型
            System.out.println(method.getReturnType());
            //获取修饰符
            System.out.println(method.getModifiers());
            //获取注解信息
            System.out.println(Arrays.toString(method.getDeclaredAnnotations()));

            System.out.println("-------------------分割线--------------------");
        }

        //调用方法
        //如果是重载方法,需要指定参数类型
        Method method = studentClass.getMethod("introduceYourSelf");
        //如果是私有方法
        method.setAccessible(true);
        method.invoke(student);


        //3、获取构造信息
        for (Constructor<?> declaredConstructor : studentClass.getDeclaredConstructors()) {
            //获取参数, 参数做了进一步封装,有名字,访问控制符等
            Parameter[] parameters = declaredConstructor.getParameters();
            //获取构造方法的访问控制符
            int modifier = declaredConstructor.getModifiers();
        }

        //5、判断对象的类型
        //注解
        boolean annotation = studentClass.isAnnotation();
        //匿名类
        boolean anonymousClass = studentClass.isAnonymousClass();
        //数组
        boolean array = studentClass.isArray();
        //枚举
        boolean anEnum = studentClass.isEnum();
        //接口
        boolean anInterface = studentClass.isInterface();
        //局部类
        boolean localClass = studentClass.isLocalClass();
        //内部类
        boolean memberClass = studentClass.isMemberClass();

        //6、创建对象
        Student student1 = studentClass.newInstance();
    }

Student类

public class Student {
    public String number;

    private String name;

    private int age;

    private String address;

    private String idCard;

    private int gender;

    public String introduceYourSelf() {
        return "我叫:" + name + "年龄是:" + age + "地址是:" + address;
    }


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getIdCard() {
        return idCard;
    }

    public void setIdCard(String idCard) {
        this.idCard = idCard;
    }

    public int getGender() {
        return gender;
    }

    public void setGender(int gender) {
        this.gender = gender;
    }

    @Override
    public String toString() {
        return "Student{" +
                "number='" + number + '\'' +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", address='" + address + '\'' +
                ", idCard='" + idCard + '\'' +
                ", gender='" + gender + '\'' +
                '}';
    }
}

实战:完成对象拷贝功能

需求:完成两个对象之间的属性拷贝,只针对变量名、类型(int、float......)和访问控制符都相同的变量。包装类和基本类型视作同类型。

分析:

实现这个功能可能有哪些步骤,我们需要理清:

1、获取两个类的属性对象

2、在操作属性的时候,考虑到有私有属性,所以要先破解属性

3、获取属性对象的修饰符、名称、类型

4、比对,如果满足变量名、类型、访问控制符都相同,则进行值拷贝。

实现:

public class BeanUtilsTest {

    private static void copyProperties(Object source, Object target) throws BeansException, IllegalAccessException {
        //获取各自的字节码对象
        Class<?> sourceClass = source.getClass();
        Class<?> targetClass = target.getClass();

        Field[] sourceFields = sourceClass.getDeclaredFields();
        Field[] targetFields = targetClass.getDeclaredFields();
        for (Field sourceField : sourceFields) {
            //破解访问权限
            sourceField.setAccessible(true);
            //获取变量名
            String name = sourceField.getName();
            //获取访问修饰符
            int modifiers = sourceField.getModifiers();

            for (Field targetField : targetFields) {
                //校验:变量名、访问修饰符、类型
                if(name.equals(targetField.getName()) && modifiers == targetField.getModifiers() && typeCheck(sourceField, targetField)) {
                    //获取变量值
                    Object value = sourceField.get(source);
                    targetField.setAccessible(true);
                    targetField.set(target, value);
                }
            }
        }

    }

    //判断类型是否一致
    private static boolean typeCheck(Field sourceField, Field targetFiled) {
        /**只需要比较前三个字母
         * Byte byte
         * Short short
         * Integer int
         * Long long
         * Float float
         * Double double
         * Boolean bool
         * Character char
         */
        Class<?> sourceType = sourceField.getType();
        Class<?> targetType = targetFiled.getType();
        String sourceTypeName = sourceType.getSimpleName().toLowerCase().substring(0, 3);
        String targetTypeName = targetType.getSimpleName().toLowerCase().substring(0, 3);
        return sourceType.equals(targetType) || sourceTypeName.equals(targetTypeName);
    }

}

 

基本类型是获取不到字节码对象的,例如int.class(),但是包装类有 Integer.class。所以如果想把基本类型和包装类型视为相同,则需要进行进一步的处理(typeCheck)。

getSimpleName()是获取类型名的缩写,例如java.lang.Byte会缩写成Byte。

另外一个测试类也贴一下:

 

StudentVO类

public class StudentVO {

    public String number;

    private String name;

    private Integer age;

    private String address;

    private String gender;

    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    @Override
    public String toString() {
        return "StudentVO{" +
                "number='" + number + '\'' +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", address='" + address + '\'' +
                ", gender='" + gender + '\'' +
                '}';
    }
}

项目中业务写的比较多,直接用到反射的场合不是很多,基本都是间接用(因为有各种方便的框架替我们完成了很多复杂的工作!)。

之前有个需求,实现操作日志功能,用户每点击一次界面、调用一个api,都需要记录下来。用到了自定义注解和反射,有时间也贴出来。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值