一、前言
Java 集合框架主要包括两种类型的容器,一种是集合(Collection),存储一个元素集合,另一种是图(Map),存储键/值对映射。Collection 接口又有 3 种子类型,List、Set 和 Queue,再下面是一些抽象类,最后是具体实现类,常用的有 ArrayList、LinkedList、HashSet、LinkedHashSet、HashMap、LinkedHashMap 等等。
在jdk1.2之前,Java就提供了诸如:Dictionary, Vector, Stack,这些类用来存储和操作对象组。但是它们缺少一个统一的主题。于是在jdk1.2之后便出现了集合框架,在jdk1.5上又添加了Iterable接口用来实现集合的迭代, 整个集合框架就围绕一组标准接口(例如Set,List) 而设计。你可以直接使用这些接口的标准实现,诸如: LinkedList, HashSet, 和 TreeSet等,除此之外你也可以通过这些接口实现自己的集合。
读者还应注意Java中集合与数组的区别:
- 已定义集合的大小可变,而已定义的数组大小不可变(注意:集合内部实现机制也用到了对象数组,只是通过数组的复制或链式存储的方式使其从外部看起来是可变的,而其本身并未颠覆数组大小不可变这一观点)
- 数组可以存储基本数据类型和引用类型,而集合只能存储引用类型,例如传入add方法中的int会被自动封装成Integer类型
- 数组只能存储相同类型的数据,而集合如未确定泛型的具体类型,则可存储任意引用类型数据
二、集合类图概述:
(注:1.此图只显示类常用接口和类,省略了部分中间类,将在下文逐一说明。2.Map族独立于collection,但属于集合框架中的一部分)
接口及类的详解:
1.Iterator<E>接口
首先要注意一点, Iterator接口和Iterable接口没有半毛钱的继承实现关系 ,唯一的关系是,在Iterable接口中有一个可返回Iterator类型的方法iterator()。 Iterator是一个集合的迭代器,通俗的说,它可将集合中的元素逐次取出,常用方法如下:
boolean hasNext() | 如果还可以迭代下一个元素,则返回true |
E next() | 返回迭代中的下一个元素 |
2.Iterable接口
Collection族下的类都间接实现此接口,实现此接口允许对象成为“for-each loop”语句的目标
接口中两个最常用的方法如下:
default void forEach(Consumer<? super T> action | 对 Iterable 的每个迭代元素执行给定的操作,传入一个函数的引用 |
Iterator<T> iterator() | 返回类型为 T 的迭代器 |
代码示例:
-
public static void main (String[] args) {
-
ArrayList arrayList= new ArrayList();
-
arrayList.add( 520 );
-
arrayList.add( true );
-
arrayList.add( new Date());
-
-
arrayList.forEach(System.out::println);
-
Iterator iterator=arrayList.iterator();
-
while (iterator.hasNext()){
-
System.out.println(iterator.next());
-
}
-
}
运行结果:
3.Collection接口
集合层次结构中的根接口 。 集合表示一组被称为其 元素 的对象。 一些集合允许重复元素,而其他集合不允许。JDK不提供此接口的任何直接实现:它提供了更具体的子接口的实现,如Set和List 等,常用扩展方法如下:
boolean add(E e) | 添加元素 |
boolean addAll(Collection<? extends E> c) | 将指定集合中的所有元素添加到该集合中 |
void clear() | 删除此集合中的所有元素 |
boolean contains(Object o) | 判断此集合是否包含指定元素,对比方式是equals(Object o) |
boolean isEmpty() | 判断集合是否为空 |
boolean remove(Object o) | 成功返回true,如果删除失败或者该元素不存在,返回false |
boolean removeAll(Collection<?> c) | 删除本集合中与给定集合的交集 |
boolean retainAll(Collection<?> c) | 保存本集合中与给定集合的交集 |
int size() | 返回此集合中的元素数 |
Object[] toArray() | 返回一个包含此集合中所有元素的对象数组 |
4.List接口及其实现类
首先说明List与Set的区别:
List | Set |
---|---|
元素有序(存入取出位置不变) | 元素无序 |
元素存在唯一的索引值 | 不存在索引 |
允许重复元素 | 不允许重复元素 |
在Collection基础上,List扩展的如下方法:
void add(int index,E element) | 将元素插入指定的位置 |
boolean addAll(int index,Collection<? extend E> c) | 按指定集合的迭代器顺序将其添加到本元素的指定位置 |
E get(int index) | 返回此列表指定位置的元素 |
int indexOf(Object o) int lastIndexOf(Object o) | 返回此列表中指定元素第一次(最后一次)出现的位置 如果不存在则返回-1 |
ListIterator<E> listiterator() ListIterator<E> listiterator(int index) | 返回此列表中的列表迭代器 从指定位置开始返回迭代器 |
E remove(int index) | 删除指定位置的元素,并返回到该元素 |
default void sort(Comparator<? super E> c) | 使用随附的 Comparator排序此列表来比较元素。 |
List<E> subList(int fromIndex, int toIndex) | 返回此列表中指定的 fromIndex (含)和 toIndex之间的视图。 |
注:ListIterator 接口继承自 Iterator,它允许沿任一方向遍历列表
代码示例:
-
public static void main (String[] args) {
-
ArrayList arrayList= new ArrayList();
-
arrayList.add( 520 );
-
arrayList.add( 43 );
-
arrayList.add( 321 );
-
arrayList.add( 0 , 999 );
-
arrayList.add( 334 );
-
System.out.println( "原始数组:" );
-
arrayList.forEach(System.out::println);
-
System.out.println( "获取第3个元素:" +arrayList.get( 2 ));
-
System.out.println( "a的位置:" +arrayList.indexOf( 520 ));
-
System.out.println( "排序后:" );
-
arrayList.sort(Comparator.comparing(Integer::intValue));
-
arrayList.forEach(System.out::println);
-
System.out.println( "ListIterator迭代:" );
-
ListIterator listIterator=arrayList.listIterator();
-
while (listIterator.hasNext()){
-
System.out.println(listIterator.next());
-
}
-
System.out.println( "删除第3个位置的元素:" +arrayList.remove( 2 ));
-
System.out.println( "新建数组内容为" );
-
List list=arrayList.subList( 1 , 3 );
-
list.forEach(System.out::println);
-
}
运行结果:
一.ArrayList 类
实现List接口,并允许所有元素,包括null,每个ArrayList实例都有一个容量 。 容量是用于存储列表中的元素的数组的大小。 它总是至少与列表大小一样大。 当元素添加到ArrayList时,其容量会自动增长。 没有规定增长的细节
此处说明:
ArrrayList与Vector的区别:1.ArrayList不是线程同步的,而Vector是线程同步的。2.ArrayList默认扩容量是原来的0.5倍,而Vector的默认扩容是原来的一倍
ArrayList与LinkedList的区别:ArrayList由数组备份。LinkedList由链接备份,并且LinkedList还可以用作队列。访问ArrayList中的元素更快,因为它的底层结构是数组,而当集合要频繁的进行增删,LinkedList 的性能优于 ArrayList 。
继承关系:
构造方法:
ArrayList() | 构造一个初始容量为十的空列表。 |
ArrayList(Collection<? extends E> c) | 构造一个包含指定集合的元素的列表,按照它们由集合的迭代器返回的顺序。 |
ArrayList(int initialCapacity) | 构造具有指定初始容量的空列表。 |
扩展方法:
Object clone() | 返回此 ArrayList实例的浅拷贝(关于深浅拷贝的区别见下文) |
void ensureCapacity(int minCapacity) | 增加此 ArrayList实例的容量,以确保它可以至少保存最小容量参数指定的元素数 |
void trimToSize() | 修改这个 ArrayList实例的容量是列表的当前大小 |
void replaceAll(UnaryOperator<E> operator) | 将该列表的每个元素替换为将该运算符应用于该元素的结果 |
E set(int index, E element) | 用指定的元素替换此列表中指定位置的元素 |
注:浅拷贝:被复制对象与原对象在内存中的引用是同一个对象
深拷贝:在内存中新开辟一块空间,把要复制的对象所引用的对象都复制了一遍,对现在对象的修改不会影响原有的对象。
代码示例:
-
public class Main {
-
public static void main (String[] args) {
-
ArrayList arrayList = new ArrayList( 100 );
-
People people1 = new People( 66 );
-
arrayList.add(people1);
-
arrayList.add( new People( 47 ));
-
arrayList.add( new People( 33 ));
-
arrayList.set( 2 , new People( 55 ));
-
people1.setAge( 100 );
-
arrayList.forEach(System.out::println);
-
System.out.println( "拷贝数组" );
-
ArrayList clonearray = (ArrayList) arrayList.clone();
-
clonearray.forEach(System.out::println);
-
System.out.println( "将people的值改变后:" );
-
people1.setAge( 888 );
-
clonearray.forEach(System.out::println);
-
}
-
}
-
class People {
-
int age;
-
public People ( int age) {
-
this .age = age;
-
}
-
public int getAge () {
-
return age;
-
}
-
public void setAge ( int age) {
-
this .age = age;
-
}
-
@Override
-
public String toString () {
-
return "People{" +
-
"age=" + age +
-
'}' ;
-
}
-
}
运行结果:
二.LinkedList类
LinkedList是非线程同步的,LinkedList与ArrayList的区别上文已讲,而当集合要频繁的进行增删,建议使用LinkedList提高性能,由于LinkeList还是同时实现了List和Deque接口,所以它还可以用来表示队列 ,具体实现在下文Queue接口和Deque接口中
类图结构:
三.Vector类
Vector在共功能上与ArrayList相似,如果不需要线程安全的实现,建议使用ArrayList代替Vector
构造方法如下:
Vector() | 构造一个空向量,使其内部数据数组的大小为 10 ,标准容量增量为零 |
Vector(Collection<? extends E> c) | 构造一个包含指定集合元素的向量,按照集合的迭代器返回的顺序。 |
Vector(int initialCapacity) | 构造具有指定初始容量并且其容量增量等于零的空向量。 |
Vector(int initialCapacity, int capacityIncrement) | 构造具有指定的初始容量和容量增量的空向量。 |
成员方法略
四.Stack类
Stack是一个后进先出的栈(LIFO),但Deque接口及其实现提供了更完整和一致的LIFO堆栈操作集,这些接口应优先于此类。 例如: Deque<Integer> stack = new ArrayDeque<Integer>();
Stack继承自Vector,扩展方法如下:
boolean empty() | 测试此堆栈是否为空。 |
E peek() | 查看此堆栈顶部的对象,而不从堆栈中删除它。 |
E pop() | 删除此堆栈顶部的对象,并将该对象作为此函数的值返回。 |
E push(E item) | 将项目推送到此堆栈的顶部。 |
int search(Object o) | 返回一个对象在此堆栈上的基于1的位置 |
代码示例:
-
private static void showpush (Stack st, int a) { //把项压入栈顶
-
st.push( new Integer(a));
-
System.out.println( "push(" + a + ")" );
-
System.out.println( "stack: " + st);
-
}
-
private static void showpop (Stack st) { //移除堆栈顶部的对象并作为此函数的值返回该对象
-
Integer a = (Integer) st.pop();
-
System.out.println(a);
-
System.out.println( "after pop stack: " + st);
-
}
-
private static void showpeek (Stack stack) {
-
Integer a=(Integer) stack.peek();
-
System.out.println(a);
-
System.out.println( "after peek stack:" +stack);
-
}
-
public static void main (String args[]) {
-
Stack st = new Stack();
-
System.out.println( "stack: " + st);
-
showpush(st, 42 );
-
showpush(st, 66 );
-
showpush(st, 99 );
-
showpeek(st);
-
showpop(st);
-
showpop(st);
-
showpop(st);
-
try {
-
showpop(st);
-
} catch (EmptyStackException e) {
-
System.out.println( "empty stack" );
-
}
-
}
运行结果:
5.Set接口及其实现类
Set集合是唯一元素集合,最多只有一个null值,当向集合添加重复元素时,它们将被忽略,元素是否相同是通过equals()方法实现的
一.HashSet和LinkedHashSet
区别: HashSet
不保证顺序元素。 LinkedHashSet
在插入元素时保持元素顺序
代码示例:
-
public static void main (String[] args) {
-
Set hashSet= new HashSet();
-
hashSet.add( "XML" );
-
hashSet.add( "HTML" );
-
hashSet.add( "SQL" );
-
hashSet.add( null );
-
hashSet.add( "HTML" );
-
hashSet.add( null );
-
hashSet.add( "JAVA" );
-
System.out.println( "hashSet:" +hashSet);
-
HashSet linkHashSet= new LinkedHashSet<>();
-
linkHashSet.add( "XML" );
-
linkHashSet.add( "HTML" );
-
linkHashSet.add( "SQL" );
-
linkHashSet.add( null );
-
linkHashSet.add( "HTML" );
-
linkHashSet.add( null );
-
linkHashSet.add( "JAVA" );
-
System.out.println( "LinkedHashSet:" +linkHashSet);
-
}
运行结果:
二.TreeSet类
SortedSet 接口表示Java集合中的排序集合框架,如果它的元素实现了Comparable接口,它将使用compareTo()方法来排序项目。 我们可以称之为自然顺序排序。我们也可以传递一个比较器Comparator做自定义排序。如果指定了 Comparator ,则将Comparator 用于排序并忽略 Comparable 接口。
TreeSet 类是Collection框架中SortedSet接口的一个实现。
类图结构:
代码示例:
-
public class Main {
-
public static void main (String[] args) {
-
/* 按照String中的compareTo的默认比较方法
-
先比较字符串长度,如果长度一样,则按照字典的顺序依次比较,小写大于大写*/
-
SortedSet sortedSet= new TreeSet();
-
sortedSet.add( "jav" );
-
sortedSet.add( "hdTM" );
-
sortedSet.add( "xML" );
-
sortedSet.add( "sQL" );
-
System.out.println(sortedSet);
-
/*
-
使用Comparable比较器自定义排序关键字
-
*/
-
SortedSet<Student> students= new TreeSet<>(Comparator.comparing(Student::getAge));
-
students.add( new Student( "Kangkang" , 13 ));
-
students.add( new Student( "Mike" , 11 ));
-
students.add( new Student( "Jack" , 18 ));
-
students.add( new Student( "Lihua" , 14 ));
-
System.out.println( "将学生按年龄排序:" );
-
students.forEach(System.out::println);
-
}
-
}
-
class Student {
-
String name;
-
int age;
-
public int getAge () {
-
return age;
-
}
-
public void setAge ( int age) {
-
this .age = age;
-
}
-
public Student (String name, int age) {
-
this .name = name;
-
this .age = age;
-
}
-
-
@Override
-
public String toString () {
-
return "Student{" +
-
"name='" + name + '\'' +
-
", age=" + age +
-
'}' ;
-
}
-
}
运行结果:
6.Queue接口及其实现类
简单队列用 Queue接口表示,并由LinkedList实现
简单队列队列允许您执行三个基本操作:
- 从尾部添加元素
- 从其头部移除元素
- 在元素顶部审查
Queue接口为三个操作中的每一个定义了两个方法。如果操作不可能,一个方法抛出异常,另一个方法方法返回false或null以指示失败。
方法 | 描述 |
---|---|
boolean add(E e) | 如果可能,向队列中添加一个元素。否则,它抛出异常。 |
boolean offer(E e) | 如果不能添加元素,则将元素添加到队列中,而不抛出异常。 它在失败时返回false,在成功时返回true。 |
E remove() | 删除队列的头。如果队列为空,它会抛出异常。此方法返回已移除的项目。 |
E poll() | 从队列中删除元素。如果队列为空而不是抛出异常,则返回null。 |
Eelement() | 偷看队列的头,而不从队列中删除它。 如果队列为空,它会抛出异常。 |
E peek() | 查看队列,如果队列为空而不是抛出异常,则返回null。 |
代码示例:
-
public static void main (String[] args) {
-
Queue<String> queue = new LinkedList<>();
-
queue.offer( "one" );
-
queue.offer( "two" );
-
queue.offer( "three" );
-
queue.offer( "four" );
-
System.out.println(queue);
-
//出队
-
System.out.println( "使用poll()" +queue.poll());
-
System.out.println(queue);
-
System.out.println( "使用peek()" +queue.peek());
-
System.out.println(queue);
-
}
运行结果:
7.Deque接口及其实现类
双端队列或Deque扩展队列以允许元件从两端插入和移除。
Deque 类的实例表示双端队列。 Deque 接口扩展了 Queue 接口。
它可以用作 FIFO队列 或 LIFO队列(堆栈) 。
ArrayDeque和LinkedList类是Deque接口的两个实现类。
ArrayDeque 类由数组支持,而 LinkedList 类由链表支持。
如果您使用Deque作为堆栈,则应该使用 ArrayDeque 作为 Deque 实现。
如果使用 Deque 作为FIFO队列,则应该使用 LinkedList 作为 Deque 实现。
一.ArrayDeque类
常用方法:
E peek() | 检索但不删除栈顶 |
void push(E e) | 将元素压入栈 |
E pop() | 弹出栈顶元素 |
代码示例:
-
public static void main (String[] args) {
-
Deque<String> deque= new ArrayDeque();
-
deque.push( "one" );
-
deque.push( "two" );
-
deque.push( "three" );
-
deque.push( "four" );
-
System.out.println(deque);
-
System.out.println( "使用peek():" +deque.peek());
-
System.out.println(deque);
-
System.out.println( "使用pop():" +deque.pop());
-
System.out.println(deque);
-
-
}
运行结果:
二.LinkedList类(此处用来实现双端队列)
常用方法:
void addFirst(E e) | 在该列表开头插入指定的元素 |
void addLast(E e) | 将指定的元素追加到此列表的末尾 |
E peekFirst() | 检索但不删除此列表的第一个元素,如果此列表为空,则返回 null |
E peekLast() | 检索但不删除此列表的最后一个元素,如果此列表为空,则返回 null |
E pollFirst() | 检索并删除此列表的第一个元素,如果此列表为空,则返回 null |
E pollLast() | 检索并删除此列表的最后一个元素,如果此列表为空,则返回 null |
代码示例:
-
public static void main (String[] args) {
-
Deque<String> deque= new LinkedList<>();
-
deque.addFirst( "左一" );
-
deque.addFirst( "左二" );
-
deque.addFirst( "左三" );
-
deque.addLast( "右一" );
-
deque.addLast( "右二" );
-
deque.addLast( "右三" );
-
System.out.println(deque);
-
System.out.println( "peekFirst():" +deque.peekFirst());
-
System.out.println(deque);
-
System.out.println( "peekLast():" +deque.peekLast());
-
System.out.println(deque);
-
System.out.println( "pollFirst():" +deque.pollFirst());
-
System.out.println(deque);
-
System.out.println( "pollLast():" +deque.pollLast());
-
System.out.println(deque);
-
}
运行结果:
8.Map接口及其实现类
敲黑板!:Map是Mapping(映射)的简写,不要以为是数据结构中的 Map(图)
Map是一个集合,其中每个元素表示一个键值对作为<key,value> ,<key,value> 对也称为映射中的条目,由Map中的一个内部接口Entry<K,V>表示。键和值必须是引用类型,Map接口虽和Collection没啥关系,但它仍是集合框架中很重要的一部分, 映射不允许任何重复的键。每个键映射到正好一个值。值不必是唯一的。 两个键可以映射到相同的值,Map允许最多一个null作为键,允许多个null作为其值
首先我们了解代表Map集合中每个映射条目的内部接口Map.Entry<K,V>,常用方法如下;
K getKey() | 获取该条目中的键 |
V getValue() | 获取该条目中的值 |
int hashCode() | 返回此条目的哈希值 |
V setValue(V value) | 使用指定的值替换该条目中的值 |
代码示例:(获取条目的唯一方法是使用Map.entrySet() )
-
public static void main (String[] args) {
-
Map map= new HashMap();
-
map.put( 1 , "one" );
-
map.put( 2 , "two" );
-
map.put( 3 , "three" );
-
map.put( 4 , "four" );
-
Set<Map.Entry> entrySet=map.entrySet();
-
Iterator<Map.Entry> iterator=entrySet.iterator();
-
while (iterator.hasNext()){
-
Map.Entry entry1=iterator.next();
-
System.out.println(entry1.getKey()+ "===" +entry1.getValue());
-
entry1.setValue( "hello world" );
-
}
-
System.out.println(map);
-
}
运行结果:
由结果可见,entrySet存储的原Map中的视图,从而实现对Map中元素的迭代和修改,Map接口本身是不具备迭代功能的,除了使用entrySet()外,使用keySet()和values()分别获取键,值的视图也是迭代的不错的选择。
Map接口中的常用方法如下:
V put(K key, V value) | 向集合中添加映射项目 |
void putAll(Map<? extends K,? extends V> m) | 将指定Map中的值复制到此映射 |
void clear() | 从集合中删除所有的映射 |
boolean containsKey(Object key) | 判断集合中是否包含此键的映射 |
boolean containsValue(Object value) | 判断集合中是否包含此值的映射 |
Set<K> keySet() | 返回由Map中的键组成的集合视图 |
Collection<V> values() | 返回由Map中的值组成的集合视图 |
Set<Map.Entry<K,V>> entrySet() | 返回由Map中的映射条目组成的集合视图 |
Map常用的实现类:
一.HashMap类
HashMap不保证Map中条目的任何特定的迭代顺序
代码示例:
-
public static void main (String[] args) {
-
Map map= new HashMap();
-
map.put( 9 , "one" );
-
map.put( 2 , "two" );
-
map.put( 6 , "three" );
-
map.put( 4 , "four" );
-
System.out.println(map);
-
System.out.println( "判断集合中是否包含键为2的映射: " +map.containsKey( 2 ));
-
System.out.println( "判断集合中国是否包含值为three: " +map.containsValue( "three" ));
-
Set<Integer> set=map.keySet();
-
System.out.println(set);
-
Collection<String> collection=map.values();
-
System.out.println(collection);
-
}
运行结果:
二.LinkedHashMap类
LinkedHashMap是Map接口的另一个实现类,继承自HashMap, 它使用双向链表在Map中存储条目,并保持迭代排序作为插入顺序
代码示例:
-
public static void main (String[] args) {
-
Map map= new LinkedHashMap();
-
map.put( 9 , "one" );
-
map.put( 2 , "two" );
-
map.put( 6 , "three" );
-
map.put( 4 , "four" );
-
System.out.println(map);
-
System.out.println( "判断集合中是否包含键为2的映射: " +map.containsKey( 2 ));
-
System.out.println( "判断集合中国是否包含值为three: " +map.containsValue( "three" ));
-
Set<Integer> set=map.keySet();
-
System.out.println(set);
-
Collection<String> collection=map.values();
-
System.out.println(collection);
-
}
三.TreeMap类
T reeMap是基于一个红黑树的实现 ,具体继承关系如下,它会按照你对Comparable接口或Comparator比较器的实现来对集合中的条目进行排序,同TreeSet相似,如果你设置了Comparator比较器,Comparable接口的功能将会被忽略。
下面的代码使用Comparator比较器对TreeMap中的条目进行排序:
-
public static void main (String[] args) {
-
//先比较键的长度,再忽略大小比较
-
Comparator<String> keyComparator = Comparator.comparing(String::length).thenComparing(String::compareToIgnoreCase);
-
Map map= new TreeMap(keyComparator);
-
map.put( "one" , 3 );
-
map.put( "two" , 4 );
-
map.put( "three" , 2 );
-
map.put( "four" , 1 );
-
System.out.println(map);
-
}
运行结果:
四.HashTable与HashMap的区别
HashTable直接实现Map接口,它与HashMap的区别如下:
- HashMap去掉了HashTable 的contains方法,但是加上了containsValue()和containsKey()方法。
- HashTable同步的,而HashMap是非同步的,效率上比HashTable要高。
- HashMap允许空键值,而HashTable不允许。
4.Collections集合工具类
详见我的另一博文: https://blog.csdn.net/qq_42013035/article/details/103452711
参考: