Java基础知识

Java基础速览

基础知识

整数拓展

​ 进制:二进制0b 十进制 八进制0 十六进制0x

类型转换

​ byte,short,char->int->long->float->double优先级由高到低

​ 当运算时候,有一个变量是long类型,则结果就是long类型,否则就是

import com.sun.xml.internal.ws.api.model.wsdl.WSDLOutput;
import org.w3c.dom.ls.LSOutput;

import java.sql.SQLOutput;

public class changetype {
        public static void main(String[] args){
            int i=128;
            byte b= (byte)i;//强制转换,128>127内存溢出,由低到高不用强转
            double c = i;
            System.out.println(i);
            System.out.println(b);
            //不能对布尔类型进行转换
            //不能把对象类型转换为不相干的类型
            int money =10_0000_0000;
            System.out.println(money);
            int years=222;
            long total = money * years;
            System.out.println(total);//超出了int的范围,即使total用了long,但是结果看int,还是会溢出
            long total1 = money*((long)years);//这样就解决了溢出问题
            System.out.println(total1);
    }
}

变量

type varName [=value] [{,varName}];

//数据类型 变量名 =值 ;可以使用逗号隔开来声明多个同类型的变量(不建议)

类变量

public class var {
    static int a = 0;//属性:类变量,方法中可直接使用
    String str = "hello";//实例变量:从属于对象,使用时需要在方法中声明,如果没有初始化调用默认值
    //布尔类型默认为False
    //除了基本类型,其余默认值为null;
    
    public static void main(String[] args) {
        int i=0;//局部变量
        var var_1 = new var();//将类引过来
        System.out.println(var_1.str);
        System.out.println(a);
    }
}

常量

public class var {
    static final int a = 0;//常量修饰符,两种写法
    final static int b = 1;
    public static void main(String[] args) {
        System.out.println(a);
        System.out.println(b);
    }
}

变量命名规范

所有变量:见名知意

类成员变量:首字母小写和驼峰原则

局部变量:首字母小写和驼峰原则(laseName)

常量:大写字母和下划线:MAX_VALUE

类名:首字母大写和驼峰原则:GoodMan

方法名:首字母小写和驼峰原则:runRun()

运算符

public class yunsuanfu {
    public static void main(String[] args) {
        long a = 123123123123L;
        double e = 123.123;
        int b = 123;
        short c = 10;
        byte d = 8;
        System.out.println(a+b+c+d);得到结果的类型优先级如类型转换中所示
        System.out.println(a+b+c+d+e);
        System.out.println(b+c+d);
        System.out.println(c+d);
    }

}

在这里插入图片描述

当+的旁边出现string类型,将进行拼接

" "+a+b

三元运算符

String type = score <69 ? “不及格”:“及格”

包机制

import file1.file2.*

注释doc

Scanner

package JavaStudy.scanner;

import java.util.Scanner;

public class demo2 {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        double sum =0;
        int m = 0;
        while(scanner.hasNextDouble()){
            double x = scanner.nextDouble();
            m +=1;
            sum += x;
            System.out.println("你输入了第"+m+"个数,当前和为"+sum);
        }
        System.out.println(m+"个数的和为"+sum);
    }
}
//判断字符串是否相等
Scanner scanner = new Scanner(System.in);
String s = Scanner.nextline();
s.equals("hello");

增强型for循环

public class yunsuanfu {
    public static void main(String[] args) {
        //增强型for循环,对于数组
        int[] number = {1,2,3,4,5};
        for(int x:number){
            System.out.println(x);
        }
    }
}

循环的跳转

    public static void main(String[] args) {
        outer:for(int i = 101;i<=150;i++){
        for(int j=2;j<i/2;j++){
            if(i%j==0) {
                continue outer;
            }
        }
            System.out.print(i+" ");
        }
    }

在这里插入图片描述

方法

方法的重载

重载就是在一个类中,有相同的函数名称,但形参不同的函数。

方法的重载规则:

  1. 方法名称必须相同
  2. 参数列表必须不同(个数不同、类型不同、参数排列顺序不同等)
  3. 方法的返回类型可以相同也可以不相同
  4. 仅仅返回类型不同不足以成为方法的重载
可变参数

在方法声明中,在指定参数类型后加一个省略号…

一个方法中只能指定一个可变参数,它必须是方法的最后一个参数。任何普通的参数必须在它声明之前。

public class demo {
    public static void main(String[] args) {
        demo de = new demo();
        de.test(1,2,3,4,4,5);
    }
    public void test(int x,int... i){
        System.out.println(x);
        for(int j=0;j<i.length;j++){
            System.out.println(i[j]);
        }
    }
}
递归

递归包含两个部分:

  1. 递归头:什么时候不用调用自身方法。如果没有头,将陷入死循环。
  2. 递归体:什么时候需要调用自身方法。
值传递和引用传递
    public static void main(String[] args) {
        int i = 1;
        change(i);
        System.out.println(i);
    }
    public static void change(int i){
        i = 2;
    }//输出为1
public class demo01 {
    public static void main(String[] args) {
        Person person = new Person();
        System.out.println(person.name);
        change(person);
        System.out.println(person.name);
    }
    public static void change(Person person){
        person.name = "sexy";
    }

}
class Person {
    String name;
}
输出为:
null
sexy

数组

    public static void main(String[] args) {
        //数组类型
        int[] nums;//首选
        int num[];
        nums = new int[10];
    }

数组的特点:

  1. 其长度是确定的。数组一旦被创建,它的大小就是不可以改变的。
  2. 其元素必须是相同类型,不允许出现混合类型。
  3. 数组中的元素可以是任何类型,包括基本类型和引用类型。
  4. 数组变量属于引用类型,数组也可以看成是对象,数组中的每个元素相当于该对象的成员变量。
  5. 数组本身就是对象,java中对象是在堆中的,因此数组无论保存原始类型还是其他对象类型,数组对象本身是在堆中的

初始化:

  1. 静态初始化 int[] a = {1,2,3} 或者加入有一个类Man, 则Man[] mans = {new Man(), new Man()}
  2. 动态初始化 int[] a = new int[10] ,a[0] = 1,a[1] = 2,…(没有被初始化的值都是0)

打印数组元素:

  1. for(int a:arrays){system.out.println(a)}//(增强型循环)(数组可以用作参数,也可以用作返回)

  2.     public static void main(String[] args) {
            int[] a = {1,2,3,4};
    //        for(int b:reverse(a)){
    //            System.out.println(b);
    //        }
        }
        //反转数组元素
        public static int[] reverse(int[] arrays){
            int[] result = new int[arrays.length];
            for(int i = 0,j = result.length -1;i<arrays.length;i++,j--){
                result[j] = arrays[i];
            }
            return result;
        }
    

Arrays类:

​ Arrays.sort()排序

​ Arrays.toString()供打印

​ Arrays.fill()填充

​ 等等

稀疏数组

稀疏数组可以看做是普通数组的压缩,但是这里说的普通数组是值无效数据量远大于有效数据量的数组

形如:

              0 0 0 0 0 0 0 0 0 0 0
              0 0 1 0 0 0 0 0 0 0 0
              0 0 0 0 2 0 0 0 0 0 0
              0 0 0 0 0 0 0 0 0 0 0
              0 0 0 0 0 0 0 0 0 0 0
              0 0 0 0 0 0 0 0 0 0 0
              0 0 0 0 0 0 0 0 0 0 0
              0 0 0 0 0 0 0 0 0 0 0
              0 0 0 0 0 0 0 0 0 0 0
              0 0 0 0 0 0 0 0 0 0 0
              0 0 0 0 0 0 0 0 0 0 0

其稀疏数组的形式为

              11 11 2
              1  2  1
              2  4  2

它是多行三列的矩阵,第一行记录原矩阵规模(11,11)和有效数个数(2),其余行记录有效数位置和值(行,列,值)

public static void main(String[] args) {
        int[][] array1 = new int[11][11];
        array1[1][2] = 1;
        array1[2][3] = 2;
        System.out.println("输出原始数组");
        for (int[] ints : array1) {
            for (int anInt : ints) {
                System.out.print(anInt+"\t");
            }
            System.out.println();
        }
        //转换为稀疏数组保存
        int sum = 0;
        for(int i = 0;i<array1.length;i++){
            for(int j = 0;j<array1[i].length;j++){
                if(array1[i][j]!=0){
                    sum++;
                }
            }
        }
        System.out.println("有效值的个数为"+sum);
        int[][] array2 = new int[sum+1][3];
        array2[0][0] = 11;
        array2[0][1] = 11;
        array2[0][2] = sum;
        int count = 0;
        for(int i = 0;i<array1.length;i++){
            for(int j = 0;j<array1[i].length;j++){
                if(array1[i][j]!=0){
                    count++;
                    array2[count][0] = i;
                    array2[count][1] = j;
                    array2[count][2] = array1[i][j];
                }
            }
        }
        for(int i = 0;i<array2.length;i++){
            System.out.println(array2[i][0]+"\t"+array2[i][1]+"\t"+array2[i][2]);
        }
        System.out.println("还原为原始矩阵");
        int[][] array3 = new int[array2[0][0]][array2[0][1]];
        for(int i = 1;i<array2.length;i++){
            array3[array2[i][0]][array2[i][1]] = array2[i][2];
        }
        System.out.println("输出还原后的矩阵");
        for (int[] ints : array3) {
            for (int anInt : ints) {
                System.out.print(anInt+"\t");
            }
            System.out.println();
        }
    }

内存分析

java内存:

  1. 堆:存放new的对象和数组,可以被所有的线程共享,不会存放别的对象引用
  2. 栈:存放基本变量类型(会包含这个基本类型的具体数值),引用对象的变量(会存放这个引用在堆里面的地址)
  3. 方法区:可以被所有的线程共享,包含了所有的class和static变量

面向对象

创建与初始化对象

  1. 使用new关键字创建的时候,除了分配内存空间之外,还会给创建好的对象进行默认的初始化以及对类中构造器的调用。

  2. 类中的构造器也称为构造方法,是在进行创建对象的时候必须要调用的。并且构造器有两个特点:1. 必须和类的名字相同。2. 必须没有返回类型,也不能写void;

  3. 默认初始化:

    ​ 数字:0 0.0

    ​ char: u0000

    ​ boolean:false

    ​ 引用:null

public class demo03 {
    //java的构造器。
    String name;
    public demo03(){
        this.name = "cat";
    }//无参构造
    public demo03(String name){
        this.name = "cat";
    }//有参构造:一旦定义了有参构造,无参就必须显示定义!!!
}

快捷键:alt+insert

创建对象内存分析

在这里插入图片描述

封装

高内聚,低耦合:高内聚就是类的内部数据操作细节自己完成,不允许外部干涉。低耦合:仅暴露少量的方法给外部使用

属性私有,get/set

快捷键:alt + insert

作用:

  1. 提高程序的安全性,保护数据
  2. 隐藏代码的实现细节
  3. 统一接口
  4. 系统可维护性增加了

继承

extends(扩展)

类中属性的优先级:public->protected->default->private,继承的只能是public

ctrl+h打开继承树

在Java中,所有的类,都默认直接或者间接继承object类

super

类通过this调用本身的方法,包括protected优先级及以下属性。

子类通过super可以调用父类的方法,包括protected优先级及以下属性。

super()调用父类的构造器,必须放在子类构造器的第一行。不写的话默认调用无参构造器。

注意点:

  1. super调用父类的构造函数,必须在构造方法的第一个
  2. super必须只能出现在子类的方法或者构造方法中
  3. super和this不能同时调用构造方法!

VS this:

  1. 代表的对象不同:this:本身调用这个对象; super:代表父类对象的调用
  2. 前提:this:没有继承也可以使用; super:只能在继承条件才可以使用
  3. 构造方法:this()本类的构造 ; super()父类的构造
重写

父类的引用可以指向子类,父类a指向子类student
在这里插入图片描述
在这里插入图片描述

当修饰符是static时候,即是静态方法的时候不能重写。

在这里插入图片描述
在这里插入图片描述

重写:需要有继承关系,子类重写父类的方法

  1. 方法名必须相同
  2. 参数列表必须相同
  3. 修饰符:范围可以扩大但不能缩小
  4. 抛出的异常:范围,可以被缩小,但不能扩大;

为什么需要重写:

  1. 父类的功能,子类不一定需要,或者不一定满足

  2. 实际应用中,用得最多的一种运行时多态,就是用只自知道父类型(可能是类,更多的可能是接口)的定义,这样只能调用父类型的百方法(这在大型软件里很常用,父类型和子类型可能是不同的人编写的,以利于协作编度程)。

    因此,如果子类型重写了父类型的同名方法,那么只知道父类型的定义就可以调用子类型的方知法了,这体现了软件复用

多态

动态编译:类型:可扩展性

注意事项:

  1. 多态是方法的多态,属性没有多态
  2. 父类和子类,有联系。类型转换异常,ClassCastException
  3. 存在条件:继承关系,方法需要重写,父类引用指向子类对象
  4. static方法,属于类,它不属于实例,不能重写
  5. final 常量,不能重写;
  6. private 方法,不能重写;

instanceof(类型转换) 引用类型,判断一个对象是什么类型,比如object instanceof Student,判断两者是否有父子联系

    public static void main(String[] args) {
        //类型的转换 父与子
        //高                   低
        Person student = new Student();
        //将student这个对象转换为Student类型,然后才可以使用Student类型的方法
        //高转低需要强制转换
        Student student1 = (Student)student;
        student1.run();//可以使用了
        //但子类转换为父类会丢失一些方法!
    }

父类转换为子类,需要强制转换,子类转换父类不用,但可能会丢失一些方法。

static

Static修饰的变量或方法可以直接通过“类.变量或方法"或者直接“变量或方法”的方式来调用。

public class Teacher {
    private static int age = 3;
    public static void main(String[] args) {
        System.out.println(Teacher.age);
        //或者System.out.println(age);
    }
}

类中代码块的执行顺序

public class Teacher {
    {
        System.out.println("匿名代码块");//赋初始值
    }
    static {
        System.out.println("静态代码块");//只执行一次
    }
    public Teacher(){
        System.out.println("构造器");
    }

    public static void main(String[] args) {
        Teacher teacher1 = new Teacher();
        Teacher teacher2 = new Teacher();
    }
}
//输出结果:
静态代码块
匿名代码块
构造器
//第二次输出
匿名代码块
构造器

静态导入包

如:import static java.lang.Math.random;

那么方法中即可以直接使用random();

抽象类

abstract

public abstract class Person {
    //约束,有人帮我们实现
    //抽象方法,只有方法名字,没有方法的实现!
    //不能new这个抽象类,只能靠子类去实现它,约束。
    //抽象类中可以写普通方法
    //抽象方法必须在抽象类中
    public abstract void doSomething();
    public Person(){
        System.out.println("抽象类的构造器");
    }
}

接口

接口:只有规范!自己无法写方法~专业的约束!约束和实现分离:面向接口编程

  1. 接口就是规范,定义的是一组规则,体现了现实世界中“如果你是。。。则必须能。。。”的思想。
  2. 接口的本质是契约,就像人类的法律一样。制定好后大家一起遵守。
  3. OO的精髓,是对对象的抽象,最能体现这一点的就是接口。为什么我们讨论设计模式都只针对具备了抽象能力的语言,就是因为设计模式所研究的,实际上就是如何合理的去抽象。
  4. 声明类的关键字是class,声明接口的关键字是interface。

作用:

  1. 约束
  2. 定义一些方法,让不同的人实现
  3. 里面的方法相当于,public abstract
  4. 常量:public static final
  5. 接口不能被实例化,接口中没有构造方法
  6. implements可以实现多个接口
  7. 必须要重写接口中的方法

抽象类和接口区别(jdl1.8之后接口发生了一些改动,详自搜)

抽象类中可以有普通方法及其实现,抽象类有构造器

接口只有方法不能有实现,相当于都是抽象方法,没有构造器,可以通过implements实现多个接口,必须重写接口中方法

内部类

内部类就是在一个类的内部在定义一个类,比如,A类中定义了一个B类,那么B类相对A类来说就是内部类,而A类对于B类来说就是外部类了

  1. 成员内部类
  2. 静态内部类
  3. 局部内部类
  4. 匿名内部类
public class Outer {
    private int id = 1;
    public void out(){
        System.out.println("这是外部类的方法");
    }
    public class Inner{
        //成员内部类,可以获得外部类的私有属性
        public void in(){
            System.out.println("这是内部类的方法");
        }
        public void getId(){
            System.out.println(id);
        }
    }
    //静态内部类
    public static class Inner1{

    }
    //局部内部类
    public void method(){
        class Inner2{
        }
    }
}
public class Application {
    public static void main(String[] args) {
        //没有名字初始化类,不用将实例保存到变量中,这就是匿名内部类
        new Apple().eat();
    }
}
class Apple{
    public void eat(){
        System.out.println("吃");
    }
}

Java 多态 ——一个案例

最近,发现基础真的hin重要。比如,Java中多态的特性,在学习中就是很难懂,比较抽象的概念。学的时候就犯糊涂,但日后会发现,基础在日常工作的理解中占有重要的角色。

下面,我将用一个代码实例,回忆和巩固多态的概念和存在的意义。理解多态,是面向对象编程的重要一步。

先来个热身,看下图在mian函数调用的时候,1和2的不同。2就是用了多态的思维,这是最最简单的小栗子。an相当于一个引用(类型),但是它可以代表多种状态。

img

多态(概念):

所谓多态,就是指一个引用(类型)在不同的情况下的多种状态。也可以理解为,多态是指通过指向父类的指针,来调用在不同子类中实现的方法。。

场景假设:

一个主人养了猫和狗,猫和狗都有自己爱吃的东西,主人在喂它们的时候,如果既要判断是猫还是狗,再判断他们分别爱吃什么,就显得很麻烦。如果主人养了很多种动物,这样的重复判断,就会浪费很多时间。有什么办法,能让主人拿到一种食物就知道这是哪种动物的,就好了。

一个完整的 代码实例:

1.首先,创造动物类:

// 动物类
class Animal {
    int age;
    String name;

    public int getAge() {
        return age;
    }

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

    public String getName() {
        return name;
    }

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

    // 动物类里面有叫和吃两个方法
    public void cry() {
        System.out.println("我不知道叫什么");
    }

    public void eat() {
        System.out.println("我不知道吃什么");
    }
}

2.其次,分别创造猫类和狗类(他们继承于动物类):

// 狗类继承于动物类
class Dog extends Animal {
    // 覆盖(重写)方法
    public void cry() {
        System.out.println("旺旺");
    }

    public void eat() {
        System.out.println("我是狗,我爱吃骨头");
    }
}

// 猫类继承于动物类
class Cat extends Animal {
    // 覆盖(重写)方法
    public void cry() {
        System.out.println("喵喵");
    }

    public void eat() {
        System.out.println("我是猫,我爱吃鱼");
    }
}

3.再者,创建食物类:

// 食物类
class Food {

    String name;

    public String getName() {
        return name;
    }

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

    // 食物类里面让它有一个方法
    public void showName() {

    }
}

4.再者,猫和狗都有自己不同的爱吃的食物(他们继承于食物类):

// 鱼(食物的一种)继承于食物
class Fish extends Food {
    public void showName() {
        System.out.println("食物:鱼");
    }
}

// 骨头(食物的一种)继承于食物
class Bone extends Food {
    public void showName() {
        System.out.println("食物:骨头");
    }
}

5.主人类(就可以将动物和对应的食物统一起来):

// 主人类 存在一種餵食方法
class Master {
    // 给动物喂食物,如果没有多态,他要写给猫喂食和给狗喂食两个方法
    // 有了多态,以后即使再来好多动物,用这一个函数就可以了
    public void feed(Animal an, Food f) {
        an.eat();
        f.showName();

    }
}

6.最后,方法的调用(测试):

public class DuoTaiDemo {

    public static void main(String args[]) {

        Master master = new Master();
        master.feed(new Dog(), new Bone());

        // hin方便,可以再试试
        master.feed(new Cat(), new Fish());

    }
}

img

异常

异常处理机制

在这里插入图片描述

快捷键:ctrl+alt+t

关键字:try,catch,finally,throw,throws

    public static void main(String[] args) {
        int a = 0;
        int b = 1;
        try{
            System.out.println(b/a);
        }catch (Error e){//想要捕获的异常类型
            System.out.println("程序出现异常0,变量b不能为0");
        }catch (Exception e){
            System.out.println("程序出现异常1,变量b不能为0");
        }catch (Throwable t){
            System.out.println("程序出现异常2,变量b不能为0");
        }
        finally {
            System.out.println("finally");
        }
    }
    public void test(int a,int b){
        if(b ==0){
            throw new ArithmeticException();//主动抛出异常,一般在方法中使用
        }
    }
    public static void main(String[] args) {
        try {
            new Application().test(1,0);
        } catch (ArithmeticException e) {
            e.printStackTrace();
        } finally {
        }
    }
	//假设方法中,处理不了这个异常。方法向上抛出异常throws
    public void test(int a,int b)throws ArithmeticException{
        if(b ==0){
            throw new ArithmeticException();//主动抛出异常
        }
    }

Java中处理异常throw和throws

1.首先我们来了解什么是异常呢?

异常阻止当前方法或作用域继续执行的问题。

2.处理异常

说到处理异常,我们当然会想到 try catch finally

在java中我们会对异常的处理有更高的认识 我们会学习 throw throws等更好的处理异常

3.常见异常

img

4.throw关键字:语句抛出异常 throws关键字:声明异常(方法抛出一个异常)

  1. throw 是语句抛出一个异常。

    语法:throw (异常对象);
    throw e;

  2. throws 是方法可能抛出异常的声明。(用在声明方法时,表示该方法可能要抛出异常) 调用者必须做出处理(捕获或继续抛出)

    语法:(修饰符)(方法名)([参数列表])[throws(异常类)]{…}
    public void doA(int a) throws Exception1,Exception3{…}

  3. throws可以单独使用,但throw不能, throw要么和try-catch-finally语句配套使用,要么与throws配套使用。但throws可以单独使 用,然后再由处理异常的方法捕获。

  4. throw语句用在方法体内,表示抛出异常,由方法体内的语句处理
    throws语句用在方法声明后面,表示再抛出异常,由调用这个方法的上一级方法中的语句来处理,必须做出处理(捕获或继续声明)

  5. throws主要是声明这个方法会抛出这种类型的异常,使其他地方调用它时知道要捕获这个异常,使得提醒必须做出处理。否则编译是不会通过的。
    throw是具体向外抛异常的动作,所以它是抛出一个异常实例。

eg:

public void dothing(int a,int b) throws Exception1,Exception3 {
           try{
                 //......

           }catch(Exception1 e){
              throw e;
           }catch(Exception2 e){
              System.out.println("自己打印提示,不抛出");
           }
           if(a!=b)
              throw new  Exception3("自定义异常");
}

代码块中可能会产生3个异常,(Exception1,Exception2,Exception3)。
如果产生Exception1异常,则捕获之后再抛出,由该方法的调用者去处理。
如果产生Exception2异常,则该方法自己处理了(即打印出字符串:自己打印提示,不抛出)。所以该方法就不会再向外抛出Exception2异常了,void dothing() throws Exception1,Exception3 里面的Exception2也就不用写了(当然你写了也不会报错的),throws 就是声明可能抛出的错误,而Exception2 并未做出抛出操作。
而Exception3异常是该方法的某段逻辑出错,程序员自己做了处理,在该段逻辑错误的情况下抛出异常Exception3,则该方法的调用者也要处理此异常。

集合

原文链接:https://blog.csdn.net/feiyanaffection/article/details/81394745

img

这里写图片描述

img

集合和数组的区别:

这里写图片描述

Collection集合的方法:

这里写图片描述

2.List:
(1)ArrayList:底层数据结构是数组,查询快,增删慢,线程不安全,效率高,可以存储重复元素
(2)LinkedList 底层数据结构是链表,查询慢,增删快,线程不安全,效率高,可以存储重复元素
(3)Vector:底层数据结构是数组,查询快,增删慢,线程安全,效率低,可以存储重复元素

(4小结:

3.Set:
(1)

  • HashSet底层数据结构采用哈希表实现,元素无序且唯一,线程不安全,效率高,可以存储null元素,元素的唯一性是靠所存储元素类型是否重写hashCode()和equals()方法来保证的,如果没有重写这两个方法,则无法保证元素的唯一性。
  • 具体实现唯一性的比较过程:存储元素首先会使用hash()算法函数生成一个int类型hashCode散列值,然后已经的所存储的元素的hashCode值比较,如果hashCode不相等,则所存储的两个对象一定不相等,此时存储当前的新的hashCode值处的元素对象;如果hashCode相等,存储元素的对象还是不一定相等,此时会调用equals()方法判断两个对象的内容是否相等,如果内容相等,那么就是同一个对象,无需存储;如果比较的内容不相等,那么就是不同的对象,就该存储了,此时就要采用哈希的解决地址冲突算法,在当前hashCode值处类似一个新的链表, 在同一个hashCode值的后面存储存储不同的对象,这样就保证了元素的唯一性。
  • Set的实现类的集合对象中不能够有重复元素,HashSet也一样他是使用了一种标识来确定元素的不重复,HashSet用一种算法来保证HashSet中的元素是不重复的, HashSet采用哈希算法,底层用数组存储数据。默认初始化容量16,加载因子0.75。
    Object类中的hashCode()的方法是所有子类都会继承这个方法,这个方法会用Hash算法算出一个Hash(哈希)码值返回,HashSet会用Hash码值去和数组长度取模, 模(这个模就是对象要存放在数组中的位置)相同时才会判断数组中的元素和要加入的对象的内容是否相同,如果不同才会添加进去。
Hash算法是一种散列算法。
Set hs=new HashSet();

hs.add(o);
|
o.hashCode();
|
o%当前总容量 (015)
|
| 不发生冲突
是否发生冲突—————–直接存放
|
| 发生冲突
| 假(不相等)
o1.equals(o2)——————-找一个空位添加
|
| 是(相等)
不添加

覆盖hashCode()方法的原则:
1、一定要让那些我们认为相同的对象返回相同的hashCode值
2、尽量让那些我们认为不同的对象返回不同的hashCode值,否则,就会增加冲突的概率。
3、尽量的让hashCode值散列开(两值用异或运算可使结果的范围更广)
HashSet 的实现比较简单,相关HashSet的操作,基本上都是直接调用底层HashMap的相关方法来完成,我们应该为保存到HashSet中的对象覆盖hashCode()和equals(),因为再将对象加入到HashSet中时,会首先调用hashCode方法计算出对象的hash值,接着根据此hash值调用HashMap中的hash方法,得到的值& (length-1)得到该对象在hashMap的transient Entry[] table中的保存位置的索引,接着找到数组中该索引位置保存的对象,并调用equals方法比较这两个对象是否相等,如果相等则不添加,注意:所以要存入HashSet的集合对象中的自定义类必须覆盖hashCode(),equals()两个方法,才能保证集合中元素不重复。在覆盖equals()和hashCode()方法时, 要使相同对象的hashCode()方法返回相同值,覆盖equals()方法再判断其内容。为了保证效率,所以在覆盖hashCode()方法时, 也要尽量使不同对象尽量返回不同的Hash码值。

如果数组中的元素和要加入的对象的hashCode()返回了相同的Hash值(相同对象),才会用equals()方法来判断两个对象的内容是否相同。

===================================================================================
(2)LinkedHashSet底层数据结构采用链表和哈希表共同实现,链表保证了元素的顺序与存储顺序一致,哈希表保证了元素的唯一性。线程不安全,效率高.

===================================================================================
(3)TreeSet底层数据结构采用二叉树来实现,元素唯一且已经排好序;唯一性同样需要重写hashCode和equals()方法,二叉树结构保证了元素的有序性。根据构造方法不同,分为自然排序(无参构造)和比较器排序(有参构造),自然排序要求元素必须实现Compareable接口,并重写里面的compareTo()方法,元素通过比较返回的int值来判断排序序列,返回0说明两个对象相同,不需要存储;比较器排需要在TreeSet初始化是时候传入一个实现Comparator接口的比较器对象,或者采用匿名内部类的方式new一个Comparator对象,重写里面的compare()方法;

===================================================================================
(4)小结:Set具有与Collection完全一样的接口,因此没有任何额外的功能,不像前面有两个不同的List。实际上Set就是Collection,只 是行为不同。(这是继承与多态思想的典型应用:表现不同的行为。)Set不保存重复的元素。Set 存入Set的每个元素都必须是唯一的,因为Set不保存重复元素。加入Set的元素必须定义equals()方法以确保对象的唯一性。Set与Collection有完全一样的接口。Set接口不保证维护元素的次序。

List和Set总结:

(1)、List,Set都是继承自Collection接口,Map则不是
(2)、List特点:元素有放入顺序,元素可重复 ,Set特点:元素无放入顺序,元素不可重复,重复元素会覆盖掉,(注意:元素虽然无放入顺序,但是元素在set中的位置是有该元素的HashCode决定的,其位置其实是固定的,加入Set 的Object必须定义equals()方法 ,另外list支持for循环,也就是通过下标来遍历,也可以用迭代器,但是set只能用迭代,因为他无序,无法用下标来取得想要的值。)
(3).Set和List对比:
Set:检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变。
List:和数组类似,List可以动态增长,查找元素效率高,插入删除元素效率低,因为会引起其他元素位置改变。
(4)、ArrayList与LinkedList的区别和适用场景
Arraylist:
优点:ArrayList是实现了基于动态数组的数据结构,因为地址连续,一旦数据存储好了,查询操作效率会比较高(在内存里是连着放的)。
缺点:因为地址连续, ArrayList要移动数据,所以插入和删除操作效率比较低。

LinkedList:
优点:LinkedList基于链表的数据结构,地址是任意的,所以在开辟内存空间的时候不需要等一个连续的地址,对于新增和删除操作add和remove,LinedList比较占优势。LinkedList 适用于要头尾操作或插入指定位置的场景
缺点:因为LinkedList要移动指针,所以查询操作性能比较低。
适用场景分析:
当需要对数据进行对此访问的情况下选用ArrayList,当需要对数据进行多次增加删除修改时采用LinkedList。

img

Map详解:

Map用于保存具有映射关系的数据,Map里保存着两组数据:key和value,它们都可以使任何引用类型的数据,但key不能重复。所以通过指定的key就可以取出对应的value。

(1)、请注意!!!, Map 没有继承 Collection 接口, Map 提供 key 到 value 的映射,你可以通过“键”查找“值”。一个 Map 中不能包含相同的 key ,每个 key 只能映射一个 value 。 Map 接口提供 3 种集合的视图, Map 的内容可以被当作一组 key 集合,一组 value 集合,或者一组 key-value 映射。

(2)Map:

img

(3)HashMap和HashTable的比较:

img

(4)TreeMap:

这里写图片描述

(5)Map的其它类:
IdentityHashMap和HashMap的具体区别,IdentityHashMap使用 == 判断两个key是否相等,而HashMap使用的是equals方法比较key值。有什么区别呢?
对于==,如果作用于基本数据类型的变量,则直接比较其存储的 “值”是否相等; 如果作用于引用类型的变量,则比较的是所指向的对象的地址。
对于equals方法,注意:equals方法不能作用于基本数据类型的变量
如果没有对equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址;
诸如String、Date等类对equals方法进行了重写的话,比较的是所指向的对象的内容。

这里写图片描述

Hashmap和Hashset区别

在这里插入图片描述

Map小结:

HashMap 非线程安全
HashMap:基于哈希表实现。使用HashMap要求添加的键类明确定义了hashCode()和equals()[可以重写hashCode()和equals()],为了优化HashMap空间的使用,您可以调优初始容量和负载因子。

TreeMap:非线程安全基于红黑树实现。TreeMap没有调优选项,因为该树总处于平衡状态。

适用场景分析:
HashMap和HashTable:HashMap去掉了HashTable的contains方法,但是加上了containsValue()和containsKey()方法。HashTable同步的,而HashMap是非同步的,效率上比HashTable要高。HashMap允许空键值,而HashTable不允许。

HashMap:适用于Map中插入、删除和定位元素。
Treemap:适用于按自然顺序或自定义顺序遍历键(key)。

5.线程安全集合类与非线程安全集合类
LinkedList、ArrayList、HashSet是非线程安全的,Vector是线程安全的;
HashMap是非线程安全的,HashTable是线程安全的;
StringBuilder是非线程安全的,StringBuffer是线程安全的。

数据结构
ArrayXxx:底层数据结构是数组,查询快,增删慢
LinkedXxx:底层数据结构是链表,查询慢,增删快
HashXxx:底层数据结构是哈希表。依赖两个方法:hashCode()和equals()
TreeXxx:底层数据结构是二叉树。两种方式排序:自然排序和比较器排序

序列化

  1. 什么是Java序列化

    序列化就是把对象改成二进制的过程,可以保存到磁盘或者网络发送

    1. java.io.Serializable
    2. java.io.Externalizable
    3. ObjectInputStream
    4. ObjectOutputStream

java序列化,看这篇就够了

一、序列化的含义、意义及使用场景二、序列化实现的方式1、Serializable1.1 普通序列化1.2 成员是引用的序列化1.3 同一对象序列化多次的机制1.4 java序列化算法潜在的问题1.5 可选的自定义序列化2、Externalizable:强制自定义序列化3、两种序列化对比三、序列化版本号serialVersionUID四、总结

一、序列化的含义、意义及使用场景

  • 序列化:将对象写入到IO流中
  • 反序列化:从IO流中恢复对象
  • 意义:序列化机制允许将实现序列化的Java对象转换位字节序列,这些字节序列可以保存在磁盘上,或通过网络传输,以达到以后恢复成原来的对象。序列化机制使得对象可以脱离程序的运行而独立存在。
  • **使用场景:所有可在网络上传输的对象都必须是可序列化的,**比如RMI(remote method invoke,即远程方法调用),传入的参数或返回的对象都是可序列化的,否则会出错;所有需要保存到磁盘的java对象都必须是可序列化的。通常建议:程序创建的每个JavaBean类都实现Serializeable接口。

二、序列化实现的方式

如果需要将某个对象保存到磁盘上或者通过网络传输,那么这个类应该实现Serializable接口或者Externalizable接口之一。

1、Serializable
1.1 普通序列化

Serializable接口是一个标记接口,不用实现任何方法。一旦实现了此接口,该类的对象就是可序列化的。

  1. 序列化步骤:
  • 步骤一:创建一个ObjectOutputStream输出流;

  • 步骤二:调用ObjectOutputStream对象的writeObject输出可序列化对象。

    public class Person implements Serializable {
      private String name;
      private int age;
      //我不提供无参构造器
      public Person(String name, int age) {
          this.name = name;
          this.age = age;
      }
    
      @Override
      public String toString() {
          return "Person{" +
                  "name='" + name + '\'' +
                  ", age=" + age +
                  '}';
      }
    }
    
    public class WriteObject {
      public static void main(String[] args) {
          try (//创建一个ObjectOutputStream输出流
               ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt"))) {
              //将对象序列化到文件s
              Person person = new Person("9龙", 23);
              oos.writeObject(person);
          } catch (Exception e) {
              e.printStackTrace();
          }
      }
    }
    
  1. 反序列化步骤:
  • 步骤一:创建一个ObjectInputStream输入流;

  • 步骤二:调用ObjectInputStream对象的readObject()得到序列化的对象。

    我们将上面序列化到person.txt的person对象反序列化回来

    public class Person implements Serializable {
      private String name;
      private int age;
      //我不提供无参构造器
      public Person(String name, int age) {
          System.out.println("反序列化,你调用我了吗?");
          this.name = name;
          this.age = age;
      }
    
      @Override
      public String toString() {
          return "Person{" +
                  "name='" + name + '\'' +
                  ", age=" + age +
                  '}';
      }
    }
    
    public class ReadObject {
      public static void main(String[] args) {
          try (//创建一个ObjectInputStream输入流
               ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.txt"))) {
              Person brady = (Person) ois.readObject();
              System.out.println(brady);
          } catch (Exception e) {
              e.printStackTrace();
          }
      }
    }
    //输出结果
    //Person{name='9龙', age=23}
    

    waht??? 输出告诉我们,反序列化并不会调用构造方法。反序列的对象是由JVM自己生成的对象,不通过构造方法生成。

1.2 成员是引用的序列化

如果一个可序列化的类的成员不是基本类型,也不是String类型,那这个引用类型也必须是可序列化的;否则,会导致此类不能序列化。

看例子,我们新增一个Teacher类。将Person去掉实现Serializable接口代码。

public class Person{
    //省略相关属性与方法
}
public class Teacher implements Serializable {

    private String name;
    private Person person;

    public Teacher(String name, Person person) {
        this.name = name;
        this.person = person;
    }

     public static void main(String[] args) throws Exception {
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("teacher.txt"))) {
            Person person = new Person("路飞", 20);
            Teacher teacher = new Teacher("雷利", person);
            oos.writeObject(teacher);
        }
    }
}

img

我们看到程序直接报错,因为Person类的对象是不可序列化的,这导致了Teacher的对象不可序列化

1.3 同一对象序列化多次的机制

同一对象序列化多次,会将这个对象序列化多次吗?答案是否定的。

public class WriteTeacher {
    public static void main(String[] args) throws Exception {
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("teacher.txt"))) {
            Person person = new Person("路飞", 20);
            Teacher t1 = new Teacher("雷利", person);
            Teacher t2 = new Teacher("红发香克斯", person);
            //依次将4个对象写入输入流
            oos.writeObject(t1);
            oos.writeObject(t2);
            oos.writeObject(person);
            oos.writeObject(t2);
        }
    }
}

依次将t1、t2、person、t2对象序列化到文件teacher.txt文件中。

注意:反序列化的顺序与序列化时的顺序一致

public class ReadTeacher {
    public static void main(String[] args) {
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("teacher.txt"))) {
            Teacher t1 = (Teacher) ois.readObject();
            Teacher t2 = (Teacher) ois.readObject();
            Person p = (Person) ois.readObject();
            Teacher t3 = (Teacher) ois.readObject();
            System.out.println(t1 == t2);
            System.out.println(t1.getPerson() == p);
            System.out.println(t2.getPerson() == p);
            System.out.println(t2 == t3);
            System.out.println(t1.getPerson() == t2.getPerson());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
//输出结果
//false
//true
//true
//true
//true

从输出结果可以看出,Java序列化同一对象,并不会将此对象序列化多次得到多个对象。

  • Java序列化算法
  1. 所有保存到磁盘的对象都有一个序列化编码号

  2. 当程序试图序列化一个对象时,会先检查此对象是否已经序列化过,只有此对象从未(在此虚拟机)被序列化过,才会将此对象序列化为字节序列输出。

  3. 如果此对象已经序列化过,则直接输出编号即可。

    图示上述序列化过程。

img

1.4 java序列化算法潜在的问题

由于java序利化算法不会重复序列化同一个对象,只会记录已序列化对象的编号。如果序列化一个可变对象(对象内的内容可更改)后,更改了对象内容,再次序列化,并不会再次将此对象转换为字节序列,而只是保存序列化编号。

public class WriteObject {
    public static void main(String[] args) {
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.txt"));
             ObjectInputStream ios = new ObjectInputStream(new FileInputStream("person.txt"))) {
            //第一次序列化person
            Person person = new Person("9龙", 23);
            oos.writeObject(person);
            System.out.println(person);

            //修改name
            person.setName("海贼王");
            System.out.println(person);
            //第二次序列化person
            oos.writeObject(person);

            //依次反序列化出p1、p2
            Person p1 = (Person) ios.readObject();
            Person p2 = (Person) ios.readObject();
            System.out.println(p1 == p2);
            System.out.println(p1.getName().equals(p2.getName()));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
//输出结果
//Person{name='9龙', age=23}
//Person{name='海贼王', age=23}
//true
//true
1.5 可选的自定义序列化
  1. 有些时候,我们有这样的需求,某些属性不需要序列化。使用transient关键字选择不需要序列化的字段。

    public class Person implements Serializable {
       //不需要序列化名字与年龄
       private transient String name;
       private transient int age;
       private int height;
       private transient boolean singlehood;
       public Person(String name, int age) {
           this.name = name;
           this.age = age;
       }
       //省略get,set方法
    }
    
    public class TransientTest {
       public static void main(String[] args) throws Exception {
           try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.txt"));
                ObjectInputStream ios = new ObjectInputStream(new FileInputStream("person.txt"))) {
               Person person = new Person("9龙", 23);
               person.setHeight(185);
               System.out.println(person);
               oos.writeObject(person);
               Person p1 = (Person)ios.readObject();
               System.out.println(p1);
           }
       }
    }
    //输出结果
    //Person{name='9龙', age=23', singlehood=true', height=185cm}
    //Person{name='null', age=0', singlehood=false', height=185cm}
    

    从输出我们看到,使用transient修饰的属性,java序列化时,会忽略掉此字段,所以反序列化出的对象,被transient修饰的属性是默认值。对于引用类型,值是null;基本类型,值是0;boolean类型,值是false。

  2. 使用transient虽然简单,但将此属性完全隔离在了序列化之外。java提供了**可选的自定义序列化。**可以进行控制序列化的方式,或者对序列化数据进行编码加密等。

    private void writeObject(java.io.ObjectOutputStream out) throws IOExceptionprivate void readObject(java.io.ObjectIutputStream in) throws IOException,ClassNotFoundException;
    private void readObjectNoData() throws ObjectStreamException;
    

    通过重写writeObject与readObject方法,可以自己选择哪些属性需要序列化, 哪些属性不需要。如果writeObject使用某种规则序列化,则相应的readObject需要相反的规则反序列化,以便能正确反序列化出对象。这里展示对名字进行反转加密。

    public class Person implements Serializable {
       private String name;
       private int age;
       //省略构造方法,get及set方法
    
       private void writeObject(ObjectOutputStream out) throws IOException {
           //将名字反转写入二进制流
           out.writeObject(new StringBuffer(this.name).reverse());
           out.writeInt(age);
       }
    
       private void readObject(ObjectInputStream ins) throws IOException,ClassNotFoundException{
           //将读出的字符串反转恢复回来
           this.name = ((StringBuffer)ins.readObject()).reverse().toString();
           this.age = ins.readInt();
       }
    }
    

    当序列化流不完整时,readObjectNoData()方法可以用来正确地初始化反序列化的对象。例如,使用不同类接收反序列化对象,或者序列化流被篡改时,系统都会调用readObjectNoData()方法来初始化反序列化的对象。

  3. 更彻底的自定义序列化

    ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;
    ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;

    • writeReplace:在序列化时,会先调用此方法,再调用writeObject方法。此方法可将任意对象代替目标序列化对象

      public class Person implements Serializable {
        private String name;
        private int age;
        //省略构造方法,get及set方法
      
        private Object writeReplace() throws ObjectStreamException {
            ArrayList<Object> list = new ArrayList<>(2);
            list.add(this.name);
            list.add(this.age);
            return list;
        }
      
         public static void main(String[] args) throws Exception {
            try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.txt"));
                 ObjectInputStream ios = new ObjectInputStream(new FileInputStream("person.txt"))) {
                Person person = new Person("9龙", 23);
                oos.writeObject(person);
                ArrayList list = (ArrayList)ios.readObject();
                System.out.println(list);
            }
        }
      }
      //输出结果
      //[9龙, 23]
      
    • readResolve:反序列化时替换反序列化出的对象,反序列化出来的对象被立即丢弃。此方法在readeObject后调用。

      public class Person implements Serializable {
          private String name;
          private int age;
          //省略构造方法,get及set方法
           private Object readResolve() throws ObjectStreamException{
              return new ("brady", 23);
          }
          public static void main(String[] args) throws Exception {
              try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.txt"));
                   ObjectInputStream ios = new ObjectInputStream(new FileInputStream("person.txt"))) {
                  Person person = new Person("9龙", 23);
                  oos.writeObject(person);
                  HashMap map = (HashMap)ios.readObject();
                  System.out.println(map);
              }
          }
      }
      //输出结果
      //{brady=23}
      

      readResolve常用来反序列单例类,保证单例类的唯一性。

      注意:readResolve与writeReplace的访问修饰符可以是private、protected、public,如果父类重写了这两个方法,子类都需要根据自身需求重写,这显然不是一个好的设计。通常建议对于final修饰的类重写readResolve方法没有问题;否则,重写readResolve使用private修饰。

2、Externalizable:强制自定义序列化

通过实现Externalizable接口,必须实现writeExternal、readExternal方法。

public interface Externalizable extends java.io.Serializable {
     void writeExternal(ObjectOutput out) throws IOException;
     void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}
public class ExPerson implements Externalizable {

    private String name;
    private int age;
    //注意,必须加上pulic 无参构造器
    public ExPerson() {
    }

    public ExPerson(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        //将name反转后写入二进制流
        StringBuffer reverse = new StringBuffer(name).reverse();
        System.out.println(reverse.toString());
        out.writeObject(reverse);
        out.writeInt(age);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        //将读取的字符串反转后赋值给name实例变量
        this.name = ((StringBuffer) in.readObject()).reverse().toString();
        System.out.println(name);
        this.age = in.readInt();
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ExPerson.txt"));
             ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ExPerson.txt"))) {
            oos.writeObject(new ExPerson("brady", 23));
            ExPerson ep = (ExPerson) ois.readObject();
            System.out.println(ep);
        }
    }
}
//输出结果
//ydarb
//brady
//ExPerson{name='brady', age=23}

注意:Externalizable接口不同于Serializable接口,实现此接口必须实现接口中的两个方法实现自定义序列化,这是强制性的;特别之处是必须提供pulic的无参构造器,因为在反序列化的时候需要反射创建对象。

3、两种序列化对比
实现Serializable接口实现Externalizable接口
系统自动存储必要的信息程序员决定存储哪些信息
Java内建支持,易于实现,只需要实现该接口即可,无需任何代码支持必须实现接口内的两个方法
性能略差性能略好

虽然Externalizable接口带来了一定的性能提升,但变成复杂度也提高了,所以一般通过实现Serializable接口进行序列化。

三、序列化版本号serialVersionUID

我们知道,反序列化必须拥有class文件,但随着项目的升级,class文件也会升级,序列化怎么保证升级前后的兼容性呢?

java序列化提供了一个private static final long serialVersionUID 的序列化版本号,只有版本号相同,即使更改了序列化属性,对象也可以正确被反序列化回来。

public class Person implements Serializable {
    //序列化版本号
    private static final long serialVersionUID = 1111013L;
    private String name;
    private int age;
    //省略构造方法及get,set
}

如果反序列化使用的class的版本号与序列化时使用的不一致,反序列化会报InvalidClassException异常。

img

序列化版本号可自由指定,如果不指定,JVM会根据类信息自己计算一个版本号,这样随着class的升级,就无法正确反序列化;不指定版本号另一个明显隐患是,不利于jvm间的移植,可能class文件没有更改,但不同jvm可能计算的规则不一样,这样也会导致无法反序列化。

什么情况下需要修改serialVersionUID呢?分三种情况。

  • 如果只是修改了方法,反序列化不容影响,则无需修改版本号;
  • 如果只是修改了静态变量,瞬态变量(transient修饰的变量),反序列化不受影响,无需修改版本号;
  • 如果修改了非瞬态变量,则可能导致反序列化失败。**如果新类中实例变量的类型与序列化时类的类型不一致,则会反序列化失败,这时候需要更改serialVersionUID。**如果只是新增了实例变量,则反序列化回来新增的是默认值;如果减少了实例变量,反序列化时会忽略掉减少的实例变量。

四、总结

  1. 所有需要网络传输的对象都需要实现序列化接口,通过建议所有的javaBean都实现Serializable接口。
  2. 对象的类名、实例变量(包括基本类型,数组,对其他对象的引用)都会被序列化;方法、类变量、transient实例变量都不会被序列化。
  3. 如果想让某个变量不被序列化,使用transient修饰。
  4. 序列化对象的引用类型成员变量,也必须是可序列化的,否则,会报错。
  5. 反序列化时必须有序列化对象的class文件。
  6. 当通过文件、网络来读取序列化后的对象时,必须按照实际写入的顺序读取。
  7. 单例类序列化,需要重写readResolve()方法;否则会破坏单例原则。
  8. 同一对象序列化多次,只有第一次序列化为二进制流,以后都只是保存序列化编号,不会重复序列化。
  9. 建议所有可序列化的类加上serialVersionUID 版本号,方便项目升级。
    | 程序员决定存储哪些信息 |
    | Java内建支持,易于实现,只需要实现该接口即可,无需任何代码支持 | 必须实现接口内的两个方法 |
    | 性能略差 | 性能略好 |

虽然Externalizable接口带来了一定的性能提升,但变成复杂度也提高了,所以一般通过实现Serializable接口进行序列化。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值