《回炉重造》——集合(容器)
整体框架
绿色代表接口/抽象类;蓝色代表类。
主要由两大接口组成,一个是「Collection」接口,另一个是「Map」接口。
前言
以前刚开始学习「集合」的时候,由于没有好好预习,也没有学好基础知识,接口,类,这些基础知识都没学好,所以学到这里还是懵懵懂懂的。第一次接触到「集合」,这两个字,在我的脑海中,只浮现出数学中学过的「集合」,所以当「集合」在编程语言中出现时,我就没有绕过来。不过以我现在的视角看,也是和数学中学过的「集合」这种概念是差不多的。
数学中的「集合」:
集合是
确定的一堆东西
,集合里的东西
则称为元素。现代的集合一般被定义为:由一个或多个确定的元素所构成的整体。
Java 中的「集合」:在我的理解中,集合可以说是存放 Java 对象的东西,这个东西有人称为集合,也有人称为容器,这也是为什么我的标题写的是 集合(容器)。存放在集合中的对象,人们称为元素。
为什么会有集合的出现呢?
是这样的,在某些情况下,我们需要创建许多 Java 对象,那么这些对象应该存放在哪里?
需求是这样的:
- 可以存放对象
- 可以存放不同数据类型
- 可以存放很多对象,没有限制
那么一开始会想到数组,数组可以存放对象,是的,没错,但是,数组有它的缺点,就是一旦创建后,那么数组长度是不可变的,而且存放的对象的数据类型是固定的,所以数组不满足这些条件。此时,集合就出现了。
Java 中的集合
从上面的框架图中可以看到,主要就两个接口,分别是 Collection
和 Map
。
这两个接口都抽象了元素的存储方法,具体有什么区别呢?好吧,不说也知道,Collection
就是用来存储单一元素的,而 Map
是用来存储键值对的。
下面我将从这两个接口切入,进而开始好好地回炉重造,哈哈哈哈哈。
可以带着这些问题去回顾:
- Collection 是怎样的?Map 又是怎样的?
- 它们分别还有什么子接口?它们又有哪些实现类呢?
- 提供给我们的API又有哪些呢?具体的 API 用法和效果是怎样的呢?
Collection
Collection
是最基本的集合接口,一个 Collection
代表一组 Object
类型的对象,Java 没有提供直接实现Collection 的类,只提供继承该接口的子接口(List、Set、Queue 这些)。该接口存储一组不唯一,无序的对象。这里强调不唯一、无序,那么集合的范围就很大,想要缩小,比如唯一、有序这些,就可以通过子接口来规定,刚好,它就是这样来定义子接口的。
- List 接口:元素不唯一且有序,说明可以存储多个相同的元素,但是存储是有顺序的,即有序可重复。
- Set 接口:元素唯一且无序,说明不能存储多个相同的元素,存储的元素没有顺序,即无序不可重复。
我们再来看看 Collection 接口它抽象出来的方法有哪些。
其中,还可以看到有个以 Iterable(可迭代的)
来分类的方法,主要就是 iterator()
这个方法,即迭代器。所谓迭代器,就是用来遍历集合元素的一个东西。
/*** Returns an iterator over the elements in this collection. There are no* guarantees concerning the order in which the elements are returned* (unless this collection is an instance of some class that provides a* guarantee).** @return an <tt>Iterator</tt> over the elements in this collection*/Iterator<E> iterator();
iterator()
这个方法就是用来返回对此集合中元素的迭代器,也就是说获取该集合的迭代器。 这个抽象方法不保证迭代的顺序(除非此集合是某个提供保证的类的实例)。
再通俗一点,我们想要遍历集合的元素,那么就需要通过集合对象获取迭代器对象,通过迭代器对象来遍历集合中的元素,而且遍历的顺序是跟该集合有关的。
关于这个迭代器,后续再来讲吧。
下面开始说一下基本的接口实现类,基本的API,加上一些自己的见解,最主要是先回顾 API 的使用,毕竟还有好多知识,这些知识需要建立在我们会用的前提下,所以这里浅入浅出~
List 接口下的实现类
List
接口下的实现类有 ArrayList
、LinkedList
、Vector
、Stack
这里简要介绍下 List
接口,List
接口是一个有序的 Collection
,使用此接口能够精确的控制每个元素插入的位置,能够通过「索引」(即元素在 List 中位置,类似于数组的下标,从0开始)来访问 List 中的元素。它存储一组不唯一、有序(插入顺序)的对象。
ArrayList
我们看下 ArrayList 源码,它是这样定义的:
public class ArrayList<E> extends AbstractList<E>implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{...
}
可以看到,它
- 继承了
AbstractList
- 实现了
List
,RandomAccess
,Cloneable
,Serializable
ArrayList
是动态数组,所谓动态数组,即可以动态的修改,随时出入、删除元素,还可以动态扩容,也就是没有固定的容量限制,可以存放很多元素,直到你的内存爆炸。
初始化是这样的:
// 1. 以多态的方式写,接口不能实例化,所以通过其实现类对接口实例化。
List<E> list = new ArrayList<>();
// 2. 直接ArrayList
ArrayList<E> list = new ArrayList<>();
这两种写法有什么区别呢?
第1种写法:此时的 List 的对象 list
,可以通过这个对象调用 List 接口声明的方法,但是不能调用 ArrayList 独有的方法,换句话说,List 这个接口规定了一些抽象的方法,具体实现不关心,你可以直接调用。这里「具体实现不关心」就是说,你是使用 ArrayList 来实例化 List 接口或者使用 LinkedList 来实例化 List 接口,List 接口它都不关心,外界使用的时候,知道 List 提供这些 API 就够了。另一个角度理解,即该 list
对象拥有 List 的属性和方法,没有 ArrayList 独有的属性和方法。
第2种写法:此时 ArrayList 的对象 list
,可以调用所有方法,毕竟 ArrayList 实现了 List 接口,那么 List 有的方法,ArrayList 的对象 list
也有。
进入正题
这些 API 的使用,需要熟悉,毕竟算法题也会用到。
public void apiOfArrayList() {int idx;List<Integer> list = new ArrayList<>();// 添加元素list.add(23);list.add(30);// 根据下标(索引)获取元素idx = list.get(0);idx = list.get(1);// 更新元素值,在某个位置重新赋值list.set(1, 32);List<String> list2 = new ArrayList<>();list2.add("god23bin");list2.add("LeBron");list2.add("I love Coding");// 移除下标(索引)为2的元素list2.remove(2);// 移除指定元素list2.remove("god23bin");// 获取集合长度,遍历会用到int len = list2.size();// 判断某个元素是否在集合中,算法题会用到的boolean flag = list2.contains("god23bin");// 判断集合是否为空,算法题会用到的boolean flag2 = list2.isEmpty();}
排序:
- ArrayList 中的 sort() 方法
Random random = new Random();
List<Integer> numList = new ArrayList<>();
for (int i = 0; i < 10; ++i) {numList.add(random.nextInt(100));
}
// 将numList升序排序
numList.sort(Comparator.naturalOrder());
// 将numList降序排序
numList.sort(Comparator.reverseOrder());
- Collections 工具类的 sort() 方法
// 将numList升序排序
Collections.sort(numList);
关于 Collections 工具类的排序
看看这两个方法,这两个方法都是泛型方法。
public static <T extends Comparable<? super T>> void sort(List<T> list) {list.sort(null);
}public static <T> void sort(List<T> list, Comparator<? super T> c) {list.sort(c);
}
第一个方法需要「待排序类」实现 Comparable
接口,这样才能使用这个方法。
第二个方法需要「待排序类」有一个比较器,即 Comparator
,换句话说需要有一个比较器类实现 Comparator
接口,这样才能使用这个方法。
扯到 Comparable 和 Comparator
所以,如果你想要某个类的对象支持排序,那么你就需要让这个类实现 Comparable
接口,这个接口只有一个抽象方法 compareTo()
,我们需要实现它,它的规则是:若 当前值 较大则返回正值,若相等则返回0,若 当前值 较小则返回负值。
这里我们可以看到,Collections 是可以对 numList 进行排序的,因为这个 numList 集合的元素类型是 Integer,为什么 Integer 类型的元素支持排序?我们可以从源码中看到 Integer 是实现了 Comparable 接口的,所以 Integer 类型的元素才支持排序。
public final class Integer extends Number implements Comparable<Integer> {...public int compareTo(Integer anotherInteger) {return compare(this.value, anotherInteger.value);}public static int compare(int x, int y) {return (x < y) ? -1 : ((x == y) ? 0 : 1);}...
}
回到第一句话,如果你想要某个类的对象支持排序,那么你就需要让这个类实现 Comparable
接口,不然是不支持排序的。
下面,我这里就分别使用两种方式(实现 Comparable 或 Comparator)让某个类支持排序。
搞定 Comparable
举个栗子:我这里有一个 Game 类(待排序类,本身不支持排序,我们的任务是让 Game 具有可排序的能力),当你把多个 Game 对象放到集合中使用 Collections 这个工具类进行排序时,Collections 是不知道如何给 Game 排序的,直到 Game 实现了 Comparable 接口后,Collections 才知道 Game 该如何排序。
Game 类实现 Comparable 接口,重写 compareTo()
方法。
public class Game implements Comparable<Game> {public String name;public Double price;// 省略 getter setter 构造方法@Overridepublic int compareTo(Game o) {return comparePrice(this.price, o.price);}public int comparePrice(double p1, double p2) {return p1 > p2 ? 1 : (p1 == p2 ? 0 : -1);}
}
这样,我们就可以使用 Collections 对 Game 进行排序。
List<Game> gameList = new ArrayList<>();
gameList.add(new Game("GTA", 58.0));
gameList.add(new Game("FC", 118.0));
gameList.add(new Game("2K", 199.0));
Collections.sort(gameList); // 进行排序
System.out.println(gameList); // 打印排序结果
搞定 Comparator
同理,我这里有一个 Game 类
public class Game {public String name;public Double price;// 省略 getter setter 构造方法
}
写一个 Game 的比较器类 GameComparator
,让这个类实现 Comparator 接口,重写 compare()
方法
public class GameComparator implements Comparator<Game> {@Overridepublic int compare(Game g1, Game g2) {return g1.getPrice() - g2.getPrice();}
}
这样,我们就可以使用 Collections 对 Game 进行排序。
List<Game> gameList = new ArrayList<>();
gameList.add(new Game("GTA", 58.0));
gameList.add(new Game("FC", 118.0));
gameList.add(new Game("2K", 199.0));
Collections.sort(gameList, new GameComparator()); // 使用比较器进行排序
System.out.println(gameList); // 打印排序结果
总结排序
你可以选择两种方式(实现 Comparable 或 Comparator)中的其中一个,让某个类支持排序。
- 选择 Comparable,那么该类需要实现该接口
- 选择 Comparator,那么需要定义一个比较器,实现该接口
最后通过 Collections.sort()
进行排序。
LinkedList
LinkedList
是链表,属于线性表,学过数据结构的我们也是知道的,有指针域和数据域,虽然说 Java 里没有指针,但是有指针的思想,这里我也说不太清楚,反正是可以按指针来理解的。(如有更好的描述,欢迎帮我补充啦!)
在 Java 中,这个 LinkedList 是 List 接口下面的实现类,也是很常用的一种集合容器,算法题也会用到它。
我们看下 LinkedList 源码,它是这样定义的:
public class LinkedList<E>extends AbstractSequentialList<E>implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{...
}
可以看到,它
- 继承了
AbstractSequentialList
- 实现了
List
,Deque
,Cloneable
,Serializable
同样,LinkedList 需要掌握的方法和 ArrayList 差不多,可以说基本是一样的,只是底层实现不一样。
目前这里就不演示基本的使用方法了,你可以自己动手试试啦!
Vector
我们看下 Vector
源码,它是这样定义的:
public class Vector<E>extends AbstractList<E>implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{...
}
可以看到,它
- 继承了
AbstractList
- 实现了
List
,RandomAccess
,Cloneable
,Serializable
这样一看,它和 ArrayList 的定义,简直是一模一样。那它们之间有什么区别吗?那当然是有啦!
区别就是 Vector 是线程安全的,在多线程操作下不会出现并发问题,因为 Vector 在每个方法上都加上了 synchronized
关键字,保证多个线程操作方法时是同步的。
Stack
Stack
顾名思义,就是栈,它是 Vector 的子类,实现了标准的栈这种数据结构。
public class Stack<E> extends Vector<E> {...
}
它里面包括了 Vector 的方法,也有自己的方法。
- empty():判断栈是否为空
- peek():查看栈顶元素
- push():入栈
- pop():出栈
- search():搜索元素,返回元素所在位置
public void apiOfStack() {Stack<Integer> stack = new Stack<>();// 入栈stack.push(1);stack.push(2);stack.push(3);stack.push(4);// 获取栈的大小 == 栈中元素个数 == 栈的长度int size = stack.size();// 查看(返回)栈顶元素Integer peek = stack.peek();// 出栈stack.pop();// 判断栈是否为空boolean empty = stack.empty();// 搜索 元素1 此时栈中元素为 1 2 3,栈顶是3,栈底是1// 从栈顶往下找,第一个元素的位置记为1int search = stack.search(1);}
但是目前这个已经官方不推荐使用了,而是选择使用 LinkedList 来用作栈。
这里就要扯到队列 Queue 啦!
Queue 接口
Java 中的 Queue
是一个接口,和上面的 Stack 不同,Stack 是类。
我们看下 Queue 接口源码,它是这样定义的:
public interface Queue<E> extends Collection<E> {...
}
这个接口就抽象了 6 个方法:
- add():入队,即队尾插入元素
- offer():入队,即队尾插入元素
- peek():查看队头元素
- poll():查看队头元素
- remove():出队,即移除队头元素
- element():出队,即移除队头元素
很大的疑问来了!这些方法有什么区别??我们看看源码怎么说的,这个源码说明也不怕,下面我有翻译~
add() 和 offer() 的区别
/*** Inserts the specified element into this queue if it is possible to do so* immediately without violating capacity restrictions, returning* {@code true} upon success and throwing an {@code IllegalStateException}* if no space is currently available.** @param e the element to add* @return {@code true} (as specified by {@link Collection#add})* @throws IllegalStateException if the element cannot be added at this* time due to capacity restrictions* @throws ClassCastException if the class of the specified element* prevents it from being added to this queue* @throws NullPointerException if the specified element is null and* this queue does not permit null elements* @throws IllegalArgumentException if some property of this element* prevents it from being added to this queue*//*** 将指定的元素插入此队列(如果可以立即执行此操作而不违反容量限制),成功则返回 true* 如果容量不够,则失败,抛出 IllegalStateException*/boolean add(E e);/*** Inserts the specified element into this queue if it is possible to do* so immediately without violating capacity restrictions.* When using a capacity-restricted queue, this method is generally* preferable to {@link #add}, which can fail to insert an element only* by throwing an exception.** @param e the element to add* @return {@code true} if the element was added to this queue, else* {@code false}* @throws ClassCastException if the class of the specified element* prevents it from being added to this queue* @throws NullPointerException if the specified element is null and* this queue does not permit null elements* @throws IllegalArgumentException if some property of this element* prevents it from being added to this queue*//*** 如果可以在不违反容量限制的情况下立即将指定的元素插入到此队列中。* 使用容量受限的队列时,通常此方法是最好的入队方法 ,只有当引发异常时才可能无法插入元素。* 成功返回 true,失败返回 false*/boolean offer(E e);
所以区别就是:在容量有限制的队列中,add() 超过限制会抛出异常,而 offer() 不会,只会返回 false
remove() 和 poll() 的区别
/*** Retrieves and removes the head of this queue. This method differs* from {@link #poll poll} only in that it throws an exception if this* queue is empty.** @return the head of this queue* @throws NoSuchElementException if this queue is empty*//*** 检索并删除此队列的队头元素。 这个方法与 poll 的区别仅仅是当队列为空时删除会抛出异常。*/E remove();/*** Retrieves and removes the head of this queue,* or returns {@code null} if this queue is empty.** @return the head of this queue, or {@code null} if this queue is empty*//*** 检索并删除此队列的队头元素。如果队空,则返回 null* or returns {@code null} if this queue is empty.** @return the head of this queue, or {@code null} if this queue is empty*/E poll();
所以区别是:当队空时删除元素,那么 remove() 会抛出异常, poll() 会返回null
element() 和 peek() 的区别
/*** Retrieves, but does not remove, the head of this queue. This method* differs from {@link #peek peek} only in that it throws an exception* if this queue is empty.** @return the head of this queue* @throws NoSuchElementException if this queue is empty*//*** 队列为空时会抛出异常*/E element();/*** Retrieves, but does not remove, the head of this queue,* or returns {@code null} if this queue is empty.** @return the head of this queue, or {@code null} if this queue is empty*//*** 队列为空时会返回 null*/E peek();
所以区别是:当队空查看队头元素时,那么 element() 会抛出异常, peek() 会返回null
总的来说,就是失败的区别:
抛出异常 | 返回特殊值 | |
---|---|---|
入队 | add() | offer() 返回false |
出队 | remove() | poll() 返回 null |
查看队头元素 | element() | peek() 返回 null |
双端队列和优先级队列
Queue 有个子接口 Deque
,就是双端队列,需要掌握的 Deque 实现类为 LinkedList
和 ArrayDeque
。然后我们可以发现 Deque 这个接口抽象出来的方法,在原有的 Queue 上,多出了 First、Last 这些方法,对应着就是从队列的头部和尾部进行操作(入队、出队等等)。
有个抽象类 AbstractQueue
实现了 Queue 接口,然后 PriorityQueue 继承了 AbstractQueue
。
演示下基本的 API,大部分操作都是大同小异。
public void apiOfDeque() {// 通过 LinkedList 创建 Deque 对象Deque<Integer> deque = new LinkedList<>();// 正常队尾入队 => 完成入队后 [1,2,3]deque.addLast(1);deque.addLast(2);deque.addLast(3);// 从队头入队 => [4,1,2,3]deque.addFirst(4);// 获取队头元素 => 4// 这里 get 和 peek 的区别就是,get 如果队空会抛出异常Integer first = deque.getFirst();// 使用 offer 入队 => [4,1,2,3,5]deque.offerLast(5);// 使用 poll 出队 => [1,2,3,5]Integer integer = deque.pollFirst();// 剩下的操作也是差不多的...}
至于优先级队列的呢?之后再写啦!这个坑等着后面补回来
《回炉重造》——集合(容器)相关推荐
- 《回炉重造 Java 基础》——集合(容器)
整体框架 绿色代表接口/抽象类:蓝色代表类. 主要由两大接口组成,一个是「Collection」接口,另一个是「Map」接口. 前言 以前刚开始学习「集合」的时候,由于没有好好预习,也没有学好基础知识 ...
- javacript回炉重造之基础细节点
ascript之回炉重造 var n5=2e5 2*10的五次方 0x开头十六进制 0o开头八进制 0b开头二进制 typeof 用于检测数据类型 值类型(基本类型):字符串(String).数字(N ...
- 机器人学回炉重造(1-2):各种典型机械臂的正运动学建模(标准D-H法)
文章目录 写在前面 三连杆平面机械臂 平行四边形操作臂 闭链结构 例:平行四边形操作臂 球形臂 拟人臂 球腕 斯坦福机械臂 带球形手腕的拟人化机械臂 DLR机械臂 参考文献 写在前面 本文所有机械臂均 ...
- 机器人学回炉重造(2-4):运动学奇异位型分析
文章目录 什么是运动学奇异位型? 例子:平面二连杆机械手的奇异位型 奇异位型解耦 腕部奇异位型 手臂奇异位型 转载:6轴串联关节机器人的奇异点 参考文献 什么是运动学奇异位型? 在初步系统地了解了机器 ...
- 回炉重造之数据结构【一】基本概念
回炉重造之数据结构[一]绪论 文章目录 回炉重造之数据结构[一]绪论 数据结构的基本概念 基本概念和术语 数据结构的三要素 算法和算法评价 算法的基本概念 算法效率的度量 数据结构的基本概念 基本概念 ...
- java基础—集合框架
java基础-集合框架 JDK1.2开始引入了集合框架的概念,以弥补java中只有数组这种容器的单一问题,这些框架多数由接口构成,另外也包含了一些对于接口实现的类,其中这些接口的最上层接口为java. ...
- php byte stringbuffer,重拾java基础(十三):String姐妹StringBuffer、StringBuilder总结
重拾java基础(十三):String姐妹StringBuffer.StringBuilder总结 一.StringBuffer类概述buffer:缓冲 2. 字符串缓冲区,跟String非常相似,都 ...
- Vue回炉重造之封装防刷新考试倒计时组件
你好,我是Vam的金豆之路,可以叫我豆哥.2019年年度博客之星.技术领域博客专家.主要领域:前端开发.我的微信是 maomin9761,有什么疑问可以加我哦,自己创建了一个微信技术交流群,可以加我邀 ...
- 机器人学回炉重造(5-2):关节空间规划方法——梯形加减速(与抛物线拟合的线性函数)、S型曲线规划
文章目录 写在前面 学习代码都记录在[个人github](https://github.com/xuuyann/RobotLearningCode)上,欢迎关注~ 梯形加减速(与抛物线拟合的线性函数) ...
- 真人电影中的幻想生物迷墙:索尼克为什么被骂到回炉重造?
<大侦探皮卡丘>上映在即,当网友们对着雷佳音配音的皮卡丘大呼好萌好萌时,我们仿佛又来到了大型真香现场--明明在几个月之前,当人们看到毛茸茸的大叔音皮卡丘时还每个细胞都充满了拒绝. 也有一种 ...
最新文章
- 天了噜,Java 8 要停止维护了!
- CNN实操记录(goal:一日一更新)
- C语言之do_while输出菱形
- G-Sensor 校准标准
- 关于Mysql修改密码的方法汇总
- HTML5 怎么自定义字体
- 【洛谷试炼场】洛谷新手村——洛谷的第一个任务
- 强制卸载pip以及安装老版本pip
- 速卖通关键词挖掘工具_谷歌优化关键词挖掘工具大全
- font-style 属性 oblique 是什么意思
- 目前主要的计算机汉字输入方法是什么,[计算机汉字输入方法.ppt
- kafka-eagle详细安装配置图文教程
- 电线电缆使用时的安全要求和存放方法
- SSH Tunnel 使用
- Android友盟第三方登录
- Generator 快速理解
- 在线教育,百鬼夜行?
- 分享一款开箱即用的qss暗黑主题
- 日元负利率和美元暴跌,是对人民币的夹击
- 本科计算机专业英语要过几级,计算机英语必须过几级
热门文章
- win10计算机怎么打开方式,Win10如何还原打开方式?还原打开方式的方法
- 在线qq的html代码,网页QQ
- 适配IE浏览器的那些坑
- 关于URL中带空格的问题
- D. Lizard Era: Beginning(折半搜索)
- 分时电价模型,削峰填谷,转移24小时一天中用电率,减少谷峰差
- python怎么筛选并列关系数据_一文带你学会如何用Python生成带误差棒的并列和堆积柱状图...
- IE11下载文件时,文件扩展名自动由点改为下划线
- 一个30岁转行程序员的心路历程
- 全网首发ai绘画小程序基于novelai