集合框架
集合的概念
-
对象的容器,实现了对对象常用的操作方法,可实现数组的功能
-
和数组的区别:
(1)数组的长度固定,集合长度不固定
(2)数组可以存储基本类型和引用类型,集合只能引用类型
Collection接口
无序,无下标,无顺序,重点是它的跌打器Iterator JDK1.8中 增加了一个新的方法 可以直接用lambda 表达式去遍历每一个元素 这样就不用每次判断 是否hasNext();
具体跟Spliterators 接口有关 ,有时间研究一下
forEachRemaining(Consumer<?super E> action) 对每个剩余元素执行给定的操作,直到所有元素都被处理或动作引发异常;
简单使用 这里以List的迭代器为例
List<String> stringList = new ArrayList<>();
stringList.add("one");
stringList.add("two");
stringList.add("three");
stringList.add("four");
stringList.add("five");
stringList.listIterator().forEachRemaining(String -> System.out.println(String +" "));
List的接口与实现类
主要方法
List 有序 有下标 可以重复 两个子类 ArrayList(),LinkedList();
**subList(int fromIndex,int toIndex)**从原有的集合中截取一个新的集合 包含下标为fromIndex的元素 以及fromIndex和toIndex之间的元素,不包含toIndex
重点是list的遍历ListIterator 同Iterator不一样,可以反向遍历,并且替代指定位置的元素(set)
提供的方法有
-
-
add(E e)
将指定的元素插入列表(可选操作)。boolean
hasNext()
返回true
如果遍历正向列表,列表迭代器有多个元素。boolean
hasPrevious()
返回true
如果遍历反向列表,列表迭代器有多个元素。E
next()
返回列表中的下一个元素,并且前进光标位置。int
nextIndex()
返回随后调用next()
返回的元素的索引。E
previous()
返回列表中的上一个元素,并向后移动光标位置。int
previousIndex()
返回由后续调用previous()
返回的元素的索引。void
remove()
从列表中删除由next()
或previous()
返回的最后一个元素(可选操作)。void
set(E e)
用 指定的元素替换由next()
或previous()
返回的最后一个元素(可选操作)。
-
实现类
ArrayList : 由数组结构实现,查询快,增删慢,线程不安全。
源码分析:
-
DEFAULT_CAPACITY 默认容量 10 但是如果没有向集合中添加元素 容量是0 添加任意一个元素之后 容量是10
-
elementData :存放元素的数组;
-
底层集合的添加,删除方法都是对数组的操作,源码里有一个方法是数组的扩容grow(),每次扩容后的大小是原数组的1.5倍
LinkedList:双向链表结构实现,增删快,查询慢。
链表的基本概念:**链表是一系列存储数据元素的单元通过指针串联起来形成的,因此每个单元至少有两个作用域,一个域用于存储数据元素,另一个指向其他单元的指针,然后它叫做 节点 **(链表的第一个节点和最后一个节点,分别称为链表的头节点和尾节点)
双向链表:由节点组成,它的每个数据节点中都有两个指针,一个是指向前一个节点和后一个节点
源码分析:
transient int size = 0; //集合大小
/**
* Pointer to first node.
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
transient Node<E> first; //头节点
/**
* Pointer to last node.
* Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
transient Node<E> last; //尾节点
/**
* Constructs an empty list.
*/
public LinkedList() { //构造
}
Node节点
private static class Node<E> {
E item; //元素
Node<E> next; //指向下一个节点
Node<E> prev; //指向上一个节点
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
add() 源码分析
/**
* Links e as last element.
*/
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
添加第一个元素的时候 创建一个Node节点 既是last 也是 first ,当再添加元素的时候 final Node l = last; l 就等于last
所以final Node newNode = new Node<>(l, e, null) 看一下 Node的构造方法
所以新节点newNode的上一个节点prev指向的就是last
每新add一个元素 就是last
last = newNode;
尾节点就是newNode
临时变量l不为空,所以**l.next = newNode;**这个时候l是firstNode 所以它的next指向新节点 node 也是lastNode
总结:第一次添加的时候 头尾指向的是一个节点,第二次 。。。n次添加的时候,用一个临时节点=尾节点,尾节点等于新节点同时新节点的prev指向的是临时节点,临时节点不为空,所以它的下一个节点指向的就是尾节点。(有点绕)
至于删除 只需要找到要删除的节点,重新指向一下节点即可;不像数组要重新排列
泛型和工具类
什么是泛型?
把类型明确的工作推迟到创建对象或者调用方法的 时候才去明确的特殊类型。其本质是参数化类型,把类型作为参数传递。常见的形式有泛型类,泛型接口,泛型方法。
为什么需要泛型?
早期Java是使用Object来代表任意类型的,但是向下转型有强转的问题,这样程序就不太安全
首先,我们来试想一下:没有泛型,集合会怎么样
- Collection、Map集合对元素的类型是没有任何限制的。本来我的Collection集合装载的是全部的Dog对象,但是外边把Cat对象存储到集合中,是没有任何语法错误的。
- 把对象扔进集合中,集合是不知道元素的类型是什么的,仅仅知道是Object。因此在get()的时候,返回的是Object。外边获取该对象,还需要强制转换
有了泛型以后:
- 代码更加简洁【不用强制转换】
- 程序更加健壮【只要编译时期没有警告,那么运行时期就不会出现ClassCastException异常】
- 可读性和稳定性【在编写集合的时候,就限定了类型】
泛型类
使用泛型可以创建变量,作为方法的参数,以及作为方法的返回值,T不能实例化,泛型不能相互复制
泛型只能使用引用类型;
泛型接口
不能使用静态常量
泛型方法
public static <T> T show(T t){
return t;
}
泛型好处
提高代码的重用性,防止类型转换异常,提高代码安全性ClassCastException
泛型集合
参数化类型,类型安全的集合,强制集合元素类型一致
优点:编译时检查,访问时不必类型转换,不同泛型之间引用不能互相赋值,泛型不存在多态。
Set接口与实现类
无序,无下标,元素不可重复,允许空值全部继承Collection的方法
-
HashSet
- 基于HashCode实现元素不可重复,以及存放位置;
- 当存入元素的哈希码相同时,会调用equals进行确认,如果为true,则拒绝后者存入。
- 判断元素是否重复基于hashcode和equals方法,所以我们在用hashSet的时候需要重写equals()方法
- 数组+链表的形式
- HashSet底层就是利用的HashMap
-
TreeSet 红黑树
- 基于排列顺序实现元素不可重复
- 实现了sortedSet接口,对集合元素自动排序
- 元素对象的类型必须实现Comparable接口,指定排序规则,
- 通过ComparableTo方法确定是否为重复元素’; 实现Comparable接口重写ComparableTo方法 ;
- 也可在创建的时候指定比较规则
TreeSet<User> treeSet = new TreeSet<>(new Comparator<User>() { @Override public int compare(User o1, User o2) { //比较规则 return 0; } });
Map接口与实现类
-
**HashMap **
存储任意的键值对,K:无序 无下标,不允许重复 V:无序 无下标 允许重复;
除了增删查清空的方法之外,重点entrySet() keySet() 返回set集合;
keySet() 获取所有的key值;
**entrySet()**获取所有的key value值封装成一个entry (Map.entry)类型 一个entry就是一个键值对;
默认初始容量16 允许key value 为null;
数组+链表+红黑树;
HashMap的key值 通过hashCode和equals方法判定和set一样;
源码分析:
- HashMap刚创建时,table是null,节省空间,当添加第一个元素时,table容量调整为16
- 当元素个数大于阈值(16*0.75 = 12)时,会进行扩容,扩容后的大小为原来的两倍,目的是减少调整元素的个数
- jdk1.8 当每个链表长度 >8 ,并且数组元素个数 ≥64时,会调整成红黑树,目的是提高效率
- jdk1.8 当链表长度 <6 时 调整成链表
- jdk1.8 以前,链表时头插入,之后为尾插入
HashTable
线程安全,运行效率慢;不允许null作为key或是value
properties
Properties
类表示一组持久的属性。Properties
可以保存到流中或从流中加载。 属性列表中的每个键及其对应的值都是一个字符串。
-
SortedMap 接口
-
TreeMap 实现类 元素需要实现Comparable接口 重写比较规则 TreeSet类似
实现了SortedMap接口(是map的子接口),可以对key自动排序
-
-
Collections 工具类
也可以通过Comparator 重写排序规则;
创建安全的集合