不懂就问,读一读源码——Java 容器

容器概览

在这里插入图片描述
容器主要包含 Collection 和 Map 两种。
Collection 存储对象集合,Map 存储键值对(两个对象)的映射表。

读源码

IDEA,Ctrl + click 。

1 ArrayList

1.读源码

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

ArrayList 继承了 :

  • AbstractList,一个 List 的抽象类;
  • RandomAccess,一个标记接口,说明实现了快速随机访问;
  • Cloneable,一个标记接口,说明支持拷贝(克隆);如果不实现该接口,会抛出CloneNotSupportedException(克隆不被支持)异常;
  • java.io.Serializable,一个序列化接口,说明该对象可以序列化;(序列化是啥,简单说就是把它打包,利于后面持久化存储啊运输什么的,这里不深究)

然后就是递归学习的时候了:

1.1 ArrayList → AbstractList 类 ?
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> 

看了下,是一个抽象类,实现了一些基础 method,以及迭代器:
在这里插入图片描述

1.1.1 ArrayList → AbstractList → AbstractCollection 类 ?
public abstract class AbstractCollection<E> implements Collection<E>

一个抽象类,继承于 Collection 接口,实现了一些基础方法,声明了一些基础方法:
在这里插入图片描述
同时定义了 Array 的最大长度:

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

为什么要留 8 个字节出来呢?我也很好奇,所以来看看
Stackoverflow
8 bytes 存储 64 bits 数,用来存储数组长度。(懂了,所以 留八个字节)

1.1.1.1 ArrayList → AbstractList → AbstractCollection / List → Collection 接口?

接口,方法的特征集合,逻辑抽象,到这里你是否明白了:
在这里插入图片描述

1.1.2 ArrayList → AbstractList → List 接口?

在这里插入图片描述

有问题…

看了俩接口了,结果都发现?里面有实现方法,就像下面的 sort 和 spliterator。
在这里插入图片描述
好奇怪,是不是我学艺不精。所以我又去了解了一下。Java 接口可以实现具体方法?

从 Java 8开始就可以在接口里实现具体方法了。突然发现,虽然现在使用的是 jdk1.8,但是基础语法都还只是熟悉的 8 之前的。然后我又看了下 2020 的 jdk 版本占比:
GRAALVM survey results
在这里插入图片描述

其实该多了解下新的 java 了。
继续之前的话题:

默认方法与静态方法

默认方法,default 修饰,只能在 接口 中用;静态方法,我们都熟悉。
从 Java 8 开始就支持 接口 内实现 默认方法和静态方法 了,而且可以多个。

为什么会有这个修改?
  • 默认方法
    接口 一般都是很早就写好了,(接口,方法的特征集合,逻辑抽象),但 接口 的特性就让它在写好之后,难以去修改,因为接口实现类必须实现所有接口方法的特性,一旦修改,所有继承者都要去实现新的修改。
    所以很麻烦。
    所以懒人原则,添加了一个默认方法,添加新的默认方法时,所有实现类自动继承默认方法,无需修改。
  • 静态方法
    默认方法也可能被重写,被修改,那么我们也需要一个被继承但不被修改的默认方法,就像 final 特性一样,就推出了 static 方法。
有个小问题

我们测试一下 default 方法:

public interface Student {
    default void walk(){
        System.out.println("Student Walk!");
    }
}

public interface Teacher {
    default void walk(){
        System.out.println("Teacher Walk!");
    }
}

public class oneMan implements Student,Teacher {
    
}

这个时候,IDEA 报错了:
在这里插入图片描述
同名的 默认方法 被继承,编译器会无法识别,所以需要重写该方法。
但是其实还是可以调用 接口 方法的:

public class oneMan implements Student,Teacher {

    @Override
    public void walk() {
        Student.super.walk();
        Teacher.super.walk();
    }
}
1.2 ArrayList → RandomAccess ?

它是一个标志性接口,所以是空的。

public interface RandomAccess {
}

但是运作原理是什么呢?
我百度到说 它和 Collections 的一个 binarySearch 二分查找有关,于是我去看了下:

public static <T>
    int binarySearch(List<? extends Comparable<? super T>> list, T key) {
        if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
            return Collections.indexedBinarySearch(list, key);
        else
            return Collections.iteratorBinarySearch(list, key);
    }

我们看到 RandomAccess 了,那这段代码啥意思呢?

instanceof 关键字,测试对象是否是一个类的实例。这里就是检查 list 是不是 RandomAccess 的实例,也就是测试 list 是否实现了 RandomAccess 接口。从而选择执行哪个二分查找,indexedBinarySearch or iteratorBinarySearch。
有啥区别呢

private static <T>
    int indexedBinarySearch(List<? extends Comparable<? super T>> list, T key) {
        int low = 0;
        int high = list.size()-1;

        while (low <= high) {
            int mid = (low + high) >>> 1;
            Comparable<? super T> midVal = list.get(mid);
            int cmp = midVal.compareTo(key);

            if (cmp < 0)
                low = mid + 1;
            else if (cmp > 0)
                high = mid - 1;
            else
                return mid; // key found
        }
        return -(low + 1);  // key not found
    }
    
private static <T>
    int iteratorBinarySearch(List<? extends Comparable<? super T>> list, T key)
    {
        int low = 0;
        int high = list.size()-1;
        ListIterator<? extends Comparable<? super T>> i = list.listIterator();

        while (low <= high) {
            int mid = (low + high) >>> 1;
            Comparable<? super T> midVal = get(i, mid);
            int cmp = midVal.compareTo(key);

            if (cmp < 0)
                low = mid + 1;
            else if (cmp > 0)
                high = mid - 1;
            else
                return mid; // key found
        }
        return -(low + 1);  // key not found
    }

我把实现贴出来了,实现了 RandomAccess ,查找用的是 for 循环,也就是 indexed 查找,而未实现就会用 iterator 迭代器。
如果你知道 iterator 的优势,你就知道,迭代器对于 LinkList 这种 链表 实现的聚合对象遍历比 for 更快,而对于 ArrayList 这种 数组实现 的聚合对象遍历 就更慢了。
所以 ArrayList 才支持 RandomAccess , 而 LinkList 不支持。

RandomAccess 是一个 标志接口,它只是用来选择 使用哪种 遍历方式,所以开发时需要注意这些 区别。

1.3 ArrayList → Cloneable ?

同样,是一个标志接口。有经验了,我们直接看 ArrayList.clone()

public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
    }

这里用了一个拷贝,同时调用了 Object.clone()
我们去看看:

    protected native Object clone() throws CloneNotSupportedException;

只有这一行,但是我们学 JVM 结构的时候 有注意到过,native 库。显然这个方法是 非 java 语言实现的,具体实现在 JVM native 库里面。

到这里 Cloneable 都没有进行判断是否实现。
所以大概是 在 JVM native 里实现判断的了。

1.4 ArrayList → Serializable ?

Serializable 接口,一个对象序列化的接口,也是标识接口,一个类只有实现了Serializable接口,它的对象才能被序列化。

public interface Serializable {
}
什么是序列化?

在学校的确一般情况下,遇不到序列化。(因为你不看源码都不知道你用的这些对象都是 可序列化的)
序列化主要是用于存储和传输数据,其实就是将对象状态转换为可保持或传输的格式的过程。与序列化相对的是反序列化,它将流(Stream)转换为对象。这两个过程结合起来,可以轻松地存储和传输数据。

序列化用于 数据 流操作,所以显然会在 java.io 包中,java.io.Serializable
序列化就是将对象转化为字节序列,反序列化就是将字节序列恢复为对象

为什么需要序列化?

我大概了解了一下,我们来说说:

  1. 序列化的意义:序列化机制允许将实现序列化的Java对象转换位字节序列,这些字节序列可以保存在磁盘上,或通过网络传输,以达到以后恢复成原来的对象序列化机制使得对象可以脱离程序的运行而独立存在

到重点了,序列化可以让对象脱离程序而独立存在?这个问题看起来很迷糊,其实我们无时无刻都在使用序列化,磁盘的存储,网络数据的传输,Java 的所有对象需要实现这些功能,都需要序列化操作。序列化就是对对象状态的数据格式转换,就是因为我存储了对象的状态信息,所以才叫让对象脱离程序运行而独立存在;也就是说,我存储了状态信息,我无论在哪个程序反序列化,我都可以恢复到序列化之前的状态。

  1. 我看到说的最多的就是 RMI,所以我也去了解了一下:

​ Java RMI,即 远程方法调用(Remote Method Invocation),JDK 1.2 实现的(98年),可以被看做是 RPC(远程过程调用)的 Java 版本,是 Java 分布式架构的一个基础框架。
它和序列化啥关系?
序列化的对象脱离特点可以让它实现分布式对象,而 RMI 会利用 对象序列化 来运行远程主机的服务,也由于 Java RMI 主要支持 Java,Java RMI 也可以说是用于 JVM 之间的通信,类似于 IPC(进程间通信) LPC(本地过程调用) 一样,支持一个 JVM 调用另一个 JVM 的对象数据 。

这一切都依赖于 序列化 Serializable 。

参考: 简书-分布式架构基础:Java RMI详解

  1. 序列化可以用于内存类写入磁盘,文件,数据库

有些存储需要存储数据状态,序列化可以保存对象状态,比如游戏存盘,状态保存什么的,都可以将内存对象序列化写入磁盘、数据库。

  1. 序列化操作可以统一文件数据格式,统一存储

序列化以后就都是字节流了,无论原来是什么东西,都能变成一样的东西。

参考:cnblog-序列化实现方式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值