前面介绍了集合与映射两类容器,它们的共同特点是每个元素都是唯一的,并且采用二叉树方式的类型还自带有序性。然而这两个特点也存在弊端:其一,为啥内部元素必须是唯一的呢?像手机店卖出了两部Mate20,虽然这两部手机一模一样,但理应保存两条销售记录才是。其二,不管是哈希类型还是二叉类型,居然都不允许按照加入时间的先后排序,要知道现实生活中不乏各种先来后到的业务场景。为了更方便地应对真实场景中的各类需求,Java又设计了清单List这么一种容器,用来处理集合与映射所不支持的业务功能。
提到清单,脑海里顿时浮现出从上往下排列的一组表格,例如购物清单、愿望清单、待办事项等等,它们的共同点一是都有序号,二是按线性排列。清单里的元素允许重复加入,并且根据入伙的时间顺序先后罗列,这些特征决定了清单是种贴近日常生活的简易容器。不过Java中的List属于接口,实际开发用到的是它的一个实现类ArrayList(列表,又称动态数组)。在某种程度上,列表的确跟数组很像,比如二者的内部元素都分配了整数序号/下标、都支持通过序号/下标来访问指定位置的元素等等。但列表贵为容器中的一员,自然拥有几点数组所不能比拟的优势,包括但不限于:

一、列表允许动态添加新元素,不管调用多少次add方法,也不必担心列表空间不够用的问题。下面代码便演示了如何声明列表实例并对其依次添加元素:

		// 创建一个列表(动态数组),其元素为MobilePhone类型
ArrayList<MobilePhone> list = new ArrayList<MobilePhone>();
list.add(new MobilePhone("华为", 5000)); // 第一个添加的元素,默认分配序号为0
list.add(new MobilePhone("小米", 2000)); // 第二个添加的元素,默认分配序号为1
list.add(new MobilePhone("OPPO", 4000)); // 第三个添加的元素,默认分配序号为2
list.add(new MobilePhone("vivo", 1000)); // 第四个添加的元素,默认分配序号为3
list.add(new MobilePhone("vivo", 1000)); // 第五个添加的元素,默认分配序号为4

而数组的大小一经初始化设定就不可调整,除非另外给它分配新的数组空间;

二、数组只能对指定位置的元素进行修改操作,列表不但支持修改指定位置的元素(set方法),还支持在指定位置插入新元素(add方法),或者移除指定位置的元素(remove方法)。

三、数组只有两种遍历方式:按下标遍历、通过简化的for循环遍历。而列表支持多达四种的遍历方式,分别说明如下:
1、简化的for循环。该方式同样适用于数组和容器,具体的遍历代码示例如下:

		// 第一种遍历方式:简化的for循环同样适用于数组和容器
for (MobilePhone for_item : list) {
System.out.println(String.format("for_item:%s %d",
for_item.getBrand(), for_item.getPrice()));
}

2、迭代器遍历。该方式与利用迭代器遍历集合是一样,都要先获得当前容器的迭代器,然后依次调用迭代器的next逐个获取元素。利用迭代器遍历列表的代码如下所示:

		// 第一种遍历方式:简化的for循环同样适用于数组和容器
for (MobilePhone for_item : list) {
System.out.println(String.format("for_item:%s %d",
for_item.getBrand(), for_item.getPrice()));
}

3、索引遍历。这里的索引是以0开始的序号,对应于数组的下标,只不过列表通过get方法获取指定位置的元素,而数组通过方括号引用某个下标。下面是使用索引遍历列表的代码例子:

		// 第三种遍历方式:与数组通过下标访问相似,列表通过索引获取指定位置的元素
for (int i = 0; i < list.size(); i++) {
MobilePhone index_item = list.get(i);
System.out.println(String.format("index_item:%s %d",
index_item.getBrand(), index_item.getPrice()));
}

4、forEach遍历。Java8之后,每种容器都支持联合应用forEach与Lambda表达式的遍历方式,该方式的遍历代码见下:

		// 第四种遍历方式:使用forEach方法夹带Lambda表达式进行遍历
list.forEach(each_item -> System.out.println(String.format(
"each_item:%s %d", each_item.getBrand(), each_item.getPrice())));

  

尽管列表对于大多数的业务场景来说够用了,可是仍旧无法满足部分特定的业务需求,因为ArrayList默认把新元素添加到列表末尾,也不存在默认的删除操作。而在计算机科学常见的数据结构当中,至少还有两种是列表所不能实现的,其中一个叫做队列Deque,另一个叫做栈Stack。
队列取材于生活中的排队场景,譬如春运期间大家在火车站排队买车票,虽然有个别人嚷嚷着“我要插队”且自顾自地插了进去,也有人忍受不了漫长的等待而中途放弃排队改为骑单车回家,但多数人都会循规蹈矩地从队尾开始排队,买了票之后从队首离队。于是排队业务就抽象成为这么一种队列结构:添加时默认往末尾添加,删除时默认从开头删除。
至于栈则取材于计算机系统的寄存器操作,栈的特点是里面保存的数据为先进后出(同时也是后进先出),即最早添加的元素会被最后移除、最晚添加的元素会被最先移除。基于栈具有的数据先进后出特性,它常用于保存中断时的断点、保存子程序调用后的返回点、保存CPU的现场数据、在程序间传递参数等等。就栈作为一种容器的角色而言,每次添加的元素会默认加到开头,且每次删除操作会默认删去开头的元素,从而实现后进先出/先进后出的机制。
然而不管是队列还是栈,它们的存储形式都如同清单那样线性排列,区别在于数据进出的默认方位。因此Java把队列、栈以及清单三者加以融合,推出了链表LinkedList(又称双端队列)这种数据结构,它一起实现了List与Deque接口,并在某种程度上模拟了栈的功能,从而变成专治各种不服的万能清单。
作为清单大家族的一员,链表LinkedList的基本用法与列表ArrayList相同,并基于它的三个祖宗分别进行了下列方法拓展:
1、在清单List的功能增强方面,补充了如下的扩展方法:
addFirst:添加到清单开头
addLast:添加到清单末尾
removeFirst:删除清单开头的元素
removeLast:删除清单末尾的元素
getFirst:获取清单开头的元素
getLast:获取清单末尾的元素
2、在队列Queue的功能实现方面,提供了如下的队列方法:
offer:添加到队列末尾
offerFirst:添加到队列开头
offerLast:添加到队列末尾
peek:获取队列开头的元素
peekFirst:获取队列开头的元素
peekLast:获取队列末尾的元素
poll:删除队列开头的元素
pollFirst:删除队列开头的元素
pollLast:删除队列末尾的元素
3、在栈Stack的功能模拟方面,添加了如下的额外方法:
pop:队列开头的元素出栈,相当于方法removeFirst和pollFirst
push:新元素入栈,相当于方法addFirst和offerFirst
总的来说,链表的数据存储兼顾清单和队列的组织结构,常用于对数据进出有特殊要求的场合,例如采取先进先出FIFO的队列操作,以及采取先进后出FILO的栈操作。

更多Java技术文章参见《Java开发笔记(序)章节目录

Java开发笔记(六十七)清单:ArrayList和LinkedList的更多相关文章

  1. Java开发笔记(十七)各得其所的多路分支

    前面提到条件语句的标准格式为“if (条件) { /* 条件成立时的操作代码 */ } else { /* 条件不成立时的操作代码 */ }”,乍看之下仿佛只有两个分支,一个是条件成立时的分支,另一个 ...

  2. Java开发笔记(序)章节目录

    现将本博客的Java学习文章整理成以下笔记目录,方便查阅. 第一章 初识JavaJava开发笔记(一)第一个Java程序Java开发笔记(二)Java工程的帝国区划Java开发笔记(三)Java帝国的 ...

  3. Java开发笔记(六十九)泛型类的定义及其运用

    前面从泛型方法的用法介绍到了泛型的起源,既然单个方法允许拥有泛化的参数类型,那么一个类也应当支持类级别的泛化类型,例如各种容器类型ArrayList.HashMap等等.一旦某个类的定义代码在类名称后 ...

  4. Java开发笔记(七十六)如何预防异常的产生

    每个程序员都希望自己的程序稳定运行,不要隔三岔五出什么差错,可是程序运行时冒出来的各种异常着实烦人,令人不胜其扰.虽然可以在代码中补上try/catch语句捕捉异常,但毕竟属于事后的补救措施.与其后知 ...

  5. Java开发笔记(七十七)使用Optional规避空指针异常

    前面在介绍清单用法的时候,讲到了既能使用for循环遍历清单,也能通过stream流式加工清单.譬如从一个苹果清单中挑选出红苹果清单,采取for循环和流式处理都可以实现.下面是通过for循环挑出红苹果清 ...

  6. Java开发笔记(六十五)集合:HashSet和TreeSet

    对于相同类型的一组数据,虽然Java已经提供了数组加以表达,但是数组的结构实在太简单了,第一它无法直接添加新元素,第二它只能按照线性排列,故而数组用于基本的操作倒还凑合,若要用于复杂的处理就无法胜任了 ...

  7. Java开发笔记(六十八)从泛型方法探究泛型的起源

    前面介绍各种容器之时,通过在容器名称后面添加包裹数据类型的一对尖括号,表示该容器存放的是哪种类型的元素.这样一来总算把Java当中的各类括号都凑齐了,例如包裹一段代码的花括号.指定数组元素下标的方括号 ...

  8. Java开发笔记(九十六)线程的基本用法

    每启动一个程序,操作系统的内存中通常会驻留该程序的一个进程,进程包含了程序的完整代码逻辑.一旦程序退出,进程也就随之结束:反之,一旦强行结束进程,程序也会跟着退出.普通的程序代码是从上往下执行的,遇到 ...

  9. Java开发笔记(一百三十六)JavaFX的窗格

    虽然Java自诞生之初就推出了AWT,紧接着第二版又推出升级后的Swing,打算在桌面开发这块大展拳脚:可是后来Java在服务器开发上大放异彩,在桌面开发上反而停滞不前,可谓失之J2SE收之J2EE. ...

随机推荐

  1. SELECT 语句

    常见表的操作 查看数据库的表   show  table 查看表结构     desc 表名 删除表   drop table表 修改表的结构 添加列  alter   table 表名 add 列名 ...

  2. H5混合开发问题总结

    1.This application is modifying the autolayout engine from a background thread, which can lead to en ...

  3. JDK、JRE

    JRE: java Runtime environment (java运行环境) JVM:java virtual machine (java 虚拟机) java程序就在jvm中运行. JDK: ja ...

  4. lua语言自学知识点----Lua与.Net相互调用

    知识点: LuaInterface作用是用来完成Lua与C#的相互调用. LuaInterface核心库:1.luainterface.dll 用于C#读取lua(放在bin目录同级) 2.luane ...

  5. SQL语句题

    SQL语句题 Student(Sno,Sname,Sage,Ssex)注释:学生表(学号,姓名,性别年龄,性别) Course(Cno,Cname,Tno) 注释:课程表(课程号,课程名称,教师编号) ...

  6. 最新Java技术

    最近在网上查资料碰到好多没接触过的技术,先汇总在这里备用,以后慢慢吸收 1. JNA JNI的替代品,调用方式比JNI更直接,不再需要JNI那层中间接口,几乎达到Java直接调用动态库 2. Smal ...

  7. RabbitMQ进程结构分析与性能调优

    RabbitMQ是一个流行的开源消息队列系统,是AMQP(高级消息队列协议)标准的实现,由以高性能.健壮.可伸缩性出名的Erlang语言开发,并继承了这些优点.业界有较多项目使用RabbitMQ,包括 ...

  8. [Swift]LeetCode27. 移除元素 | Remove Element

    Given an array nums and a value val, remove all instances of that value in-place and return the new ...

  9. [Swift]LeetCode665. 非递减数列 | Non-decreasing Array

    Given an array with n integers, your task is to check if it could become non-decreasing by modifying ...

  10. websocket+rabbitmq实战

    1. websocket+rabbitmq实战 1.1. 前言   接到的需求是后台定向给指定web登录用户推送消息,且可能同一账号会登录多个客户端都要接收到消息 1.2. 遇坑 基于springbo ...