java基础复习(六):容器类库

本文深入探讨了Java中的容器类库,包括容器的本质、数组与容器的区别、泛型的使用以及常见容器如ArrayList和linkedList的特点与对比。详细解析了线程安全的CopyOnWriteArrayList实现原理,分析了HashMap的哈希函数、扩容策略及并发问题。此外,还对比了hashTable和concurrentHashMap的设计差异,展示了如何在实际开发中选择和使用合适的容器类型。
摘要由CSDN通过智能技术生成

理解容器的本质

java 中的容器(这里不区分集合、容器)通常指的是能够存放对象的类型,底层通常都是数组对象,并且提供了一组操作底层这个数组的API。容器对象其实也就是一个普通的对象,它的成员是一个数组,可以存放外部传递的值或对象引用,但是由于封装的特性,我们不需要关注底层的数组,只需要关心容器对象本身即可。
jdk为了规范容器的行为提供了collection接口,list、set、queue都是collection的子接口,看作对“容器”行为的进一步细化与扩展。而Map是JDK提供的映射接口,map的实现类大多保存entry或node节点,因此也可以看作容器。
值得注意的是,map接口本身是不提供返回迭代器的方法的,而collection实现了iterator接口,规定容器对象必须具有返回迭代器对象的方法,map实现类对象如果想要迭代一般是通过返回keySet实例间接实现迭代的。

容器与数组

容器和数组本质上都是对象,而我们是也都是通过引用间接访问它们的。我们创建容器对象和数组对象都离不开关键词new(不考虑反射)。
java的数组都是通过jvm指令创建的。(基本类型数组对应newarray,引用类型数组对应anewarray,多维数组对应multianewarray),而创建实例对应的指令是new指令。
我们获取容器的长度,本质上是获取底层数据结构的长度。而我们获取数组对象的长度,本质上是调用JVM指令arraylength,其中数组长度保存在数组对象的对象头中。

java数组

java数组分为两种初始化(分配内存空间,并指定初始值)方式:
【1】动态初始化

int a[]=new int[3];

程序员只指定数组长度,由系统为数组元素分配初始值(默认值)

注意:对象数组(包括string和数组对象 object[])默认值为null。short/int/long默认值为0、float/double默认值0.0、char默认值‘0’、boolean默认值false

【2】静态初始化

int a[] = {
   1,21,22};
int aa[]=new int[]{
   1,2,3};
 int[][]cc = new int[][]{
   {
   1,2,3},{
   4,3,2,1}};

程序员显式的指定数组的初始值,由系统决定数组长度

数组本质上是一个对象,数组中的每一个单位都可以看作是它的成员,如果是值类型那么成员对应内存上存放的就是字面量,如果是对象,那么成员对应内存上存放的是一个地址值。
数组又是支持随机访问的,每一个“单位”的大小都是固定的,因此可以根据首地址计算出任意一个单元的地址。

注意:
数组命名时名称与[]可以随意排列,但声明的二维数组中第一个中括号中必须要有值,它代表的是在该二维数组中有多少个一维数组。

//        int[][] a = new int[][10];  不可以
        int[]a[] = new int[10][]; //名称与[]顺序无所谓

因为数组本质上是对象,因此不要使用“==”直接比较,而是使用arrays.equals(),或者收到遍历成员并比较.toString同理,如果直接打印数组那么只能得到JVM默认的实现( [ 就代表一维数组 )。
数组另一种创建方式就是通过java.reflect.array去反射创建。

容器的最佳实践

容器是一种框架,是对底层数据结构访问的封装,因此如果能够直接使用数组解决的功能我们应该尽量使用数组,数组占用内存肯定是比封装了数组的容器对象要小的。使用容器,主要就是容器的开发者为我们封装了操作底层数据结构的接口如排序、遍历、插入、删除。容器各种操作的复杂度和底层的数据结构息息相关,应该根据具体的场景选择合适的容器(分析它的底层数据结构)。
我们应该尽量避免使用“无界”的容器,应该指定合适的初始容量和扩容阈值(如果可以的话)。
如果使用的基于哈希函数实现的容器,那么我们应该保证待存入的对象重写了hashCode()方法和equals()方法。

泛型

聊容器就离不开泛型了。泛型就是一种工具,没有它也不影响我们使用,因为它也不过是JDK5才引入的。
泛型的本质是参数化类型,被操作的数据类型可以被指定为方法签名的一个参数
【1】泛型可以将类型安全检查从运行期提前到编译期
【2】我们将一个从一个指定了泛型的容器取出元素时,不需要手动强制类型装换了,这个工作在编译期将自动完成
【3】增加了代码的复用性,框架普遍使用了泛型。
由于泛型不支持基本类型(因为无法将一个基本类型和Object类型之间进行强制类型装换),所以使用泛型容器时总是存在大量的拆箱和装箱。
java源码经过编译后,所有类型参数都会被替换为实际类型(泛型擦除),编译器在元素访问的代码出进行解语法糖,插入强制类型转换的代码。

jvm的signature属性保留了一个方法在字节码层面的特征签名,这其中包含了泛型信息,泛型的擦除是仅仅对方法中Code属性的擦除,元数据仍然保存在方法区,这也是通过反射可以获得泛型信息的原因。(反射就是通过class对象这个入口访问方法区的元数据)

泛型参数不考虑继承关系,通配符?可以让泛型可以接收任意类型参数,?可以引用各种参数化类型,可以调用与参数化无关的方法(例如size()),对于返回值就是参数化类型(如返回E)的方法,仅仅能通过Object接收,而形参为参数化类型的(如V v)方法,无法调用,编译报错(会被一个capture类型的入参占位,无法传入参数)

无界通配——?:参数和返回值为泛型的方法,不能使用
子类限定(上限确定,?只能接收XXX或其子类)——?extends XXX:参数为泛型的方法不能使用【正三角】
父类限定(下限确定,?只能接收XXX或其父类)——?super XXX返回值为泛型的方法不能使用【倒三角】

自定义泛型类/方法

泛型类中,可以在成员变量上使用。也可以在方法中使用(返回值类型、形参类型、局部变量类型)

    class ea<E>{
   
        E date ;
        public E a(E e){
   
            E i;
            return e;
        }
    }

new E()是不可以的,想想就知道,因为编译后E就会被擦除了,而创建对象时,new后面跟的构造函数的符号引用是需要保存在常量池中的。

   public<E> E a
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值