Java 反射reflect 序列化

本菜鸡之前只是听过这几个概念,但是具体是做什么的却不太明白,因此对这几个概念进行了学习,并记录做区分理解,如果您发现其中的错误,请指出,谢谢~

1、反射的概述

Java的反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对任意一个对象,都能够调用他的任意方法和属性;这种 “动态获取信息以及动态调用对象的属性方法” 的功能就是Java的反射机制。

Java文件编译后产生字节码文件 .class,类加载器通过二进制流,从文件系统中加载class文件,在执行程序的时候,将字节码文件读到JVM中,然后在自动在内存中创建一个java.lang.Class对象(一个类只会产生一个Class对象),这个对象会被放入到字节码信息中,这个Class对象就对应加载的字节码信息,这个对象将被作为程序访问方法区中这个类各种数据的一个接口。

那么,反射其实就是通过类的Class对象,反向获取这个类创建的对象的各种信息。

说明:在运行期间如果要产生某个类的对象,JVN会检查该类的Class对象是否已经被加载,如果没有被加载,会根据类的名称找到对应的.class 文件并加载,一旦某个类的Class对象已经被加载,就可以用他来产生该类型的所有对象。

2、Class类

其实也就是利用面向对象的思维将其他的类向上抽取,提炼成的一个类。

如:student类中有他的属性、方法等,computer类也有他的属性、方法等,将他们抽取,形成一个Class类,那么通过Class 类 可以创建 student实例,然后通过student实例对象获得他相应的属性或者方法。

Class 类的实例表示正在运行的 Java 应用程序中的类和接口。也就是jvm中有N多的实例每个类都有该Class对象。(包括基本数据类型)
Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的defineClass 方法自动构造的。也就是这不需要我们自己去处理创建,JVM已经帮我们创建好了。

3、获取Class对象的四种方式

最常用的就是利用Class类的静态方法 forName(“包名加类名组成的字符串”)

package fanshe;

/**
 * 获取Class对象的四种方式
 */
public class testFanshe {
    public static void main(String[] args) throws ClassNotFoundException {
        //第一种方式 ----> 已经有了对象,就不需要进行反射获取对象了,一般不用
        Student student = new Student();
        Class stuClass1 = student.getClass();
        System.out.println(stuClass1);

        //第二种方式 ----> 需要导包,依赖性强,不导包会编译报错
        Class stuClass2 = Student.class;
        System.out.println(stuClass2);

        //第三种方式 ----> 最常用的方式,可以传入字符串(包名加类名),也可以写在配置文件中调用配置之后的字符串
        Class stuClass3 = Class.forName("fanshe.Student");
        System.out.println(stuClass3);

        //第四种方式----->类的加载器(了解即可)
        ClassLoader classLoader = testFanshe.class.getClassLoader();
        Class stuClass4 = classLoader.loadClass("fanshe.Student");
        System.out.println(stuClass4);

        // 可以看出,三种方法创建出来的Class 对象是同一个对象
        System.out.println(stuClass1 == stuClass2);
        System.out.println(stuClass2 == stuClass3);
        System.out.println(stuClass3 == stuClass4);
    }
}

调用了无参的构造方法
class fanshe.Student
class fanshe.Student
class fanshe.Student
class fanshe.Student
true
true
true

4、Class类可以构造的具体实例有哪些?

接口,类(内部类,外部类),注解,数组,基本数据类型,返回值等
 

package fanshe;
public class testInstance {
    public static void main(String[] args) {
        Class c1 = Student.class; // 类
        Class c2 = Runnable.class; // 接口
        Class c3 = Override.class; // 注解
        Class c4 = int.class; // 基本数据类型
        Class c5 = void.class; // 返回值类型
        int[] arr1 = {1, 2, 3};
        int[] arr2 = {3, 4, 5, 6, 7, 8};
        Class c6 = arr1.getClass(); // 数组, 数组只能用 getClass()获取
        Class c7 = arr2.getClass();
        System.out.println(c6 == c7); // true ,同一个维度,同一个元素类型,得到的Class对象就是同一个
    }
}

5、通过反射获取构造方法

首先创建一个Student 方法,包含六个构造器,其中三个public;从代码里可以看出:

  • getConstructors()方法只能获取到public修饰的构造器
  • getDeclaredConstructors() 可以获取到所有的构造器,包括private类型
  • getConstructor(Class<?>... parameterTypes):可变参数构造器,只能是public类型的
    • 传入一个null值/或者不传,可以获取到无参构造器(注意不带s,无参构造器只有一个)
    • 传入对应的parameterTypes,可以获取到相应的构造器
  • getDeclaredConstructor(Class<?>... parameterTypes):可以获取到非public类型的构造器
    • 注意调用私有构造器的时候,要调用declaredConstructor.setAccessible(true);这个方法,不然会有异常。
package fanshe;

import java.lang.reflect.Constructor;

public class testConstructor {
    public static void main(String[] args) throws Exception {

        // 加载Class对象,字符串传值应为真是路径,包名.类名
        Class clazz = Class.forName("fanshe.Student");

        // 获取构造方法
        System.out.println("****************公有的构造方法***********************");
        Constructor[] constructors = clazz.getConstructors();
        for (Constructor c : constructors) {
            System.out.println(c);
        }

        System.out.println("****************所有的构造方法***********************");
        Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
        for (Constructor c : declaredConstructors) {
            System.out.println(c);
        }

        System.out.println("****************公有,无参构造方法***********************");
        Constructor constructor = clazz.getConstructor(null);  //注意方法调用是getConstructor 不是 getConstructors
        System.out.println(constructor);
        Object o = constructor.newInstance();// 通过newInstance()方法创建对象 --->构造方法调用,对象创建成功

        System.out.println("****************公有,有参数构造方法***********************");
        Constructor constructor1 = clazz.getConstructor(char.class);
        System.out.println(constructor1);

        Object o3 = constructor1.newInstance('男');

        System.out.println("****************protected,有参数构造方法***********************");
        Constructor constructor2 = clazz.getDeclaredConstructor(boolean.class); //注意调用方法
        System.out.println(constructor2);
        Object o1 = constructor2.newInstance(true);

        System.out.println("****************private,有参数构造方法***********************");
        Constructor declaredConstructor = clazz.getDeclaredConstructor(int.class);
        System.out.println(declaredConstructor);
        declaredConstructor.setAccessible(true); //不调用这个方法,会抛出异常信息。java.lang.IllegalAccessException
        Object o2 = declaredConstructor.newInstance(18);

    }
}

使用类:

package fanshe;

public class Student {
    public String name;
    protected int age;
    char sex;
    private String phoneNum;

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

    //定义了六个构造方法,三个公有
    public Student() {
        System.out.println("调用了无参的构造方法");
    }
    Student(String str) {
        System.out.println("默认的构造方法+" + str);
    }
    public Student(char name) {
        System.out.println("调用了char的构造方法 +" + name);
    }
    public Student(String name, int age) {
        System.out.println("多个参数的构造方法 : 姓名:" + name + "年龄:" + age);
    }
    protected Student(boolean n) {
        System.out.println("受保护的构造方法 n = " + n);
    }
    private Student(int age) {
        System.out.println("私有的构造方法   年龄:" + age);
    }

    //**************成员方法***************//
    public void show1(String s) {
        System.out.println("调用了:公有的,String参数的show1(): s = " + s);
    }
    protected void show2() {
        System.out.println("调用了:受保护的,无参的show2()");
    }
    void show3() {
        System.out.println("调用了:默认的,无参的show3()");
    }
    private String show4(int age) {
        System.out.println("调用了,私有的,并且有返回值的,int参数的show4(): age = " + age);
        return "abcd";
    }
    public static void show5() {
        System.out.println("静态方法执行了");
    }
}

6、通过反射获取属性

Student类里面定义了四个属性:

  • getFields():获取public中修饰的属性,包含父类的public属性
  • getDeclaredFields():获取的运行时类的所有属性
  • getField(String name):获取指定的public属性,name为属性的命名
  • getDeclaredField(String name):获取指定的属性,name为属性的命名
    • 私有属性要setAccessible方法,并赋值为true
  • 给属性设置的时候,必须要有对象。
package fanshe;

import java.lang.reflect.Field;

public class testFileds {

    public static void main(String[] args) throws Exception {
        //1 获取Class 对象
        Class stuClass = Class.forName("fanshe.Student");

        System.out.println("******************获取所有的公共字段****************");
        //2 获取所有的公共字段  只有一个name
        Field[] fields = stuClass.getFields();
        for (Field field : fields) {
            System.out.println(field);
        }

        System.out.println("******************获取所有字段,包括私有****************");
        //3 获取所有字段,包括私有
        Field[] declaredFields = stuClass.getDeclaredFields();
        for (Field field : declaredFields) {
            System.out.println(field);
        }

        System.out.println("*************获取公有字段**并调用************************");
        Field name = stuClass.getField("name");
        System.out.println(name);

        // 创建一个对象
        Student o = (Student) stuClass.getConstructor().newInstance();
        // 给对象的name 属性赋值
        name.set(o, "zhangSan");
        System.out.println(o.name);

        System.out.println("*************获取私有字段**并调用************************");
        Field age = stuClass.getDeclaredField("age");
        age.set(o, 18);
        Field sex = stuClass.getDeclaredField("sex");
        sex.set(o, 'M');
        Field phoneNum = stuClass.getDeclaredField("phoneNum");
        phoneNum.setAccessible(true); // 私有属性要setAccessible方法,并赋值为true
        phoneNum.set(o, "1234687848");

        System.out.println(o.toString());
    }
}

7、通过反射获取方法

Student类里面定义了五个方法,四种类型一样一个,和一个static类的

  • getMethods():获取类中所有公共方法,包括父类,和继承的接口
  • getDeclaredMethods():获取类中声明的所有方法,只包含本类的声明方法,不包括构造方法
  • getMethod(String name, Class<?>... parameterTypes):第一个参数是方法名,第二个是参数类型
  • getDeclaredMethod(String name, Class<?>... parameterTypes):第一个参数是方法名,第二个是参数类型
package fanshe;

import java.lang.reflect.Method;

public class testMethod {
    public static void main(String[] args) throws Exception {

        // 获取Class 对象
        Class stu = Class.forName("fanshe.Student");

        System.out.println("***************获取所有的”公有“方法*******************");
        System.out.println("***************包含继承过来的公共方法*******************");
        // 获取所有的公共方法
        Method[] methods = stu.getMethods();
        for (Method method : methods) {
            System.out.println(method);
        }

        System.out.println("***************获取所有的”声明“方法*******************");
        System.out.println("***************只包含本类的声明方法,不包括构造方法*******************");
        Method[] declaredMethods = stu.getDeclaredMethods();
        for (Method method : declaredMethods) {
            System.out.println(method);
        }

        System.out.println("***************获取公共方法show1*******************");
        Method show1 = stu.getMethod("show1", String.class);
        System.out.println(show1);

        Student o = (Student) stu.getConstructor().newInstance();
        // o.show1("sss");
        show1.invoke(o, "zhangSan");

        System.out.println("***************获取私有方法show4*******************");
        Method show4 = stu.getDeclaredMethod("show4", int.class);
        System.out.println(show4);
        show4.setAccessible(true); // 通过这个设置可以调用私有方法
        show4.invoke(o, 18);

        Method show5 = stu.getDeclaredMethod("show5");
        show5.invoke(null, null);// 因为方法是静态的,所以第一个传参可以为null,因为是无参函数,所以第二个参数为null
    }
}

8、通过反射获取其他的信息

  • Class superClass = stuClass1.getSuperclass(); //调用父类的信息
  • Package aPackage = stuClass1.getPackage(); // 调用当前类的包
  • Annotation[] annotations = stuClass1.getAnnotations(); //调用当前类的注解,只能获取RUNTIME类型的注解,不能获取SOURCE 类型的
  • Class[] interfaces = stuClass1.getInterfaces();//调取当前类的接口

9、反射是否破坏了面向对象的封装性?

封装是指对外隐藏对象的属性和实现细节,仅仅对外提供公共的访问方式,私有的方法其实是为了让其他的类无法直接使用这个方法,因为可能会实现不了想要的功能;通过反射,虽然是可以获取私有属性和私有方法的,但是,通过反射调用私有方法后,这个方法仍然是私有的,仍然在子类中不可见,从这个角度而言,其实封装性并没有被破坏,而且在写代码时也没有必要去反射私有方法。(仅仅展示了反射功能的强大)

10、反射创建对象的效率高还是new 创建对象的效率高?

通过new 创建的效率高,因为通过反射创建对象时,首先要查找类资源,使用类加载器,过程相对繁琐,效率低。

11、反射的优缺点?

  • 优点:反射可以动态的创建对象和编译,最大程度的发挥了Java的灵活性
  • 缺点:对性能有影响,通过反射创建对象时,首先要查找类资源,使用类加载器,过程相对繁琐,效率低。

12、哪些地方会用到反射机制?

  • JDBC连接数据库的时候使用Class.forName()通过反射加载数据库的驱动程序;
  • Spring框架也用到很多反射机制,最经典的就是xml的配置模式。Spring 通过 XML 配置模式装载 Bean 的过程:
    • 将程序内所有 XML 或 Properties 配置文件加载入内存中; 
    • Java类里面解析xml或properties里面的内容,得到对应实体类的字节码字符串以及相关的属性信息; 
    • 使用反射机制,根据这个字符串获得某个类的Class实例; 
    • 动态配置实例的属性;

13、对象流,ObjectInputStream 和 ObjectOutputStream

用于存储和读取基本数据类型或者对象的处理流,他的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。

java中的ObjectInputStream和ObjectOutputStream的使用_我自是年少韶华倾负的博客-CSDN博客

14、Java中的序列化和反序列化

序列化原版意图是希望对一个Java对象做一下“变换”,变成字节序列,这样一来方便持久化存储到磁盘中,避免程序运行结束后对象就从内存中消失,另一个就是变成字节序列也更加有利于在网络中的传输。

  • 序列化:将Java对象转换为字节序列
  • 反序列化:将字节序列恢复为原来的Java对象

说到序列化,就会引出Serializable接口,如果不实现Serializable接口,那么就会抛出一个NotSerializableException;但是其实Serializable接口是一个空的接口,没有任何的实现,因为Serializable接口其实只是作为一个标记,他告诉代码,只要是实现了Serializable接口的类都是可以被序列化的,然而真正的序列化动作是不需要靠它来完成。

15、serialVersionUID

 假设现在有一种场景:有一个person类,创建一个person对象,然后对他进行序列化,将对象写入到文件中,然后反序列化的时候发现person类中没有重写toString方法,不符合要求,因此在person类中添加toString方法,再次对之前序列化的文件进行反序列化,发现抛出一个异常:InvalidCastException;

解决方法,就是在类中添加一个序列号,serialVersionUID,序列化版本标识符静态常量。

  • private static final long serialVersionUID=xxxx;
  • serialVersionUID表示类的不同版本之间的兼容性,换句话说,他的目的就是以序列化对象进行版本控制,判断各个版本反序列化时是否兼容
  • 如果没有显示定义这个静态变量,他的值是Java运行时环境根据类的内部细节自动生成的,如果类的实例变量做了修改,serialVersionUID就有可能发生变化,因此要显示的声明。
  • 简单来说,Java序列化的机制就是通过在运行时判断serialVersionUID来验证版本的一致性的,反序列化的时候,JVM会把传来的字节流中的serialVersionUID鱼本地相应的实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常(InvalidCastException)

16、不会被序列化的两种特殊情况

  1. 被static修饰的字段:序列化保存的是对象的状态而不是类的状态,因此会忽略掉static静态域
  2. 被transient修饰的字段:序列化某个类对象的时候,如果不希望某个字段被序列化(如密码等),就可以用transient修饰符进行修饰,比如 private transient String password;那么序列化类对象的时候,password字段就会设置为默认值null。

17、序列化的一些细节

  1. 被序列化的类的内部所有属性都必须是可序列化的(基本数据类型是可序列化的,如果类中包含了其他类,那么被包含的类也要实现Serializable接口)
  2. static,transient修饰的字段不可序列化(见16)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值