集合概念
集合,有时也称作容器(Container), 是对象的持有者,它们可以有助于高效访问的方式存储的组织对象。以生活中的案例为例: 集合就像装衣服的柜子,衣服就是集合中的元素。
为什么使用集合
在最开始我们使用变量存储数据,为了存储多个数据我们引入了数组的概念。但是数组的由于长度固定造成了我们在添加数据但是未知大小时存储很困难。为了解决这个问题,我们引如集合概念,集合是一类更高级的数据结构封装,灵活使用集合可以有效的规避数组的一些问题,提高开发效率。
集合框架图
Collection中每次操作的都是一个对象,如果要操作一对对象就必须使用Map,Map中所有的对象都按照key->value形式保存,也称二元偶对象。Iterator(迭代器)不是一个集合,它是一种用于访问集合的方法。Collections 类是 Java 提供的一个操作 Set、List 和 Map 等集合的工具类。
泛型
泛型概念
把类型明确的工作推迟到创建对象或调用方法的时候才去明确的特殊的类型。
泛型一般表示为<E>
例如:
public class ArrayList<E> implements List<E>{
}
这个ArrayList类中用到的类,用E替代(泛型), 当创建对象时才明确它的类型。
new ArrayList<String>(); //创建对象时泛型明确为String类型
为什么使用泛型?
-
使代码更加简洁
-
使程序更加健壮
-
提高可读性和稳定性
程序案例:
List<User> users = new ArrayList<>();
User user = new User();
users.add(user);
如程序演示:使用泛型定义了对象使用类型为Dog类型,如果使用其它类型编译器检查则会报错,早检查出错增强程序的健壮性。
泛型的擦除
java的一个特性,在运行时,java不会保留泛型。
Java 泛型的参数只可以代表类,不能代表个别对象。由于Java泛型的类型参数之实际类型在编译时会被消除,所以无法在运行时得知其类型参数的类型。Java 编译器在编译泛型时会自动加入类型转换的编码,故运行速度不会因为使用泛型而加快。
泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。 Java语言引入泛型的好处是安全简单。
泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,以提高代码的重用率。
通配符泛型
无边界通配符: 例如List<?> , ? 表示不确定的java类型。
固定上边界通配符: 例如List<? extends Car> ,表示指定类的子类,Car即为上边界。
固定下边界通配符: 例如List<? super Car>,表示Car的父类,Car即为下边界。
Collection接口
The root interface in the collection hierarchy。
集合层次结构的根接口。定义了集合的常用api。
List接口
List接口是Collection接口的子接口。有序的集合,存储元素和取出元素的顺序是一致的(存储abc则取出abc)有索引,包含了一些带索引的方法允许存储重复的元素。
ArrayList
ArrayList类特性
底层由动态数组构成,即数组的长度可变。
ArrayList的类层次结构
ArrayList的属性
数组的初始长度为10
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA ={};
//第一次添加元素时知道该 elementData 从空的构造函数还是有参构造函数被初始化的。以便确认如何扩容。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA ={};
transient Object[] elementData;// non-private to simplify nested class access
private int size;
//数组最大的分配容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
构造方法
无参构造方法
当使用默认构造方法时,对象数组指向DEFAULTCAPACITY_EMPTY_ELEMENTDATA容量为0,当第一次添加数据时,将数组的长度扩展为默认大小DEFAULT_CAPACITY。
源码分析:
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
有参构造方法 初始化数组的长度
源码分析:
public ArrayList(int initialCapacity) {
if(initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
}else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
}
}
添加元素
add(E e):添加 元素
List\<Integer> list = new ArrayList<>();
list.add(1);
源码分析:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // 确认容量是否足够
elementData[size++] = e;
return true ;
}
确认最小空间
private void ensureCapacityInternal( int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
初始化数组空间大小
private static int calculateCapacity(Object[] elementData, int minCapacity){
//如果是空数组,则将数组长度变为DEFAULT_CAPACITY=10;
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY\*, minCapacity);
}
return minCapacity;
}
实现数组的扩容处理
private void ensureExplicitCapacity(int minCapacity) {
//如果当集合容量+1 > 数组的长度,那么数组要进行扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); //扩容1.5倍
if (newCapacity - minCapacity < 0) newCapacity = minCapacity;
if (newCapacity - *MAX_ARRAY_SIZE\* > 0) //超过了数组的最大指
newCapacity = *hugeCapacity*(minCapacity);
elementData = Arrays.*copyOf*(elementData, newCapacity); //数组扩容
}
删除元素remove(int index),根据位置删除:
list.remove(1); //删除元素-根据位置删除
源码分析:
public E remove(int index) {
......
E oldValue = elementData(index); //删除之前获取旧数据
int numMoved = size - index - 1; //要移动的数组元素数量
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; //将最后一个元素置为null,数组容量-1
return oldValue;
}
删除元素:根据对象内容删除
删除的对象需要实现equals()方法
boolean remove(Object o) //根据对象删除
List<String> list = new ArrayList<>();
list.add("apple");
list.add("banana");
list.add("orange");
list.remove("banana");
源码分析:
我们发现删除时需要调用元素的equals()方法。
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {//使用对象的equals方法比较
fastRemove(index);
return true;
}
}
return false;
}
集合中的类型是String,则使用String中的equals()方法,如果为自定义类型,需要重写对象的equals()方法。
/**
* String类中重写了equals()方法
*/
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
删除元素源码分析:
private void fastRemove(int index) {
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,numMoved);
elementData[--size] = null; // clear to let GC do its work
}
修改元素
E set(int index, E e) 修改元素\
list.set(1, "orange");
源码分析:
public E set(int index, E element) {
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
查询元素
E get(int index) 查询元素
list.get(3);
源码分析:
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
LinkedList
什么是链表?
逻辑上连续,空间上不联系,如下图车厢和前后两个车厢相连(双向链表)。
如何定义链表(双向链表)
定义节点
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;
}
}
2头节点即链表的第一个节点。
transient Node<E> first;
尾节点即链表的最后一个节点
transient Node<E> last;
设计双向链表(LinkedList的实现)
boolean add(E e) //添加节点
源码分析:
public boolean add(E e) {
linkLast(e);
return true;
}
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++;
:
}
boolean remove(Object o) 删除节点
源码分析:
public boolean remove(Object o) {
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
E unlink(Node<E> x) { //x为要删除的节点对象
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
if (prev == null) { //删除头节点
first = next;
} else {
prev.next = next;//删除节点的前一个节点连接后一个节点
x.prev = null;
}
if (next == null) {//删除尾节点
last = prev;
} else {
next.prev = prev;//删除节点的后一个节点连接前一个节点
x.next = null;
}
x.item = null;
size--;
return element;
}
E set(int index, E element) 修改节点信息
源码分析:
public E set(int index, E element){
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
public E get(int index) 查询某个节点信息
源码分析:
public E get(int index) {
return node(index).item;
}
Node<E> node(int index) {
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++) x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--) x = x.prev;
return x;
}
}
Vector类
Vector 类实现了一个动态数组。。
Vector的类的层次结构
public class Vector<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
Vector的api
public synchronized boolean add(E e):添加
public synchronized boolean add(E e) {
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
public synchronized E remove(int index): 删除
public synchronized E set(int index, E element):修改
public synchronized E get(int index):查询
程序案例:
Vector<String> v = new Vector<>();
v.add("apple");
v.add("orange");
v.add("banana");
v.remove(1);
v.set(0, "strawberry");
String f = v.get(1);
System.*out\*.println(f);
System.*out\*.println(v);
程序运行结果:
banana
[strawberry, banana]
Iterator接口实现元素遍历
迭代器是一种设计模式,它是一个对象,它可以遍历并选择序列中的对象,而开发人员不需要了解该序列的底层结构。迭代器通常被称为“轻量级”对象,因为创建它的代价小。
程序案例:
List<String> l = new ArrayList();
l.add("apple");
l.add("orange");
l.add("banana");
for (Iterator<String> iter = l.iterator(); iter.hasNext();) {
String str = (String) iter.next();
System.*out\*.println(str);
}
Stack类
限定仅在表尾进行插入或删除操作的线性表,表尾—栈顶,表头—栈底,不含元素的空表称空栈。
\
类的结构
Stack常用API
程序案例:
Stack<Integer> stack = new Stack<>();
stack.push(6);
stack.push(2);
stack.push(5);
stack.push(1);
Queue接口
队列(简称作队)也是一种特殊的线性表,队列的数据元素以及数据元素间的逻辑关系和线性表完全相同,其差别是线性表允许在任意位置插入和删除,而队列只允许在其一端进行插入操作在其另一端进行删除操作。队列中允许进行插入操作的一端称为队尾,允许进行删除操作的一端称为队头。队列的插入操作通常称作入队列,队列的删除操作通常称作出队列。
![img](https://img-blog.csdnimg.cn/img_convert/6ad903a6bff6d8febd8f911389993599.jpeg)
接口结构
Queue常用API
Set接口
set接口是继承自Collection的子接口。
Set接口特性
特点是元素不重复,存储无序。在set接口的实现类中添加重复元素是不会成功的,判断两个元素是否重复根据元素类重写的hashCode()和equals()方法。
Set接口的实现类 HashSet
常用api:
boolean add(E e);
boolean remove(Object o);
/*底层使用map,删除key*/
public boolean remove(Object o) {
return map.remove(o)==*PRESENT\*;
}
其它Set接口的实现类
TreeSet<E>
LinkedHashSet<E>