Fork me on Gitee

常犯指数3颗星-泛型序列化

泛型、反射、编译优化

实现了Serializable接口缺报错怎么办?

序列化和反序列化

  • 序列化:将对象写入到IO流中
  • 反序列化:从IO流中恢复对象

Serializable接口:是一个标记接口,不用实现任何方法,标记当前类对象是可以序列化的

序列化需要考虑哪些问题?

  • 子类实现序列化接口,父类没有实现,那么,子类可以序列化吗?

只要父类实现了无参构造函数,子类可以序列化

  • 类中存在引用对象,那么,这个类对象在什么情况下可以实现序列化呢?

类中所有的对象都是可序列化的,才可以实现序列化

  • 同一个对象多次序列化(之间有属性更新),会影响序列化吗?

会影响到序列化

泛型不仅仅是规定集合中的类型那么简单

  • 什么是泛型

泛型就是参数化类型,就是将类型由原来的具体的类型参数化

  • 为什么要使用泛型

能够在不创建新类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型

类型擦除:编译期间,编译中不包含类型信息

 private static void easyUse() throws Exception {

//        List<String> left = new ArrayList<>();
//        List<Integer> right = new ArrayList<>();
//
//        System.out.println(left.getClass());
//        System.out.println(left.getClass() == right.getClass());

//        if(left instanceof ArrayList<String>){
//
//        }

        // 正确写法
//        if(left instanceof ArrayList){
//
//        }
//        if(left instanceof ArrayList<?>){
//
//        }
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.getClass().getMethod("add",Object.class).invoke(list,"abcd");
        list.getClass().getMethod("add",Object.class).invoke(list,"1.2");

        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    }

运行结果: 对于left.getClass()会返回ArrayList,对于两个类型是否相等,自然会返回true,因为都是ArrayList。利用反射在运行期间塞入不同类型的值,是可以通过成功输出的。原因就在泛型参数类型擦除

image-20230619000750627

  • 泛型是先检查在编译的

如下图,在编译前先检查已经出现报错了

image-20230619001410099

  • 泛型不支持继承
private static void genericityCanNotExtend(){
        // 第一类错误 引用传递不支持继承
//        ArrayList<String> first = new ArrayList<Object>();
//
//        ArrayList<Object> list1 = new ArrayList<>();
//        list1.add(new Object());
//        ArrayList<String> list2 = list1;

        // 第二类错误
        ArrayList<Object> second = new ArrayList<String>();

        ArrayList<String> list1 = new ArrayList<>();
        list1.add(new String());
        ArrayList<Object> list2 = list1;


}

image-20230619002122502

  • 泛型类型变量不能是基本数据类型
/**
  * 泛型类型变量不能是基本数据类型: 类型擦除后是Object类型,不能存储int数据类型
  */
private static void baseTypeCanNotUseGenericity(){
    List<int> invalid = new ArrayList<>();

}
  • 泛型的参数类型只能是类类型
 /**
   * 泛型的类型参数只能是类类型,不能是简单类型
   */
private static <T> void doSomeThing(T... values){
    for(T value:values){
        System.out.println(value);
    }
}

举例:泛型类相参数 分别是 类类型和 简单类型

 /**
   * 泛型的类型参数只能是类类型,不能是简单类型
   */
private static <T> void doSomeThing(T... values){
    for(T value:values){
        System.out.println(value);
    }
}

    public static void main(String[] args) throws Exception {
        Integer [] inst1 = new Integer[]{1,2,3};
        int[] inst2 = new int[]{1,2,3};

        doSomeThing(inst1);

        System.out.println("---------------");
        doSomeThing(inst2);
    }

输出结果:类类型被正确打印,而简单类型将数组整体作为泛型类型值打印。

image-20230619003020794

切记不要使用原始类型,这可能会出现灾难性的后果

原始类型可用的场景和坑

image-20230620233342869

  • 对于原始类型给出优化建议

初步优化 : 使用List 至少明确告诉编译器,容器可以存放任意类型的对象

进一步优化:明确的指出具体的类型,把类型检查的工作交给编译器来完成,List

未进行优化前

/**
  * 简单使用原始类型
  */
private static void simpleExample(){
    List data = new ArrayList<>();
    data.add("qinyi");
    data.add(19);
    data.add("Hello Imooc");

    //        data.forEach(
    //                System.out::println
    //        );

    //        data.forEach(
    //                d ->{
    //                    if(((String) d).equals("Hello Imooc")){
    //                        System.out.println(data.indexOf(d));
    //                    }
    //                }
    //        );
    data.forEach(d->{
        if(d instanceof String && ((String) d).equals("Hello Imooc")){
            System.out.println(data.indexOf(d));
        }
    });
}

调用该方法,有可能会因为类型转换问题报异常,本质还是因为未加泛型而使用的原始类型

优化后的代码

 /**
   * 优化使用原始类型
   */
private static void optimizeUse(){
    //        List<Object> data = new ArrayList<>();
    //        data.add("qinyi");
    //        data.add(19);
    //        data.add("Hello Imooc");
    //
    //        data.forEach(
    //                System.out::println
    //        );
    List<People> data = new ArrayList<>();

}

并不是所有的字符串拼接都使用StringBuilder

使用 "+"去拼接字符串,会造成怎样的性能拼接

  • 空间浪费:每次拼接的结果都需要创建新的不可变类
  • 时间浪费:创建的新不可变类需要初始化,产生大量临时对象,影响young gc,甚至是full gc

image-20230620235214501

举例

private static void easyContact(){
        String userName = "Qinyi";
        String age = "19";
        String job = "Developer";

        String info = userName + age +job;
        System.out.println(info);
}
/**
  * 隐式使用StringBuilder
  * @param values
  */
private static void implicitUseStringBuilder(String[] values){
    String result = "";
    for (int i = 0; i < values.length ; i++) {
        result +=values[i];
    }
    System.out.println(result);
}

可以将该类编译后,查看字节码文件

# 编译
javac StringContact.java
# 查看字节码文件
javap -l -c -p StringContact.class

可以看到编译器编译时 隐式调用了StringBuilder对象

image-20230621002729109

/**
  * 显式使用StringBuilder
  * @param values
  */	
private static void explicitUseStringBuilder(String[] values){
        StringBuilder result = new StringBuilder();
        for (int i = 0; i < values.length ; i++) {
            result.append(values[i]);
        }
        System.out.println(result.toString());

    }

image-20230621003010260

11行,new StringBuilder 对象 。for 之内不断调用StringBuilder append方法。

StringBuffer和StringBuilder的三个区别

image-20230621001031790

线程安全原因 是因为StringBuffer中用到了大量的synchronized

缓冲区 以 二者ToString源码为例,StrtingBuffer中用到了toStringCache,String Builder只是new String

image-20230621001807993

image-20230621001823268

分不清深浅拷贝,稍不留神就会吃亏

什么是深浅拷贝

image-20230621191007908

Cloneable接口

从 JVM的角度来看,Cloneable就是一个标记接口,它自身并没有定义任何的方法签名,clone方法是定义在Object中的,不要混淆。

如果类没有实现Cloneable接口,直接调用Object的Clone方法,会抛出异常。

Object提供的clone方法是浅拷贝

image-20230621191709276

浅拷贝案例

worker类

@Data
public class Worker implements Cloneable {
    private String name;
    private Integer age;
    private String gender;

    private EducationInfo educationInfo;

    public Worker(String name, Integer age, String gender, String school,String time) {
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.educationInfo = new EducationInfo(school, time);
    }

    @Override
    protected Object clone() {
        try{
            return super.clone();
        }catch (CloneNotSupportedException e){
            return null;
        }

    }

Education类

@Data
@AllArgsConstructor
public class EducationInfo implements Cloneable {
    private String school;
    private String time;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

测试方法

/**
  * 浅拷贝测试
  */
private static void copyTest(){
    Worker worker1 = new Worker("Qinyi",10,"m","大连理工","2000");
    System.out.println("原始对象:"+worker1.getEducationInfo().getSchool());

    Worker worker2= (Worker)worker1.clone();
    System.out.println("拷贝对象:"+worker2.getEducationInfo().getSchool());


    worker2.getEducationInfo().setSchool("同济大学");
    System.out.println("原始对象:"+worker1.getEducationInfo().getSchool());
    System.out.println("拷贝对象:"+worker2.getEducationInfo().getSchool());

}

运行结果: 修改了worker2的school属性,worker1 原始对象的school属性也随之发生变化。

image-20230621194400739

实现深拷贝的几种方式

  • 构造函数

    将worker中的clone方法用构造函数进行重写

@Override
public Object clone(){
    //第一种方式实现深拷贝
    Worker worker = new Worker(name,age,gender, educationInfo.getSchool(), educationInfo.getTime());
    return worker;
}

运行结果:

image-20230621194921416

  • 重载clone()方法
 try{
     Worker worker = (Worker)super.clone();
     worker.educationInfo = (EducationInfo) educationInfo.clone();
     return worker;
 }catch (CloneNotSupportedException ex){
     return null;
 }

运行结果同上,完成深拷贝。

  • 利用序列化完成深拷贝。Education和Worker都将实现 Cloneable接口改为实现Serializable。

worker中的clone方法

	/**
     * 利用序列化完成深拷贝,序列化属于深拷贝,确保引用对象可序列化
     * @return
     */
    @Override
    public Worker clone(){
        Worker worker = null;
        try{
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(this);

            //将流序列化成对象
            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bais);
            worker = (Worker)ois.readObject();

        }catch (IOException  | ClassNotFoundException e){
            e.printStackTrace();
        }
        return worker;
    }

运行结果同上,完成深拷贝。

总结

深拷贝方法 优点 缺点
构造函数 1.底层实现简单2.不需要引入第三方包3.系统开销小4.对拷贝类没有要求,不需要实现额外的接口和方法 1.可用性差,每次新增成员变量都需要改写拷贝构造函数
重载Clone方法 1.底层实现简单2.不需要引入第三方包3.系统开销小 1.可用性较差。每次新增成员变量可能需要改写clone()方法。2.拷贝类(包括成员变量)需要实现Cloneable接口
序列化方式 1.可用性强,新增成员变量不需要修改拷贝方法 1.底层实现复杂。2.拷贝类需要实现Serializable接口3.序列化和反序列化存在一定的系统开销

posted @ 2023-06-19 00:33  shine-rainbow  阅读(12)  评论(0编辑  收藏  举报