【JavaSE 集合框架的继承体系】
一、集合框架
1.集合框架由来
JDK 1.2 版本后,出现集合框架,到 JDK1.5 后,大幅度优化。
①集合本质上是存储对象的容器
②数组也能存储对象,数组的弊端就是定长(限制长度)
③为了解决数组的问题,开发出来集合框架,集合框架无需考虑长度
- 集合和数组的区别与共同点:
- 集合,数组都是容器,都可以存储数据
- 集合只存储引用数据类型,不存储基本数据类型
- 数组可以存储基本类型,也可以存储引用类型
- 数组定长,集合容器可以变长
牢记:数据多了存数组,对象多了存集合
- 集合学习的关键点:
- 怎么存储数据
- 怎么取出数据
- 选择哪种容器
2.集合框架的继承体系
(1)第一个派系:
Collection (集合)
顶级接口 单列集合(一次只能存一个对象)List (列表)
接口ArrayList (数组列表)
实现类LinkedList (链表)
实现类Vector (数组列表)
实现类,已过时
Set (集合)
接口HashSet (哈希表)
实现类LinkedHashSet (链表哈希表)
实现类,继承自HashSet
TreeSet (红黑树)
实现类
(2)第二个派系:
Map (映射键值对)
顶级接口 双列集合(一次可以存两个对象)HashMap (哈希表)
实现类LinkedHashMap (链表哈希表)
实现类,继承自HashMap
TreeMap (红黑树)
实现类Hashtable (哈希表)
实现类,已过时Properties (哈希表)
实现类,继承自Hashtable
ConCurrentHashMap (哈希表)
线程相关
(3)存在一个独立的接口
Iterator
迭代器接口(用来做遍历)
(4)泛型
- 泛型
Generic
- 写法
- 泛型类 ,泛型方法,泛型接口,泛型限定,泛型通配符
(5)增强型循环
for(:)
循环
二、 第一个派系:Collection 接口(单列集合)
是所有单列集合的顶级接口,任何单列集合都是他的子接口或者是实现类,该接口中定义的方法,是所有单列集合的共性方法。
使用接口 Collection 的实现类 ArrayList
,创建对象。
Collection<E>
尖括号就是泛型,(这是 API 文档中的写法)自己编程时 E 处要写,集合存储的数据类型
(二.1)Collection 接口的常用方法:
方法的定义 | 方法作用 |
---|---|
boolean add(E) | 元素添加到集合 |
void clear() | 清空集合容器中的元素 |
boolean contains(E) | 判断元素是否在集合中 |
boolean isEmpty() | 判断集合的长度是不是0,如果是0返回 true |
int size() | 返回集合的长度,集合中元素的个数 |
boolean remove(E) | 移除集合中指定的元素,移除成功返回 true |
T[ ] toArray(T[ ] a) | 集合转成数组 |
Collection 接口的常用方法,使用ArrayList
实现类创建对象:
(1)boolean add(E)
/**
* boolean add(E) 元素添加到集合中
* 返回值,目前都是true
*/
public static void collectionAdd(){
// 建议使用接口多态创建集合容器对象,存储的数据类型是字符串
Collection<String> coll = new ArrayList<>(); // 后面尖括号中的数据类型可以不写,但是如果要写前后的数据类型必须一致
// 注意:集合不能存储基本数据类型
// 集合对象的方法 add 添加元素
coll.add("hello");
coll.add("world");
coll.add("java");
coll.add("apple");
coll.add("banana");
/**
* 输出语句中,输出集合对象,调用的是方法 toString()
* 看到的内容是一个完整的字符串,这种操作不叫遍历
*/
System.out.println(coll);
}
(2)void clear(), int size(), boolean isEmpty()
/**
* void clear() 清空集合中的所有元素
* int size() 集合的长度
*/
public static void collectionClear(){
Collection<Integer> coll = new ArrayList<>(); // 可以写包装类,但是不能写 int
coll.add(1);
coll.add(2);
coll.add(3); // 具备自动装箱的操作
System.out.println(coll);
System.out.println("集合的长度::"+ coll.size()); //长度
coll.clear();
System.out.println(coll); // 里面的元素被清空了,但是集合本身仍然存在
System.out.println("集合的长度::"+ coll.size());
System.out.println("集合是空吗?" + coll.isEmpty()); //长度=0,isEmpty() 返回 true
}
(3)boolean contains(), boolean remove()
/**
* boolean contains(E) 判断是否包含
* boolean remove(E) 移除元素
*/
public static void collectionContains(){
// 接口多态创建集合容器对象,存储的数据类型是字符串
Collection<String> coll = new ArrayList<>();
// 集合对象的方法 add 添加元素
coll.add("hello");
coll.add("wife");
coll.add("world");
coll.add("java");
coll.add("apple");
coll.add("banana");
// 判断集合中是否包含某个元素
boolean b = coll.contains("world");
System.out.println("b = " + b);
// 移除集合中的元素
// 删除成功返回 true,如果有多个相同的对象,删除最先遇到的那个
boolean b1 = coll.remove("banana"); // 写一个本来就没有的元素,删不掉就是 false
System.out.println("b1 = " + b1);
System.out.println(coll);
}
(二.2)Collections 工具类:
- java.util.Collection 集合的顶级接口
- java.util.Collections 操作集合的工具类
- 工具类的方法全部静态方法,类名直接调用
- 主要是操作 Collection 系列的单列集合,少部分功能可以操作 Map 集合(双列集合)
/**
* 集合操作的工具类:Collections
* 工具类有组方法:synchronized 开头
* 操作过程:类名调用传递集合,返回集合
* 功能作用:传递的集合,返回后,变成了线程安全的集合
*/
public class CollectionsTest {
public static void main(String[] args) {
binarySearch();
shuffle();
sort01();
sort02();
}
// 1.集合的二分查找
public static void binarySearch(){
List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(5);
list.add(9);
list.add(15);
list.add(20);
list.add(25);
int index = Collections.binarySearch(list, 15);
System.out.println(index);
}
// 2.集合元素的随机交换位置
public static void shuffle(){
List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(15);
list.add(5);
list.add(20);
list.add(9);
list.add(25);
System.out.println("list = " + list);
Collections.shuffle(list);
System.out.println("list = " + list);
}
// 3.集合元素的排序
public static void sort01(){
List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(15);
list.add(5);
list.add(20);
list.add(9);
list.add(25);
System.out.println("list = " + list);
Collections.sort(list);
System.out.println("list = " + list);
}
// 4.集合元素的排序,逆序
public static void sort02(){
List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(15);
list.add(5);
list.add(20);
list.add(9);
list.add(25);
System.out.println("list = " + list);
// Collections.reverseOrder() 用来逆转自然顺序
Collections.sort(list,Collections.reverseOrder());
System.out.println("list = " + list);
}
}
1. Collection 下的 List 接口(列表)
List 接口,继承 Collection 接口,是单列集合
(1)List 接口的特点
- 这个接口的集合都具有索引
- 这个接口中的元素允许重复
- 这个接口中的元素是有序的
- 元素不会排序,有序指的是:元素存储和取出的顺序是一致的
List 接口的所有实现类,都具有以上三个特征
(与 List 接口并列存在的 Set 接口,基本上特点是相反的,Set 接口的集合没有索引,元素不允许重复,接口中的元素根据实际有些有序有些无序。)
(2)List 接口自己(独有)的方法 (带有索引)
① add(int index ,E e)
/**
* List接口的方法 add(int index, E e)
* 指定的索引位置,添加元素
*
* IndexOutOfBoundsException (继承自RuntimeException 异常) 集合越界异常 集合的长度是size()
* 子类:①StringIndexOutOfBoundsException 字符串越界异常 字符串的长度是 length()
* ②ArrayIndexOutOfBoundsException 数组越界异常 数组的长度是 length
*/
public static void listAdd(){
List<String> list = new ArrayList<>();
list.add("a") ; // 这样是在集合的尾部添加
list.add("b");
list.add("c");
list.add("d");
list.add("e");
System.out.println(list); // 按照输入的顺序输出
// 指定的索引上,添加元素,3 索引上添加元素
list.add(3,"QQ"); // 如果超出本来的索引数就会出现异常
System.out.println(list); // 添加后按照索引,依次输出
}
② get(int index)
/**
* List 接口的方法 E get(int index)
* 返回指定索引上的元素
* List 集合可以使用 for 循环像数组一样的方式遍历
*/
public static void listGet(){
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
// List 接口方法 get 取出元素
// String s = list.get(3);
// System.out.println(s);
for(int i = 0 ; i < list.size() ; i++){
System.out.println(list.get(i));
// 已经有了索引后,就和数组的遍历一样
}
}
③ set(int index,E e),remove(int index)
/**
* List接口方法
* E set (int index , E e) 修改指定索引上的元素,返回被修改之前的元素
* E remove(int index) 移除指定索引上的元素,返回被移除之前的元素
*/
public static void listSetRemove(){
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
System.out.println(list);
// 修改指定索引上的元素,修改 3 索引
String str = list.set(3,"https://www.baidu.com");
System.out.println(list);
System.out.println(str);
// 删除指定索引上的元素,删除 3 索引
str = list.remove(3);
System.out.println(list);
System.out.println(str);
}
(3)List 集合的特有迭代器
List 接口中的方法
listIterator()
返回迭代器,迭代器的接口是 ListIterator ,集合的专用迭代器
- ListIterator 迭代器接口的方法:
boolean hasNext()
E next()
boolean hasPrevious()
判断集合中是否有上一个元素,反向遍历E previous()
取出集合的上一个元素
/**
* List 接口的方法:
* listIterator() List 集合的特有迭代器
* 反向遍历
*/
public static void iterator(){
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
// 获取特有迭代器接口实现类对象
ListIterator<String> lit = list.listIterator();
// 由于集合遍历中的指针只有一个,需要让指针到最后位置,所以先要正向遍历
while (lit.hasNext()){
String s = lit.next();
System.out.println(s);
}
// 反向遍历
// 判断上一个元素
while (lit.hasPrevious()){
//取出元素
String s = lit.previous();
System.out.println(s);
}
// 反向遍历并不实用
}
(4) List 接口的实现类的数据结构
链表结构
(存储数据时,都会放入三个节点中间的地方,起初的节点数据为 null ,要在之后再次输入数据就会在后一个节点中输入下一个元素的地址,再次在新地址中重复操作,存储到最后的节点数据也为 null,删除其中的一个数据,大体结构不会变化,就让要删除元素的上一个节点存储要删除元素的下一个的对象地址。)
数组与链表的对比:
- 数组:
- 有索引,数组中元素的地址是连续,查询速度快
- 数组的长度为固定的,实现扩容需要新创建数组,要通过数组元素的复制,增加或者删除堆内存中的数据导致效率慢
- 链表:
- 链表没有索引,采用对象之间内存地址记录的方式存储
- 查询元素,必须通过第一个节点依次查询,查询性能慢
- 增删元素,不会改变原有链表的结构,速度比较快
1.1 List 接口的实现类 ArrayList(数组列表)
ArrayList 集合,其内部定义了一个数组,数组的名字叫做: elementDate ,默认初始化长度为 10,一旦超过长度 10,长度就会翻1.5倍,拷贝原数组中的值进入新数组,从而扔掉原数组,但是不能减容量,如果要删除中间的某个元素,就会把之后的元素复制粘贴前进到新的位置,但是它的数组长度不是元素存储的长度(10),而是其中所包含的的元素个数
ArrayList()
一旦被创建(new),就会构造一个初始容量为十的空列表,其中有一个计数器,一旦通过该类调用size
计数器方法就会返回输入元素的个数
(1) ArrayList 集合的特点
ArrayList 类实现接口 List,ArrayList 具备了 List 接口的特性: (有序、重复、索引)
- ArrayList 集合底层的实现原理是数组,大小可变(存储对象的时候长度无需考虑)。
- 数组的特点:查询速度快,增删慢。
- 数组的默认长度是10个,每次的扩容是原来长度的1.5倍。
- ArrayList 是线程不安全的集合,但是运行速度快。(API 文档中显示是:不同步)
(2) ArrayList 源码解析
① ArrayList 类的成员变量
private static final int DEFAULT_CAPACITY = 10; // 默认容量
private static final Object[] EMPTY_ELEMENTDATA = {
}; // 空数组(被 final 修饰不可改变)
transient Object[] elementData; // ArrayList 集合中的核心数组(实际在这里面存数据,可以改变长度)
private int size; // 记录数组中存储个数
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; // 数组扩容的最大值
② ArrayList 集合类的构造方法
// 无参数构造方法
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {
}; // 数组没有长度
// 有参数的构造方法
public ArrayList(int 10) {
if (initialCapacity > 0) {
// 创建了一个长度为10的数组
this.elementData = new Object[10];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
③ ArrayList 集合类的方法 add()
// main 方法中使用
public static void main(String[] args) {
new ArrayList<>().add("abc"); // 集合中添加元素
}
// 方法 add()
public boolean add("abc") {
// 只要向数组中存数据,检就要检查容量:(1),安全保障
ensureCapacityInternal(size + 1); // 总保障容量加一,保障安全
// abc 存储到数组中,存储数组0索引,size 计数器++
elementData[size++] = "abc"; // 数组扩容为10
return true;
}
Ⅰ、add()
中用到的: ensureCapacityInternal()
方法的源码:(嵌套着calculateCapacity()
方法)
// 检查集合中数组的容量,参数是1(方便理解),源码是(int minCapacicy)最小容量
private void ensureCapacityInternal(int minCapacity = 1) {
// 传入 1
// calculateCapacity 计算容量,方法的参是数组, 1
// ensureExplicitCapacity (10) 用来扩容
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
calculateCapacity()
方法的源码:
// 计算容量的方法,返回10
private static int calculateCapacity(Object[] elementData, int minCapacity = 1) {
// 存储元素的数组 == 默认的空的数组 构造方法中有赋值
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 两个值中返回最大值 max(10,1)
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
Ⅱ、add()
中用到的:ensureExplicitCapacity()
方法的源码:(嵌套着 grow()
方法)
//扩容
private void ensureExplicitCapacity(int minCapacity = 10) {
// 传入 10
// 私有的方法
modCount++; // 记录集合被操作了多少次
// 10 - 数组的长度(就是0) > 0
if (minCapacity - elementData.length > 0)
// grow方法(10) 用来数组增长
grow(minCapacity);
}
grow()
方法的源码:
// 增长的方法,参数是(10)
private void grow(int minCapacity = 10) {
// 传入 10
// 变量 oldCapacity 保存原有数组的长度(为零)
int oldCapacity = elementData.length; // 原有的数组容量为 0
// 新的数组容量 = 原有数组的容量 + (原有数组的容量 / 2)
int newCapacity = oldCapacity + (oldCapacity >> 1); // 新的数组容量为0 位移
// 0 - 10 < 0 新容量-计算出的容量
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity; // 新的数组容量变为10
// 判断是否超过最大容量
if (newCapacity - MAX_ARRAY_SIZE > 0) // 判断是否超过最大容量,如果超过最大容量,就直接赋值为最大容量
newCapacity = hugeCapacity(minCapacity);
// 数组的复制,原始数组和新的容量
elementData = Arrays.copyOf(elementData, newCapacity);
}
1.2 List 接口的实现类 LinkedList(链表)
(1) LinkedList 集合的特点
LinkedList 类实现接口 List,LinkedList 具备了 List 接口的特性 (有序,重复,索引)
- LinkedList 底层实现原理是链表,双向链表
- LinkedList 增删速度快
- LinkedList 查询速度慢
- LinkedList 是线程不安全的集合,但是运行速度快
(2) LinkedList 集合的特有方法
集合是链表的实现,可以单独操作链表的开头元素和结尾元素
void addFirst(E e)
元素插入到链表开头void addLast(E e)
元素插入到链表结尾E getFirst()
获取链表开头的元素E getLast()
获取链表结尾的元素E removeFirst()
移除链表开头的元素E removeLast()
移除链表结尾的元素void push(E e)
把元素推入堆栈中E pop()
把元素从堆栈中弹出
LinkedList 不能使用多态性(会出现一些问题),要使用纯子类。
public static void main(String[] args) {
linkedAdd();
linkedGet();
linkedRemove();
linkedPushPop();
}
// 1.void addFirst(E e) 元素插入到链表开头
// 2.void addLast(E e) 元素插入到链表结尾
public static void linkedAdd(){
LinkedList<String> linkedList = new LinkedList<String>();
linkedList.add("a"); // 结尾添加
linkedList.add("b"); // 结尾添加
linkedList.add("c"); // 结尾添加
linkedList.add("d"); // 结尾添加
System.out.println("linkedList = " + linkedList);
// 结尾添加
linkedList.addLast("f");
linkedList.add("g"); // 再次添加 "g" 在 "f" 之后
// 开头添加
linkedList.addFirst("e");
System.out.println("linkedList = " + linkedList);
}
// 3.E getFirst() 获取链表开头的元素
// 4.E getLast() 获取链表结尾的元素
public static void linkedGet(){
LinkedList<String> linkedList = new LinkedList<String>();
linkedList.add("a"); // 结尾添加
linkedList.add("b"); // 结尾添加
linkedList.add("c"); // 结尾添加
linkedList.add("d"); // 结尾添加
System.out.println("linkedList = " + linkedList);
// 获取开头元素
String first = linkedList.getFirst();
// 获取结尾元素
String last = linkedList.getLast();
System.out.println(