在编程时往往需要存放多个数据,我们可以使用之前学习过的数组进行存放,但是数组的长度是固定的,是不可变的,如果想要存放数量可变的多个数据,以及存放具有映射关系的数据那就需要Java提供的集合类来做处理了,由于集合类的主要功能就是存放数据,所以集合也被称之为容器。Java中的集合类主要由两个接口派生而出:Collection和Map,这两个接口的子接口以及子类共同构成了Java语言类集框架。
上图为Collection体系继承关系图,通过这张图我们就可以清晰的了解到Collection接口以及其子接口和子类的继承关系,接下来我将Collection体系中常用的接口及子类做一详细的介绍。
Collection接口
Collection接口是Set、Queue、List接口的父接口,可以说它定义了Collection集合体系的基础规范。
常用方法
//获取集合中元素的个数
int size();
//判断集合中是否有元素
boolean isEmpty();
//判断集合中是否包含参数对象
boolean contains(Object o);
//判断集合中是否包含参数集合中的所有元素
boolean containsAll(Collection<?> c);
//向集合中增加指定的元素
boolean add(E e);
//向集合中增加指定集合的所有元素
boolean addAll(Collection<? extends E> c);
//删除集合中指定的元素
boolean remove(Object o);
//从集合中删除指定集合中所有的元素
boolean removeAll(Collection<?> c);
//从集合中删除不是指定集合中元素的所有元素
boolean retainAll(Collection<?> c);
//获取迭代器对象
Iterator<E> iterator();
//清除集合中的所有元素,集合长度变为0
void clear();
//将集合转换为数组
Object[] toArray();
Set接口
Set接口作为Collection的子接口它拥有其所有的抽象方法,Set集合最大的特点就是其存放的数据是无序且不可重复的。
HashSet子类
HashSet是Set接口的典型实现,我们在以后开发中一旦使用Set集合,使用最多的就是HashSet子类。HashSet的底层实现是HashMap,而HashMap的底层是数组加链表和红黑树的实现模式,在数组中存放的是链表的头结点或者是红黑树的根结点,而对于链表和红黑树的实现都没有维护顺序,这将导致set也是无序的。HashSet完整的实现了Collection中的所有接口,而且其核心方法的实现都是基于HashMap方法的实现的,例如其add方法是基于HashMap的put方法实现的。
HashSet集合特点
1、集合中的元素与元素新增的顺序无关,是无序的。
2、集合中的元素是不可重复的。
3、集合中的元素可以是null。
栗子,验证HashSet集合特点
package com.icypt.hashset;
import java.util.Set;
import java.util.HashSet;
/**
*运行结果
*[null, ceshiB, ceshiC, ceshiD, ceshiA]
*/
public class Test {
public static void main(String[] args) {
//创建Set集合对象
Set<String> set = new HashSet<String>();
set.add("ceshiA");
set.add("ceshiA");
set.add("ceshiB");
set.add("ceshiC");
set.add("ceshiD");
set.add(null);
System.out.println(set);
}
}
栗子,使用HashSet的其他方法
package com.icypt.hashset;
import java.util.Set;
import java.util.HashSet;
/**
*运行结果
元素个数:5
*检查set集合中是否存在有元素:false
*检查set集合中是否存在有ceshiA元素:true
*[null, ceshiB, ceshiC, ceshiD, ceshiA]
*[null, ceshiB, ceshiC, ceshiD]
*[null, test2, ceshiB, ceshiC, ceshiD, ceshiA, test1]
*检查set集合中是否存在有set1集合中的所有元素:false
*[test2, ceshiA, test1]
*[test2, test1]
*test2,test
*[]
*/
public class TestX {
public static void main(String[] args) {
//创建Set集合对象
Set<String> set = new HashSet<String>();
//向集合中增加指定的元素
set.add("ceshiA");
set.add("ceshiA");
set.add("ceshiB");
set.add("ceshiC");
set.add("ceshiD");
set.add(null);
//获取集合中元素的个数
System.out.println("元素个数:" + set.size());
//判断集合中是否有元素
System.out.println("检查set集合中是否存在有元素:" + set.isEmpty());
//判断集合中是否包含参数对象
System.out.println("检查set集合中是否存在有ceshiA元素:" + set.contains("ceshiA"));
System.out.println(set);
//删除集合中指定的元素
set.remove("ceshiA");
System.out.println(set);
Set<String> set1 = new HashSet<String>();
set1.add("test1");
set1.add("test2");
set1.add("ceshiA");
//向集合中增加指定集合的所有元素
set.addAll(set1);
System.out.println(set);
//判断集合中是否包含参数集合中的所有元素
System.out.println("检查set集合中是否存在有set1集合中的所有元素:" + set.contains(set1));
set.retainAll(set1);
System.out.println(set);
Set<String> set2 = new HashSet<String>();
set2.add("ceshiA");
//从集合中删除不是指定集合中元素的所有元素
set.removeAll(set2);
System.out.println(set);
//将集合转换为数组
Object[] setArray = set.toArray();
String result = "";
for(int i=0; i<setArray.length; i++) {
result += setArray[i] + ",";
}
System.out.println(result.substring(0, result.lastIndexOf(",")-1));
//清除集合中的所有元素,集合长度变为0
set.clear();
System.out.println(set);
}
}
以上是HashSet常用方法的使用,需要注意的是在创建Set对象时我们在尖括号中指定了数据类型,这个数据型就是我们要向Set集合中存放的元素的数据类型或者其子类型,而对于尖括号中使用数据类型的方式称之为泛型,后续文章会讨论到关于泛型的使用。
LinkedHashSet子类
LinkedHashSet是HashSet的子类,它完整的继承了HashSet的所有成员方法没有做任何的重写操作,LinkedHashSet底层实现是LinkedHashMap,而LinkedHashMap是有序的,所以也保证了LinkedHashSet的有序性。由此看来,LinkedHashSet与HashSet在特性上唯一的不同就是它的有序性。
栗子,验证LinkedHashSet的有序性
package com.icypt.linkedhashset;
import java.util.Set;
import java.util.HashSet;
import java.util.LinkedHashSet;
/**
*运行结果
*[null, ceshiB, ceshiC, ceshiD, ceshiA]
*[ceshiA, ceshiB, ceshiC, ceshiD, null]
*/
public class Test {
public static void main(String[] args) {
//创建HashSet集合对象
Set<String> set = new HashSet<String>();
set.add("ceshiA");
set.add("ceshiA");
set.add("ceshiB");
set.add("ceshiC");
set.add("ceshiD");
set.add(null);
System.out.println(set);
//创建LinkedHashSet对象
Set<String> linkedSet = new LinkedHashSet<String>();
linkedSet.add("ceshiA");
linkedSet.add("ceshiA");
linkedSet.add("ceshiB");
linkedSet.add("ceshiC");
linkedSet.add("ceshiD");
linkedSet.add(null);
System.out.println(linkedSet);
}
通过以上栗子就可以看出HashSet的无序性和LinkedHashSet的有序性。
TreeSet子类
TreeSet实现了SortedSet接口,其底层实现是TreeMap,而对于TreeMap而言他是有序的,这种顺序与元素插入集合的顺序无关是根据元素的实际值大小进行排序的。根据元素的实际值进行排序,就要求存入TreeSet的元素必须实现Comparable接口,否则在程序运行时会抛出异常。由于其根据值排序的特殊性,TreeSet又扩展了几个额外的方法。
//返回集合中的第一个元素
public E first(){}
//返回集合中的最后一个元素
public E last(){}
//返回集合中小于指定元素的最大元素
public E lower(E e){}
//返回集合中小于或等于指定元素的最大元素
public E floor(E e) {}
//返回集合中大于指定元素的最小元素
public E higher(E e) {}
//返回集合中大于或等于指定元素的最小元素
public E ceiling(E e) {}
//查找并删除第一个元素
public E pollFirst(){}
//查找并删除最后一个元素
public E pollLast() {}
下面我们以String类对象作为TreeSet的元素来验证其方法,因为String类实现了Comparable接口。
栗子
package com.icypt.treeset;
import java.util.Set;
import java.util.TreeSet;
/**
*[A, B, C]
*返回集合中的第一个元素:A
*返回集合中的最后一个元素:C
*返回集合中小于指定元素的最大元素:A
*返回集合中小于等于指定元素的最大元素:B
*返回集合中大于指定元素的最小元素:B
*返回集合中大于等于指定元素的最小元素:A
*查找并删除第一个元素:A
*查找并删除最后一个元素:C
*[B]
*/
public class Test {
public static void main(String[] args) {
//创建TreeSet对象
TreeSet<String> treeSet = new TreeSet<String>();
treeSet.add("B");
treeSet.add("A");
treeSet.add("A");
treeSet.add("C");
System.out.println(treeSet);
System.out.println("返回集合中的第一个元素:" + treeSet.first());
System.out.println("返回集合中的最后一个元素:" + treeSet.last());
System.out.println("返回集合中小于指定元素的最大元素:" + treeSet.lower("B"));
System.out.println("返回集合中小于等于指定元素的最大元素:" + treeSet.floor("B"));
System.out.println("返回集合中大于指定元素的最小元素:" + treeSet.ceiling("B"));
System.out.println("返回集合中大于等于指定元素的最小元素:" + treeSet.lower("B"));
System.out.println("查找并删除第一个元素:" + treeSet.pollFirst());
System.out.println("查找并删除最后一个元素:" + treeSet.pollLast());
System.out.println(treeSet);
}
}
关于Set接口以及其子类就讨论这么多,下面我们来看Queue接口的使用。
Queue接口
Queue接口在Java中是队列数据结构的实现标准。所谓队列是一种“先入先出”的集合,队列的头部是队列中存放时间最长的元素,而尾部是队列中存放时间最短的元素,Queue接口在Collection接口的基础上又额为增加了几个抽象方法。
//在队列的尾部插入指定的元素
boolean add(E e);
//在队列的尾部插入指定的元素,如果使用有容量限制的队列时,此方法优于add方法
boolean offer(E e);
//检索并删除队列的头元素,如果队列为空则抛出异常
E remove();
//检索并删除队列的头元素,如果队列为空则返回null
E poll();
//检索队列的头元素,不删除,如果队列为空则抛出异常
E element();
//检索队列的头元素,不删除,如果队列为空则返回null
E peek();
Deque接口
Deque接口是双端队列的实现标准,所谓双端队列,就是指在队列的两端都可以进行新增和删除操作,它既可以作为队列使用又可以作为栈来使用。由于它继承了Queue接口,所以它拥有Queue接口中的所有方法,同时它额外又增加了很多抽象方法。
//栈方法,入栈操作,相当于addFirst
void push(E e);
//栈方法,出栈操作,相当于removeFirst
E pop();
boolean remove(Object o);
boolean contains(Object o);
int size();
//在双端队列的头部插入指定元素
void addFirst(E e);
//在双端队列的尾部插入指定元素
void addLast(E e);
//在双端队列的头部插入指定元素
boolean offerFirst(E e);
//在双端队列的尾部插入指定元素
boolean offerLast(E e);
//检索并删除该双端队列的第一个元素,如果队列为空会抛出异常
E removeFirst();
//检索并删除该双端队列的最后一个元素,如果队列为空会抛出异常
E removeLast();
//检索并删除该双端队列的第一个元素,如果队列为空,则返回null
E pollFirst();
//检索并删除该双端队列的最后一个元素,如果队列为空,则返回null
E pollLast();
//检索该双端队列的第一个元素,如果队列为空会抛出异常
E getFirst();
//检索该双端队列的最后一个元素,如果队列为空会抛出异常
E getLast();
//检索该双端队列的第一个元素,如果队列为空,则返回null
E peekFirst();
//检索该双端队列的第一个元素,如果队列为空,则返回null
E peekLast();
//删除该双端队列第一次出现的元素
boolean removeFirstOccurrence(Object o);
//删除该双端队列最后一次出现的元素
boolean removeLastOccurrence(Object o);
ArrayDeque子类
ArrayDeque是基于数组实现的双端队列,可以说是双端队列的标准实现。既然是数组那么在创建其对象时肯定要初始化其大小,默认的大小为16,最小为8,当然可以在创建对象时通过构造方法指定其大小。
栗子,作为队列使用“先入先出”
package com.icypt.queue;
import java.util.ArrayDeque;
/**
* 运行结果
* [第一个, 第二个, 第三个, 第四个, 第五个, 第六个]
* [第六个, 第五个, 第四个, 第三个, 第二个, 第一个]
*/
public class Test {
public static void main(String[] args) {
ArrayDeque<String> aq = new ArrayDeque<String>();
aq.addLast("第一个");
aq.addLast("第二个");
aq.addLast("第三个");
aq.addLast("第四个");
aq.addLast("第五个");
aq.addLast("第六个");
System.out.println(aq);
aq = new ArrayDeque<String>();
aq.addFirst("第一个");
aq.addFirst("第二个");
aq.addFirst("第三个");
aq.addFirst("第四个");
aq.addFirst("第五个");
aq.addFirst("第六个");
System.out.println(aq);
}
}
双端队列的方法都比较简单,就不一一演示了。使用双端队列作为标准队列操作时,一定要注意队列的入口和出口问题,要想达到“先入先出”的效果,在使用方法时必须遵循“倒序入队,顺序出队”的原则,也就是如果你使用的是Last系列方法进队,则必须使用first系列方法出队。
List接口
List接口定义了一个有序可重复集合的实现标准,它继承与Collection接口,所以完整的拥有Collection的所有方法,同时它还额外新增了一些方法。
//在集合的指定索引处增加元素,索引所在位置元素后移
boolean add(int index, E element);
//在集合的指定索引处增加集合c中的所有元素
boolean addAll(int index, Collection<? extends E> c);
//获取指定索引处的元素
E get(int index);
//查找指定元素第一次出现时的索引
int indexOf(Object o);
//查找指定元素最后一次出现时的索引
int lastIndexOf(Object o);
//删除指定索引处的元素并返回该元素
E remove(int index);
//替换指定索引处的元素
E set(int index, E element);
//返回从指定开始索引到结束索引之间所有元素的集合
List<E> subList(int fromIndex, int toIndex);
//jdk1.8新增默认方法,根据Operator指定的规则重新设置所有元素
default void replaceAll(UnaryOperator<E> operator){}
//jdk1.8新增默认方法,根据Comparator对集合进行排序
default void sort(Comparator<? super E> c) {}
ArrayList及Vector子类
ArrayList子类是List接口的标准实现,它的底层实现是一个动态数组,所谓动态数组是指数组的容量是可以发生变化的,当增加元素时如果超过了该数组的长度则数组会自动扩容。
Vector子类也是List接口的典型实现,其底层实现原理与ArrayList完全相同,它是一个比较古老的集合类了,在JDK1.0时就已经存在了。它与ArrayList最大的不同在于它的很多方法都由synchronized关键字修饰,所以是线程安全的,除此之外,在用法上完全一致,关于线程安全的问题我会在多线程的章节中详细阐述。
栗子,ArrayList的使用
package com.icypt.arraylist;
import java.util.List;
import java.util.ArrayList;
/**
*[A, C, D, F, I, G, F, B, C]
*[A, C, D, F, H, I, G, F, B, C]
*[A, C, D, F, H, I, G, F, B, X, Y, Z, C]
*获取指定索引处的元素F
*查找指定元素第一次出现时的索引3
*查找指定元素最后一次出现时的索引7
*删除指定索引处的元素并返回该元素F
*[A, C, D, F, H, I, G, B, X, Y, Z, C]
*替换指定索引处的元素A
*[L, C, D, F, H, I, G, B, X, Y, Z, C]
*返回从指定开始索引到结束索引之间所有元素的集合[L, C, D]
*[B, C, C, D, F, G, H, I, L, X, Y, Z]
*[B1, C1, C1, D1, F1, G1, H1, I1, L1, X1, Y1, Z1]
*/
public class Test {
public static void main(String[] args) {
//创建ArrayList对象
List<String> list = new ArrayList<String>();
list.add("A");
list.add("C");
list.add("D");
list.add("F");
list.add("I");
list.add("G");
list.add("F");
list.add("B");
list.add("C");
System.out.println(list);
//在集合的指定索引处增加元素
list.add(4, "H");
System.out.println(list);
List<String> list1 = new ArrayList<String>();
list1.add("X");
list1.add("Y");
list1.add("Z");
//在集合的指定索引处增加集合c中的所有元素
list.addAll(9, list1);
System.out.println(list);
System.out.println("获取指定索引处的元素" + list.get(3));
System.out.println("查找指定元素第一次出现时的索引" + list.indexOf("F"));
System.out.println("查找指定元素最后一次出现时的索引" + list.lastIndexOf("F"));
System.out.println("删除指定索引处的元素并返回该元素" + list.remove(7));
System.out.println(list);
System.out.println("替换指定索引处的元素" + list.set(0, "L"));
System.out.println(list);
System.out.println("返回从指定开始索引到结束索引之间所有元素的集合" + list.subList(0,3));
//jdk1.8新增默认方法,根据Comparator对集合进行排序
list.sort((o1,o2) -> o1.compareTo(o2));
System.out.println(list);
//jdk1.8新增默认方法,根据Operator指定的规则重新设置所有元素
list.replaceAll(t -> t + 1);
System.out.println(list);
}
}
ArrayList是开发中最常用的List接口的实现类,尤其在jdk1.8以后,其使用更加的方便,比如对于其元素值的简单的排序,一行代码搞定了。
LinkedArrayList 子类
LinkedArrayList 这个子类比较特殊,它既实现了List接口又实现了Deque接口,所以它既可以充当有序可重复集合使用,又可以充当双端队列使用。它的实现原理与ArrayDeque以及ArrayList不同,这两子类底层都是以动态数组的形式实现的,而LinkedArrayList 的底层是基于链表实现的,性能上来看的话,基于链表的实现在新增或者删除操作时比较优秀。由于其使用方式已经在上文中讨论过了,这里就不赘述了。
迭代器(Iterator)
迭代器是一种设计模式,Java中使用Iterator接口定义了迭代器的实现标准,开发人员无需关注其内部实现,只需调用其提供的接口方法完成集合的遍历操作即可。
常用方法
//判断集合是否存在下一个元素
boolean hasNext();
//返回一下元素
E next();
//删除迭代器返回的元素
default void remove() {}
栗子
package com.icypt.iterator;
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
/**
*运行结果
*[A, B, B, C]
*A
*B
*B
*C
*/
public class Test {
public static void main(String[] args) {
//创建ArrayList对象
List<String> list = new ArrayList<String>();
list.add("A");
list.add("B");
list.add("B");
list.add("C");
System.out.println(list);
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()) {
String str = iterator.next();
System.out.println(str);
}
}
}
发现迭代器的使用使我们的集合遍历操作变的更加简单了,当然还有更加简单的方式,我们在后续的文章中慢慢介绍。
至此Collection体系的集合就讨论完了,下篇我们接着讨论Map体系集合的相关知识。
注释::本文来自公众号冰点IT
java培训:http://www.baizhiedu.com/java2019