第8章 java集合
Java集合类是一种特别有用的工具类,可用于存储数量不等的对象,并可以实现常用的数据结构,如栈、队列等。除此之外,Java集合还可用于保存具有映射关系的关联数组。Java集合大致可分为Set、List、Queue和Map四种体系,其中Set代表无序、不可重复的集合;List代表有序、重复的集合;而Map则代表具有映射关系的集合,Java5又增加了Queue体系集合,代表一种队列集合实现。
8.1 java集合概述
- 集合类主要负责保存、盛装其他数据,因此集合类也被称为容器类。(所有的集合类都位于java.util包下,处理多线程环境下的并发安全问题,Java5还在java.util.concurrent包下提供了一些多线程支持的集合类。)
- Java的集合类主要由两个接口派生而出:Collection和Map,Collection和Map是Java集合框架的根接口,这两个接口又包含了一些子接口或实现类。
- Collection集合:
- Map保存的每项数据都是 key-value 对(根据key来获取数据)
Set代表无序、不可重复的集合;
List代表有序、重复的集合;
Map则代表具有映射关系(key-value)的集合;
Queue体系集合,代表一种队列集合实现。
- 常用的实现类:HashSet、TreeSet、ArrayList、ArrayDeque、LinkedList、HashMap、TreeMap 等。
8.2 Collection和Iterator接口
- Collection接口定义的如下操作集合元素的方法:
boolean add(Object o)
:该方法用于向集合里添加
一个元素。
boolean addAll(Collection c)
:该方法把集合c
里的所有元素添加
到指定集合里。
void clear()
:清除
集合里的所有元素,将集合长度变为0。
boolean contains(Objecto)
:返回集合里是否包含
指定元素。
boolean containsAll(Collection c)
:返回集合里是否包含集合c
里的所有元素。
boolean isEmpty()
:返回集合是否为空
。
Iterator iterator()
:返回一个Iterator对象
,用于遍历
集合里的元素。
boolean remove(Object o)
:删除
集合中的指定元素o,(当包含了一个或多个元素o时,只删除第一个)
boolean removeAll(Collection c)
:从集合中删除集合c
里包含的所有元素(相当于用调用该方法的集合减集合c
)
boolean retainAll(Collection c)
:从集合中删除集合c里不包含
的元素(相当于把调用该方法的集合变成该集合和集合c的交集
)
int size()
:该方法返回集合里元素的个数
。
Object[] toArray()
:该方法把集合
转换成一个数组
。
8.2.1 使用Lambda表达式遍历集合
- java 8 为Iterable接口新增了一个forEach(Consumer action) 默认方法(参数类型为函数式接口,因此可以使用Lambda表达式遍历集合元素)。
public class CollectionEach{
public static void main(Stirng[] args){
//创建一个集合
Collection books = new HashSet();
books.add("java讲义");
books.add("c语言讲义");
books.add("c++讲义");
//调用forEach()方法遍历集合
books.forEach(obj -> System.out.println("集合元素:" + obj));
}
}
8.2.2 使用java 8 增强的Iterator遍历集合元素
- Iterator主要用于遍历(迭代访问)Collection集合中的元素,Iterator对象也被称为迭代器。
- Iterator接口里定义了4个方法:
boolean hasNext()
:如果被迭代的集合元素还没有被遍历完,则返回true。
Object next()
:返回集合里的下一个元素。
void remove()
:删除集合里上一次next方法返回的元素。
void forEachRemaining(Consumer action)
:该方法可使用Lambda表达式来遍历集合元素。(Java8为Iterator新增的默认方法)
public class IteratorTest{
public static void main(String[] args){
//创建集合、添加元素的代码与前一个程序相同
...
//获取books集合对应的迭代器
Iterator it = books.iterator();
while(it.hasNext()){
//it.next()方法返回的数据类型为Object类型,需要强制转换
String books = (String)it.next();
System.out.println(book);
if(book.equlas("java讲义")){
//从集合中删除上一次next()方法返回的元素
it.remove();
}
//对book变量赋值,不会改变集合元素本身
book = "测试字符串";
}
System.out.println(books);
}
}
- Iterator仅用于遍历集合,Iterator本身并不提供盛装对象的能力。如果需要创建Iterator对象,则必须有一个被迭代的集合。
- 当使用Iterator对集合元素进行迭代时,Iterator并不是把集合元素本身传给了迭代变量,而是把集合元素的值传给了迭代变量,所以修改迭代变量的值对集合元素本身没有任何影响。
- 迭代时Collection集合里的元素不能被改变,只有通过Iterator的
remove()
方法删除上一次next()
方法返回的集合元素才可以;否则将会引发java.util.Concurrent ModificationException异常。 - Iterator迭代器采用的是快速失败(fail-fast)机制,一旦在迭代过程中检测到该集合已经被修改(通常是程序中的其他线程修改),程序立即引发异常。
8.2.3 使用Lambda表达式遍历Iterator
- java 8 为Iterator新增了一个forEachRemaining(Consumer action) 方法(参数也是函数式接口)。程序会依次将集合元素传给Consumer的accept(T t) 方法。
Iterator it = books.iterator();
it.forEachRemaining(obj -> System.out.println("集合元素:" + obj));
- Iterator的
forEachRemaining()
方法来遍历集合元素,参数是一个Lambda表达式(目标类型是Comsumer),因此上面代码也可用于遍历集合元素。
8.2.4 使用foreach循环遍历集合元素
for (Object obj : books){
//此处的book变量也不是集合元素本身
String book = (String) obj;
System.out.println(book);
}
- 使用foreach循环来迭代更加简洁。foreach循环中的迭代变量也不是集合元素本身,系统只是依次把集合元素的值赋给迭代变量,因此在foreach循环中修改迭代变量的值也没有任何实际意义。
- 同样,当使用foreach循环迭代访问集合元素时,该集合也不能被改变,否则将引发Concurrent ModificationException异常。
8.2.5 使用java 8 新增的Predicate操作集合
java 8 为Collection集合新增了一个removeIf(Predicate filter)
方法(批量删除符合filter条件的所有元素)。Predicate对象作为参数,也是函数式接口,可使用Lambda表达式。
//过滤条件:所有长度小于10的字符串元素都会被删除
books.removeIf(ele -> ((String)ele).length() < 10);
8.2.6 使用java 8 新增Stream操作集合
- Java8还新增了Stream、IntStream、LongStream、DoubleStream等流式API,这些API代表多个支持串行和并行聚集操作的元素。上面4个接口中,Stream是一个通用的流接口,而IntStream、LongStream、DoubleStream则代表元素类型为int、long、double的流。
- Java8还为上面每个流式API提供了对应的Builder,例如Stream.Builder、IntStream.Builder、LongStream.Builder、DoubleStream.Builder。
- 独立使用Stream的步骤如下:
①使用Stream 或XxxStream的builder()
类方法创建该Stream对应的Builder。
②重复调用Builder的add()
方法向该流中添加多个元素。
③调用Builder的build()
方法获取对应的Stream。
④调用Stream的聚集方法。
IntStream is = IntStream.builder()
.add(20)
.add(13)
.add(-2)
.add(18)
.build();
//下面调用聚集方法的代码每次只能执行一行
System.out.println("is")
System.out.println("is 所有元素的最大值:"+is.max().getAsInt());
System.out.println("is 所有元素的最小值:"+is.min().getAsInt());
System.out.println("is 所有元素的总和:"+is.sum());
System.out.println("is 所有元素的总数:"+is.count());
System.out.print1n("is所有元素的平均值:”+is.average());
System.out.print1n("is所有元素的平方是否都大于20:"+is.allMatch(ele -> ele*ele > 20));
System.out.println("is是否包含任何元素的平方大于20:"+is.anyMatch(ele -> ele*ele > 20));
//将is映射成一个新 Stream,新Stream的每个元素是原Stream元素的2倍+1
IntStream newIs = is.map(ele -> ele*2 + 1);
//使用方法引用的方式来遍历集合元素
newIs.forEach(System.out :: println);
- 中间方法:中间操作允许流保持打开状态,并允许直接调用后续方法。上面程序中的map()方法就是中间方法。中间方法的返回值是另外一个流。
- 末端方法:末端方法是对流的最终操作。当对某个Stream执行末端方法后,该流将会被“消耗”且不再可用。上面程序中的sum()、count()、average()等方法都是末端法。
- 两个特征:
有状态的方法:这种方法会给流增加一些新的属性,比如元素的唯一性、元素的最大数量、保证元素以排序的方式被处理等。有状态的方法往往需要更大的性能开销。
短路方法:短路方法可以尽早结束对流的操作,不必检查所有的元素。 - Stream常用的中间方法:
filter(Predicate predicate)
:过滤Stream中所有不符合predicate的元素。
mapToXxx(ToXxxFunction mapper)
:使用ToXxxFunction对流中的元素执行一对一的转换,该方法返回的新流中包含了ToXxxFunction转换生成的所有元素。
peek(Consumer action)
:依次对每个元素执行一些操作,该方法返回的流与原有流包含相同的元素。该方法主要用于调试。
distinct()
:该方法用于排序流中所有重复的元素(判断元素重复的标准是使用equals()比较返回true)。这是一个有状态的方法。
sorted()
:该方法用于保证流中的元素在后续的访问中处于有序状态。这是一个有状态的方法。
limit(long maxSize)
:该方法用于保证对该流的后续访问中最大允许访问的元素个数。这是一个有状态的、短路方法。
- Stream常用的末端方法:
forEach(Consumer action)
:遍历流中所有元素,对每个元素执行action。
toArray()
:将流中所有元素转换为一个数组。
reduce()
:该方法有三个重载的版本,都用于通过某种操作来合并流中的元素。
min()
:返回流中所有元素的最小值。
max()
:返回流中所有元素的最大值。
count()
:返回流中所有元素的数量。
anyMatch(Predicate predicate)
:判断流中是否至少包含一个元素符合Predicate条件。
allMatch(Predicate predicate)
:判断流中是否每个元素都符合Predicate条件。
noneMatch(Predicate predicate)
:判断流中是否所有元素都不符合Predicate条件。
findFirst()
:返回流中的第一个元素。
findAny()
:返回流中的任意一个元素。
- 除此之外,Collection接口提供了一个
stream()
默认方法,该方法可返回该集合对应的流,接下来即可通过流式API来操作集合元素。
//统计书名包含“java”子串的图书数量
books.stratm().filter( ele -> ((String)ele).contins("java") ).count();
- 程序只要调用Collection的
stream()
方法即可返回该集合对应的Stream,接下来就可通过Stream提供的方法对所有集合元素进行处理,这样大大地简化了集合编程的代码,这也是Stream编程带来的优势。
8.3 Set集合
Set集合由Set接口和Set接口的实现类组成,Set接口继承了Collection接口,因此包含了Collection接口的所有方法。(Set不允许包含重复元素)
后面介绍的HashSet、TreeSet、EnumSet三个实现类
8.3.1 HashSet类
- HashSet是Set接口的典型实现,按Hash算法来存储集合中的元素,存取、查找性能。
- HashSet具有以下特点:不能保证元素的排列顺序,顺序不同;不是同步的;集合元素值可以是null。
- 向HashSet集合中存入一个元素时,HashSet会调用该对象的
hashCode()
方法来得到该对象的hashCode值,然后根据该hashCode值决定该对象在HashSet中的存储位置。(如果两个元素equals()
方法比较true,但hashCode()
方法返回值不相等,HashSet将会把它们存储在不同的位置,依然可以添加成功。) - 把某个类的对象保存到HashSet集合中,重写这个类的
equals()
方法和hashCode()
方法时,应该尽量保证两个对象通过equals()
方法比较返回true时,它们的hashCode()
方法返回值也相等。 - HashSet中如果有多个元素的hashCode值相同,但它们通过
equals()
方法比较返回false,就需要在一个“桶”里放多个元素(链式结构保存),这样会导致性能下降。
HashSet hs = new HashSet();
- 重写
hashCode()
方法的基本规则:
程序运行中,同一个对象多次调用
hashCode()
方法应该返回相同的值。
两个对象通过equals()
方法比较返回true时,两个的hashCode()
方法应返回相等的值。
对象中用作equals()
方法比较标准的实例变量,都应该用于计算hashCode值。
- 重写hashCode()方法的一般步骤:
(1)计算出一个int类型的hashCode值;(2)用计算出来的对个hashCode值组合计算出一个hashCode值返回; - 当程序把可变对象添加到HashSet中之后,尽量不要去修改该集合元素中参与计算
hashCode()、equals()
的实例变量,否则将会导致HashSet无法正确操作这些集合元素。
8.3.2 LinkedHashSet类
- LinkedHashSet是HashSet的一个子类。使用链表维护元素的次序;遍历时按元素的添加顺序来访问集合里的元素;性能略低于HashSet(因为链表维护内部顺序)。
LinkedHashSet books = new LinkedHashSet();
- LinkedHashSet依然是HashSet,依然不允许集合元素重复。
8.3.3 TreeSet类
- TreeSet是SortedSet接口的实现类,可以确保集合元素处于排序状态。
- TreeSet提供几个额外的方法:
Comparator comparator()
:如果TreeSet采用了定制排序,则该方法返回定制排序所使用的Comparator;如果TreeSet采用了自然排序,则返回null。
Object first()
:返回集合中的第一个元素。
Object last()
:返回集合中的最后一个元素。
Object lower(Objecte)
:返回集合中位于指定元素之前的元素(即小于指定元素的最大元素,参考元素不需要是TreeSet集合里的元素)。
Object higher(Object e)
:返回集合中位于指定元素之后的元素(即大于指定元素的最小元素,参考元素不需要是TreeSet集合里的元素)。
SortedSet subSet(Object fromElement,Object toElement)
:返回此Set的子集合,范围从fromElement(包含)到toElement(不包含)。
SortedSet headSet(Object toElement)
:返回此Set的子集,由小于toElement的元素组成。
SortedSet tailSet(Object fromElement)
:返回此Set的子集,由大于或等于fromElement的元素组成。
- TreeSet采用红黑树的数据结构来存储集合元素。
(1)自然排序
- 自然排序:TreeSet回调用集合元素的
compareTo(Object obj)
方法来比较元素之间的大小关系,按照升序排列。 - 其他实现了Comparable接口的常用类:
BigDecimal、BigInteger以及所有的数值型对应的包装类:按它们对应的数值大小进行比较。
Character:按字符的UNICODE值进行比较。
Boolean:true对应的包装类实例大于false对应的包装类实例。
String:按字符串中字符的UNICODE值进行比较。
Date、Time:后面的时间、日期比前面的时间、日期大。
Treeset ts = new TreeSet();
- 如果把一个对象添加到TreeSet,则该对象的类必须实现Comparable接口,否则程序将会抛出异常。
- 大部分类在实现
compare To(Object obj)
方法时,都需要将被比较对象obj强制类型转换成相同类型,因为只有相同类的两个实例才会比较大小。 - 向TreeSet中添加的应该是同一个类的对象,否则也会引发ClassCastException异常。
- 当把一个对象加入TreeSet集合中时,TreeSet 调用该对象的
compareTo(Object obj)
方法与容器中的其他对象比较大小,然后根据红黑树结构找到它的存储位置。(如果两个对象通过compare To(Object obj)
方法比较相等,新对象将无法添加到TreeSet集合中。) - 判断两个对象是否相等的唯一标准是:两个对象通过
compare To(Object obj)
方法,返回0,TreeSet则会认为它们相等;否则就认为它们不相等。 - 重写该对象对应类应保证有一致的结果,规则:如果两个对象通过
equals()
方法比较返回true时,这两个对象通过compare To(Object obj)
方法比较应返回0。 - 向TreeSet中添加一个可变对象,并且后面程序修改了该可变对象的实例变量,将导致它与其他对象的大小顺序发生了改变,但TreeSet不会再次调整它们的顺序。(甚至可能导致保存的这两个对象通过
compare To(Object obj)
方法比较返回0。) - 推荐不要修改放入HashSet和TreeSet集合中元素的关键实例变量。
(2)定制排序
- 通过Comparator接口的帮助(接口里包含一个int compare(T o1, T o2)方法)。
- 如果需要实现定制排序:则需要在创建TreeSet 集合对象时,提供一个Comparator对象与该TreeSet集合
关联
,由该Comparator对象负责集合元素的排序逻辑。由于Comparator是一个函数式接口,因此可使用Lambda表达式来代替Comparator对象。
TreeSet ts = new TreeSet( (o1, o2) -> {
M m1 = (M)o1;
M m2 = (M)o2;
//根据M对象的age属性决定大小:age越大,M对象反而越小
return m1.age > m2.age ? -1 : m1.age < m2.age ? 1 : 0;
});
- 依然不可以向TreeSet中添加类型不同的对象,否则引发异常。
- TreeSet判断两个集合元素相等的标准是:通过Comparator(或Lambda表达式)比较两个元素返回了0,这样TreeSet不会把第二个元素添加到集合中。
8.3.4 EnumSet类
- EnumSet是一个专为枚举类设计的集合类,所有元素都必须是指定枚举类型的枚举值(创建时显式或隐式的指定),也是有序的。
- 内部以位向量的形式存储,占用内存很小,运行效率很好,尤其批量操作。
- EnumSet集合不允许加入null元素,将会抛出异常。
EnumSet es = EnumSet.allOf(Season.class);
- EnumSet没有暴露任何构造器,应该通过它提供的类方法来创建EnumSet对象:
EnumSet allOf(Class elementType)
:包含指定枚举类里所有枚举值的EnumSet集合。
EnumSet complementOf(EnumSet s)
:其元素类型与指定EnumSet里元素类型相同的EnumSet集合,新EnumSet集合包含原EnumSet集合所不包含的、此枚举类剩下的枚举值(即新EnumSet集合和原EnumSet集合的集合元素加起来就是该枚举类的所有枚举值)。
EnumSet copyOf(Collection c)
:使用一个普通集合来创建EnumSet集合。
EnumSet copyOf(EnumSet s)
:与指定EnumSet具有相同元素类型、相同集合元素的EnumSet集合。
EnumSet noneOf(Class elementType)
:元素类型为指定枚举类型的空EnumSet。
EnumSet of(E first,E...rest)
:包含一个或多个枚举值的EnumSet集合,传入的多个枚举值必须属于同一个枚举类。
EnumSet range(E from,E to)
:创建一个包含从from枚举值到to枚举值范围内所有枚举值的EnumSet集合。
- 还可以复制另外一个EnumSet集合或者Collection集合中的所有元素来创建新的EnumSet集合。(赋值Collection集合时,要求Collection集合的所有元素必须是同一个枚举类的枚举值)
8.3.5 各Set实现类的性能分析
- HashSet和TreeSet是Set的两个典型实现,HashSet性能总是比TreeSet好(TreeSet红黑树算法额外维护)。
- 只有需要保持排序的Set时,才应该使用TreeSet。
- HashSet有一个子类:LinkedHashSet,插入、删除比HashSet略慢,由于链表额外维护;但以为链表,遍历会更快。
- EnumSet是所有Set实现类中性能最好的,但只能保存同一个枚举类的枚举值作为集合元素。
- Set的三个实现类HashSet、TreeSet、EnumSet都是线程不安全的。(若多个线程访问,需通过Collections工具类的synchronizeedSortedSet方法来“包装”该Set集合,最好在创建时进行)
8.4 List集合
List集合代表一个元素有序、可重复的集合,都有其对应的顺序索引。默认按元素的添加顺序设置元素的索引。
8.4.1 java 8 改进的List接口和ListIterator接口
- List作为Collection接口的子接口,可以使用Collection接口里的方法。
List books = new ArrayList();
- List是有序集合,所以增加了一些根据索引来操作集合元素的方法:
void add(int index,Object element)
:将元素 element 插入到List集合的index处。
boolean addAll(int index,Collection c)
:将集合c所包含的所有元素都插入到List集合的index处。
Object get(int index)
:返回集合index索引处的元素。
int indexOf(Object o)
:返回对象o在List集合中第一次出现的位置索引。
int lastIndexOf(Object o)
:返回对象o在List集合中最后一次出现的位置索引。
Object remove(int index)
:删除并返回index索引处的元素。
Object set(int index,Object element)
:将index索引处的元素替换成element对象,返回被替换的旧元素。
List subList(int fromlndex,int tolndex)
:返回从索引fromIndex(包含)到索引tolndex(不包含)
- 所有的List实现类都可以调用这些方法来操作集合元素。
- List增加了根据索引来插入、替换、删除集合元素的方法。
新增的两个默认方法:
void replaceAll(UnaryOperator operator)
:根据operator指定的计算规则重新设置List集合的所有元素。
void sort(Comparator c)
:根据Comparator参数对List集合的元素排序。
- List判断两个对象相等:通过equals()方法比较返回true即可。
- 当调用List的
set(int index,Object element)
方法来改变List集合指定索引处的元素时,指定的索引必须是List集合的有效索引
。即set(int index,Object element)
方法不会改变List集合的长度。 - 与Set只提供了一个
iterator()
方法不同,List还额外提供了一个listIerator()
方法,该方法返回一个Listlterator对象,Listlterator接口继承了Iterator接口,提供了专门操作List的方法。 - Listlterator接口在Iterator 接口基础上增加了如下方法:
boolean hasPrevious()
:返回该迭代器关联的集合是否还有上一个元素。
Object previous()
:返回该迭代器的上一个元素。
void add(Object o)
:在指定位置插入一个元素。
8.4.2 ArrayList和Vector实现类
- ArrayList和Vector作为List类的两个典型实现,完全支持前面的List接口的全部功能。
- ArrayList和Vector类都是基于数组实现的List类,所以ArrayList和Vector类封装了一个动态的、允许再分配的Object[ ]数组。ArrayList或Vector对象使用initialCapacity参数来设置该数组的长度,当向ArrayList或Vector中添加元素超出了该数组的长度时,它们的initialCapacity会自动增加。
- 添加大量元素时,可使用
ensureCapacity(int minCapaticy)
方法一次性增加。 - 可以在创建时就指定initialCapacity的大小(若空或不指定,数组默认长度为10)
- ArrayList和Vector还提供了如下两个方法来重新分配Object[ ]数组。
void ensureCapacity(int minCapacity)
:将ArrayList 或Vector集合的Object[]数组长度增加大于或等于minCapacity值。
void trimToSize()
:调整ArrayList 或Vector集合的Object[]数组长度为当前元素的个数。可减少占用的存储空间。
- ArrayList和Vector在用法上几乎完全一样,但Vector是个古老的集合,有很多缺点,尽量少用。
- ArrayList是线程不安全的,Vector线程安全(也不推荐用)。
- Vector还提供一个stack子类,用于模拟“栈”,“后进先出(LIFO)”。
Object peek()
:返回“栈”的第一个元素,但并不将该元素“pop”出栈。
Object pop()
:返回“栈”的第一个元素,并将该元素“pop”出栈。
void push(Object item)
:将一个元素“push”进栈,最后一个进“栈”的元素总是位于“栈”顶。
也是比较古老,尽量少用,推荐使用ArrayDeque后面讲。
8.4.3 固定长度的List
Arrays.ArrayList
是一个固定长度的List集合,程序只能遍历访问该集合里的元素,不可增加、删除该集合里的元素。
8.5 Queue集合
Queue用于模拟队列这种数据结构,“先进先出FIFO”。Queue接口中定义了几个方法:
void add(Objecte)
:将指定元素加入此队列的尾部。
Object (element)
:获取队列头部的元素,但是不删除该元素。
boolean offer(Objecte)
:将指定元素加入此队列的尾部。当使用有容量限制的队列时,此方法通常比add(Objecte)方法更好。
Object peek()
:获取队列头部的元素,但是不删除该元素。如果此队列为空,则返回null。
Object poll()
:获取队列头部的元素,并删除该元素。如果此队列为空,则返回null。
Object remove()
:获取队列头部的元素,并删除该元素。
8.5.1 PriorityQueue实习类
- PriorityQueue是一个比较标准的队列实现类。(保存队列元素的顺序并不是加入队列的顺序,而是按队列元素的大小进行重新排序)
- PriorityQueue已经违反了队列的最基本规则:先进先出(FIFO)。
PriorityQueue pq = new PriorityQueue();
- PriorityQueue不允许插入null元素,还要对队列进行排序(两种排序方式,自然排序、定制排序,同TreeSet参考8.3.3节)。
8.5.2 Deque接口与ArrayDeque实现类
- Deque接口是Queue接口的子接口,代表一个双端队列。
- Deque接口的一些方法:
void addFirst(Objecte)
:将指定元素插入该双端队列的开头。
void addLast(Objecte)
:将指定元素插入该双端队列的末尾。
Iterator descendinglterator()
:返回该双端队列对应的迭代器,该迭代器将以逆向顺序来迭代队列中的元素。
Object getFirst()
:获取但不删除双端队列的第一个元素。
Object getLast()
:获取但不删除双端队列的最后一个元素。
boolean offerFirst(Object e)
:将指定元素插入该双端队列的开头。
boolean offerLast(Objecte)
:将指定元素插入该双端队列的末尾。
Object peekFirst()
:获取但不删除该双端队列的第一个元素;如果此双端队列为空,则返回null。
Object peekLast()
:获取但不删除该双端队列的最后一个元素;如果此双端队列为空,则返回null。
Object pollFirst()
:获取并删除该双端队列的第一个元素;如果此双端队列为空,则返回null。
Object pollLast()
:获取并删除该双端队列的最后一个元素;如果此双端队列为空,则返回null。
Object pop()(**栈方法**)
:pop出该双端队列所表示的栈的栈顶元素。相当于removeFirst()。
void push(Object e)(**栈方法**)
:将一个元素push进该双端队列所表示的栈的栈顶。相当于addFirst(e)。
Object removeFirst()
:获取并删除该双端队列的第一个元素。
Object removeFirstOccurrence(Object o)
:删除该双端队列的第一次出现的元素o。
Object removeLast()
:获取并删除该双端队列的最后一个元素。
boolean removeLastOccurrence(Object o)
:删除该双端队列的最后一次出现的元素o。
- Deque 不仅可以当成双端队列,也可以当成栈。
ArrayDeque stack = new ArrayDeque();//当成“栈”
ArrayDeque queue = new ArrayDeque();//当成“队列”
- ArrayDeque:Deque接口提供的一个典型的实现类。(基于数组实现的双端队列,可以指定Object[]数组的长度,若不指定长度为16)。
- ArrayList和ArrayDeque两个集合类的实现机制基本相似(底层都采用一个动态的、可重分配的Object[]数组来存储集合元素,超出系统重新分配)。
- ArrayDeque当成“栈”(推荐使用ArrayDeque当栈,避免使用Stack古老)
public class ArrayDequeStack{
public static void main(String[] args){
ArrayDeque stack = new ArrayDeque();
//依次将三个元素push“入栈”
stack.push("疯狂Java讲义");
stack.push("轻量级Java EE企业应用实战");
stack.push("疯狂Android讲义");
//输出:[疯狂Android讲义,轻量级Java EE企业应用实战,疯狂Java讲义]
System.out.println(stack);
//访问第一个元素,但并不将其pop出“栈”,输出:疯狂Android讲义
System.out.println(stack.peek());
//依然输出:[疯狂Android 讲义,疯狂Java讲义,轻量级Java EE企业应用实战]
System.out.println(stack);
//pop出第一个元素,输出:疯狂Android 讲义
System.out.println(stack.pop());
//输出:[轻量级Java EE企业应用实战,疯狂Java讲义]
System.out.println(stack);
}
}
- ArrayDeque当成“队列”
public class ArrayDequeQueue{
public static void main(String[] args){
ArrayDeque queue = new ArrayDeque();
//依次将三个元素“入队”
queue.offer("疯狂Java讲义");
queue.offer("轻量级Java EE企业应用实战");
queue.offer("疯狂Android讲义");
//输出:[疯狂Java讲义,轻量级JavaEE企业应用实战,疯狂Android讲义]
System.out.println(queue);
//访问队列头部的元素,但并不将其po11出队列“栈”,输出:疯狂Java讲义
System.out.println(queue.peek());
//依然输出:[疯狂Java 讲义,轻量级Java EE企业应用实战,疯狂Android讲义]
System.out.println(queue);
//po11出第一个元素,输出:疯狂Java讲义
System.out.println(queue.poll());
//输出:[轻量级Java EE企业应用实战,疯狂Android讲义]
System.out.print1n(queue);
}
}
8.5.3 LinkedList实现类
- LinkedList类是List接口的实现类,意味着他是一个List集合,可以根据索引来随机访问集合中的元素。
- LinkedList还实现了Deque接口,可以被当成双端队列来使用(可以当“栈”、“队列”)
- 下面示范了LinkedList作为List集合、双端队列、栈的用法:
public class LinkedListTest{
public static void main(String[] args){
LinkedList books = new LinkedList();
//将字符串加入队列的尾部
books.offer("疯狂java讲义");
//将一个字符串元素加入栈的顶部
books.push("轻量级Java EE企业应用实战");
//将字符串元素添加到队列的头部(相当于栈顶)
books.offerFirst("疯狂Android讲义");
//以List的方式(按索引)遍历集合元素
for(int i = 0; i < books.size(); i++){
System.out.println(books.get(i));
}
//访问不删除栈顶元素
System.out.println(books.peekFirst());
//访问不删除队列的最后一个元素
System.out.println(books.peekLast());
//栈顶元素弹出栈
System.out.println(books.pop());
//下面输出看到队列中第一个元素被删除
System.out.println(books);
//访问删除队列最后一个元素
System.out.println(books.pollLast());
//下面输出:"轻量级Java EE企业应用实战"
System.out.println(books);
}
}
- ArrayList、ArrayDeque内部以数组的形式保存集合中的元素(随机访问性能好,比使用Iterator迭代器性能都好)
- LinkedList内部以链表的形式保存集合中的元素(插入、删除性能好)
8.5.4 各种线性表的性能分析
- Java 提供的List 就是一个线性表接口,而ArrayList(基于数组的线性表)、LinkedList(基于链表的线性表)又是线性表的两种典型实现。Queue代表了队列,Deque代表了双端队列(既可作为队列使用,也可作为栈使用)
- 总体来说,ArrayList比LinkedList的性能要好,大部分时候建议使用ArrayList。
- 使用List集合建议:(1)遍历时,ArrayList、Vector集合,使用随机访问方法 get 遍历;LinkedList集合,使用迭代器(Iterator)遍历。(2)经常执行插入、删除操作来改变大量数据的List集合,建议使用LinkedList集合。(3)多个线程需要同时访问List集合中的元素,考虑使用Collections将集合包装成线程安全的集合。
8.6 java 8 增强的Map集合
- Map具有映射关系的数据,保存两组值key 和value (key和value都可以是任何引用类型数据),key不允许重复。
- key和value之间存在单向一对一关系。
- Map里包含了一个
keySet()
方法,返回Map里所有key组成的Set集合。 - Map提供了一个Entry内部类来封装key-value对,而计算Entry存储时则只考虑Entry封装的key。
- 从Java源码来看,Java是先实现了Map,然后通过包装一个所有value都为null的Map就实现了Set集合。
- 如果把Map里所有的key放在一起看,它们就组成了一个Set集合(key没有顺序,不能重复)
- 如果把Map里的所有value放在一起看,它们又非常类似于一个List(元素之间可以重复,根据索引来查找),只是Map中的索引不再是整数值,而是另一个对象。
- 如果需要从Map中取出元素,需要该元素的key索引,因此,Map有时也被称为字典,或关联数组。
- Map接口中定义的常用方法:
void clear()
:删除该Map对象中的所有key-value对。
boolean containsKey(Object key)
:查询Map中是否包含指定的key,如果包含则返回true。
boolean containsValue(Object value)
:查询Map中是否包含一个或多个value,如果包含则返回true。
Set entrySet()
:返回Map中包含的key-value对所组成的Set集合,每个集合元素都是Map.Entry(Entry是Map的内部类)对象。
Object get(Object key)
:返回指定key所对应的value;如果此Map中不包含该key,则返回null。
boolean isEmpty()
:查询该Map是否为空(即不包含任何key-value对),如果为空则返回true。
Set keySet()
:返回该Map中所有key组成的Set集合。
Object put(Object key,Object value)
:添加一个key-value对,如果当前Map中已有一个与该key相等的key-value对,则新的key-value对会覆盖原来的key-value对。
void putAll(Map m)
:将指定Map中的key-value对复制到本Map中。
Object remove(Object key)
:删除指定key所对应的key-value对,返回被删除key所关联的value,如果该key不存在,则返回null。
boolean remove(Object key,Object value)
:这是Java8新增的方法,删除指定key、value所对应的key-value对。如果从该Map中成功地删除该key-value对,该方法返回true,否则返回false。
int size()
:返回该Map里的key-value对的个数。
Collection values()
:返回该Map里所有value组成的Collection。
- Map中包括一个内部类Entry,封装了一个key-value对。Entry包含如下三个方法:
Object getKey()
:返回该Entry里包含的key值。
Object getValue()
:返回该Entry里包含的value值。
Object setValue(V value)
:设置该Entry里包含的value值,并返回新设置的value值。
8.6.1 java 8 为Map新增的方法
- Java8除为Map增加了
remove(Object key,Object value)
默认方法之外,还增加了如下方法:
Object compute(Object key,BiFunction remappingFunction)
:该方法使用remappingFunction 根据原key-value 对计算一个新value。只要新value不为null,就使用新value覆盖原 value;如果原value不为null,但新value为null,则删除原key-value对;如果原value、新value同时为null,那么该方法不改变任何key-value对,直接返回null。
Object computelfAbsent(Object key,Function mappingFunction)
:如果传给该方法的key参数在Map中对应的value为null,则使用mappingFunction根据key计算一个新的结果,如果计算结果不为null,则用计算结果覆盖原有的value。如果原Map原来不包括该key,那么该方法可能会添加一组key-value对。
Object computelfPresent(Object key,BiFunction remappingFunction)
:如果传给该方法的key参数在Map中对应的value不为null,该方法将使用remappingFunction根据原key、value计算一个新的结果,如果计算结果不为null,则使用该结果覆盖原来的 value;如果计算结果为null,则删除原key-value对。
void forEach(BiConsumer action)
:该方法是Java8为Map新增的一个遍历key-value对的方法,通过该方法可以更简洁地遍历Map的key-value对。
Object getOrDefault(Object key,V defaultValue)
:获取指定key对应的value。如果该key不存在,则返回defaultValue。
Object merge(Object key,Object value,BiFunction remappingFunction)
:该方法会先根据key参数获取该Map中对应的value。如果获取的value为null,则直接用传入的value覆盖原有的value(在这种情况下,可能要添加一组key-value对);如果获取的value不为null,则使用remappingFunction函数根据原value、新value计算一个新的结果,并用得到的结果去覆盖原有的value。
Object putIfAbsent(Object key,Object value)
:该方法会自动检测指定key对应的value是否为null,如果该key对应的 value为null,该方法将会用新value代替原来的null值。
Object replace(Object key,Object value)
:将Map中指定key对应的value替换成新value。与传统putO方法不同的是,该方法不可能添加新的key-value对。如果尝试替换的key在原Map中不存在,该方法不会添加key-value对,而是返回null。
boolean replace(K key,V old Value,V newValue)
:将Map 中指定key-value对的原value替换成新value。如果在Map中找到指定的key-value对,则执行替换并返回true,否则返回false。
replaceAll(BiFunction function)
:该方法使用BiFunction对原key-value对执行计算,并将计算结果作为该key-value对的value值。
8.6.2 java 8 改进的HashMap和Hashtable实现类
- HashMap 和Hashtable都是Map接口的典型实现类。Hashtable是一个古老的Map实现类(不推荐使用)。
Hashtable ht = new Hashtable();
HashMap ht = new HashMap();
- Hashtable和HashMap有两点区别:(1)Hashtable是线程安全的,HashMap不是(但性能高一点);Hashtable不允许使用null作为key和value,将引发异常,HashMap可以。
- 尽量少用Hashtable实现类,即使需要创建线程安全的Map实现类,可以通过后面介绍的Collections工具类把HashMap变成线程安全的。
- 为了成功地在HashMap、Hashtable中存储、获取对象,用作key的对象必须实现
hashCode()
方法和equals()
方法。 - HashMap、Hashtable不能保证其中key-value对的顺序。
(1)判断两个key相等的标准也是:两个key 通过equals()方法比较返回true,两个key的hashCode值也相等;
(2)判断两个value相等的标准更简单:只要两个对象通过equals)方法比较返回true即可。 - HashMap、Hashtable中还包含一个
containsValue()
方法,用于判断是否包含指定的value。 - 使用自定义类作为key时,如果重写该类的equals(Object obj)和hashCode()方法,则应该保证两个方法的判断标准一致——当两个key 通过equals0方法比较返回true时,两个key的
hashCode()
返回值也应该相同。 - 使用可变对象作为key,并且修改了作为key的可变对象,则可能出现:程序无法准确访问到Map中被修改过的key。(尽量不要使用可变对象,修改作为key的可变对象)
8.6.3 LinkedHashMap实现类
- HashSet 有一个LinkedHashSet子类,HashMap也有一个LinkedHashMap子类;
LinkedHashMap lhm = new LinkedHashMap();
- LinkedHashMap也使用双向链表来维护key-value对的次序(其实只需要考虑key的次序),迭代顺序与key-value对的插入顺序保持一致。
- LinkedHashMap需要维护元素的插入顺序,因此性能略低于HashMap的性能;但因为它以链表来维护内部顺序,所以在迭代访问Map里的全部元素时将有较好的性能。
8.6.4 使用Properties读写属性文件
- Properties类是Hashtable类的子类,可以把Map对象和属性文件关联起来,从而可以把Map对象中的key-value对写入属性文件中,也可以把属性文件中的“属性名=属性值”
加载到Map对象中。Properties里的key、value都是字符串类型。 - 提供三个方法来修改Properties里的key、value值:
String getProperty(String key)
:获取Properties中指定属性名对应的属性值,类似于Map的get(Object key)方法。
String getProperty(String key,String defaultValue)
:该方法与前一个方法基本相似。该方法多一个功能,如果Properties中不存在指定的key时,则该方法指定默认值。
Object setProperty(String key,String value)
:设置属性值,类似于Hashtable的put)方法。
- 还提供了两个读写属性文件的方法:
void load(InputStream inStream)
:从属性文件(以输入流表示)中加载key-value对,把加载到的key-value 对追加到Properties里(Properties是Hashtable的子类,它不保证key-value对之间的次序)。
void store(OutputStream out,String comments)
:将Properties中的key-value对输出到指定的属性文件(以输出流表示)中。
- Properties可以把key-value对以XML文件的形式保存起来,也可以从XML文件中加载key-value对,用法与此类似,此处不再赘述。
8.6.5 SortedMap接口和TreeMap实现类
- 正如 Set 接口派生出SortedSet子接口,SortedSet接口有一个TreeSet实现类。
- 同样 Map接口也派生出一个SortedMap子接口,SortedMap接口也有一个TreeMap实现类。
TreeMap tm = new TreeMap();
- TreeMap就是一个红黑树数据结构,每个key-value对作为红黑树的一个节点。根据key对节点进行排序,两种排序方式:自然排序、定制排序。
- 类似于TreeSet,TreeMap中判断两个key相等的标准是:两个key通过
compareTo()
方法返回0。 - 使用自定义类作为TreeMap的key,则重写该类的
equals()
方法和compareTo()
方法时应保持一致的返回结果:两个key通过equals()方法比较返回true时,它们通过compareTo()方法比较应该返回0。 - 再次强调:Set和Map的关系十分密切,Java源码就是先实现了HashMap、TreeMap等集合,然后通过包装一个所有的value都为null的Map集合实现了Set集合类。
- 与TreeSet类似,TreeMap中提供一系列根据key顺序访问key-value的方法:
Map.Entry firstEntry()
:返回该Map中最小key所对应的key-value对,如果该Map为空,则返回null。
Object firstKe()
:返回该Map中的最小key值,如果该Map为空,则返回null。
Map.Entry lastEntry()
:返回该Map中最大key所对应的key-value对,如果该Map为空或不存在这样的key-value对,则都返回null。
Object lastKey()
:返回该Map中的最大key值,如果该Map为空或不存在这样的key,则都返回null。
Map.Entry higherEntry(Object key)
:返回该Map中位于key后一位的key-value对(即大于指定key的最小key 所对应的key-value对)。如果该Map为空,则返回null。
Object higherKey(Object key)
:返回该Map中位于key后一位的key值(即大于指定key的最小key值)。如果该Map为空或不存在这样的key-value对,则都返回null。
Map.Entry lowerEntry(Object key)
:返回该Map中位于key前一位的key-value对(即小于指定key的最大key所对应的key-value对)。如果该Map为空或不存在这样的key-value对,则都返回null。
Object lowerKey(Object key)
:返回该Map中位于key前一位的key值(即小于指定key的最大key值)。如果该Map为空或不存在这样的key,则都返回null。
NavigableMap subMap(Object fromKey,boolean fromlnclusive,Object toKey,boolean tolnclusive)
:返回该Map的子Map,其key的范围是从fromKey(是否包括取决于第二个参数)到toKey(是否包括取决于第四个参数)。
SortedMap subMap(Object fromKey,Object tokey)
:返回该Map的子Map,其key的范围是从fromKey(包括)到toKey(不包括)。
SortedMap tailMap(Object fromKey)
:返回该Map的子Map,其key的范围是大于fromKey(包括)的所有key。
NavigableMap tailMap(Object fromKey,boolean inclusive)
:返回该Map的子Map,其key的范围是大于fromKey(是否包括取决于第二个参数)的所有key。
SortedMap headMap(Object toKey)
:返回该Map的子Map,其key的范围是小于toKey(不包括)的所有key。
NavigableMap headMap(Object toKey,boolean inclusive)
:返回该Map的子Map,其key的范围是小于toKey(是否包括取决于第二个参数)的所有key。
- 其实它们很简单。因为TreeMap中的key-value对是有序的,所以增加了访问第一个、前一个、后一个、最后一个key-value对的方法,并提供了几个从TreeMap中截取子TreeMap的方法。
8.6.6 WeakHashMap实现类
- WeakHashMap与HashMap的用法基本相似。区别在于,HashMap的key保留了对实际对象的强引用,不会被垃圾回收,也不会自动删除这些key所对应的key-value对;但WeakHashMap的key只保留了对实际对象的弱引用,这些key所引用的对象可能被垃圾回收,也可能自动删除这些key所对应的key-value对。
WeakHashMap whm = new WeakHashMap();
8.6.7 IdentityHashMap实现类
- 这个Map实现类的实现机制与HashMap基本相似,但它在处理两个key相等时比较独特:在IdentityHashMap中,当且仅当两个key 严格相等(key1==key2)时,IdentityHashMap 才认为两个key相等;对于普通的HashMap而言,只要keyl和key2通过
equals()
方法比较返回true,且它们的hashCode值相等即可。
IdentityHashMap ihm = new IdentityHashMap();
- IdentityHashMap是一个特殊的Map实现!它有意违反Map的通常规范:IdentityHashMap要求两个key严格相等时才认为两个key相等。
8.6.8 EnumMap实现类
- EnumMap是一个与枚举类一起使用的Map实现,EnumMap中的所有key都必须是单个枚举类的枚举值。创建EnumMap时必须显式或隐式指定它对应的枚举类。
EnumMap enumMap = new EnumMap(Season.class);
- EnumMap具有如下特征
(1)EnumMap在内部以数组形式保存,所以这种实现形式非常紧凑、高效。
(2)EnumMap根据key的自然顺序(即枚举值在枚举类中的定义顺序)来维护key-value对的顺序。当程序通过keySet()、entrySet()、values()
等方法遍历EnumMap时可以看到这种顺序。
(3)EnumMap不允许使用null作为key,但允许使用null作为value。(如果试图使用null作为key时将抛出NullPointerException异常。如果只是查询是否包含值为null的key,或只是删除值为null的key,都不会抛出异常。) - 与创建普通的Map有所区别的是,创建EnumMap时必须指定一个枚举类,从而将该EnumMap和指定枚举类关联起来。
8.6.9 个Map实现类的性能分析
- 对于Map的常用实现类而言,虽然HashMap和Hashtable的实现机制几乎一样,但由于Hashtable是一个古老的、线程安全的集合,因此HashMap通常比Hashtable要快。
- TreeMap 通常比HashMap、Hashtable要慢(尤其在插入、删除key-value对时更慢),因为TreeMap底层采用红黑树来管理key-value对(红黑树的每个节点就是一个key-value对)。
- 使用TreeMap有一个好处:TreeMap中的key-value对总是处于有序状态,无须专门进行排序操作。当TreeMap被填充之后,就可以调用keySet(),取得由key组成的Set,然后使用toArray)方法生成key的数组,接下来使用Arrays的binarySearch0方法在已排序的数组中快速地查询对象。
- 对于一般的应用场景,程序应该多考虑使用HashMap,因为HashMap正是为快速查询设计的(HashMap底层其实也是采用数组来存储key-value对)。但如果程序需要一个总是排好序的Map时,则可以考虑使用TreeMap。
- LinkedHashMap 比HashMap慢一点,因为它需要维护链表来保持Map中key-value时的添加顺序。
- IdentityHashMap性能没有特别出色之处,因为它采用与HashMap基本相似的实现,只是它使用==而不是equals0方法来判断元素相等。
- EnumMap的性能最好,但它只能使用同一个枚举类的枚举值作为key。
8.7HashSet和HashMap的性能选项
- 对于HashSet及其子类而言,它们采用hash算法来决定集合中元素的存储位置,并通过hash算法来控制集合的大小;
- 对于HashMap、Hashtable及其子类而言,它们采用hash 算法来决定Map中key的存储,并通过hash算法来增加key集合的大小。
- 可能发生“hash冲突”,冲突元素以链表形式存储。
- HashSet、HashMap的hash表包含如下属性:
容量(capacity):hash表中的桶数量。
初始化容量(initial capacity):创建hash表时桶的数量。HashMap和HashSet都允许在构造器中指定初始化容量。
尺寸(size):当前hash表中记录的数量。
负载因子(load factor):负载因子等于“size/capacity”。负载因子为0,表示空的hash表,0.5表示半满的hash表,依此类推。轻负载的hash表具有冲突少、适宜插入与查询的特点(但是使用Iterator迭代元素时比较慢)。
- 除此之外,hash表里还有一个“负载极限”,“负载极限”是一个0~1的数值,“负载极限”决定了hash表的最大填满程度。当hash表中的负载因子达到指定的“负载极限”时,hash表会自动成倍地增加容量(桶的数量),并将原有的对象重新分配,放入新的桶内,这称为rehashing。
- HashSet和HashMap、Hashtable的构造器允许指定一个负载极限,默认的“负载极限”为0.75,这表明当该hash表的3/4已经被填满时,hash表会发生rehashing。
- 使用足够大的初始化容量创建 HashSet和HashMap、Hashtable时,可以更高效地增加记录,但将初始化容量设置太高可能会浪费空间,因此通常不要将初始化容量设置得过高。
8.8 操作集合的工具类:Collections
8.8.1 排序操作
- Collections提供了如下常用的类方法用于对List集合元素进行排序。
void reverse(List list)
:反转指定List集合中元素的顺序。
void shuffle(List list)
:对List集合元素进行随机排序(shuffle方法模拟了“洗牌”动作)。
void sort(List list)
:根据元素的自然顺序对指定List集合的元素按升序进行排序。
void sort(List list,Comparator c)
:根据指定Comparator产生的顺序对List集合元素进行排序。
void swap(List list,inti,intj)
:将指定List集合中的i处元素和j处元素进行交换。
void rotate(List list,int distance)
:当distance为正数时,将list集合的后distance个元素“整体”移到前面;当distance为负数时,将list集合的前distance个元素“整体”移到后面。该方法不会改变集合的长度。
8.8.2 查找、替换操作
- Collections还提供了常用的查找、替换集合元素的类方法。
int binarySearch(List list,Object key)
:使用二分搜索法搜索指定的List集合,以获得指定对象在List集合中的索引。如果要使该方法可以正常工作,则必须保证List中的元素已经处于有序状态。
Object max(Collection coll)
:根据元素的自然顺序,返回给定集合中的最大元素。
Object max(Collection coll,Comparator comp)
:根据Comparator指定的顺序,返回给定集合中的最大元素。
Object min(Collection coll)
:根据元素的自然顺序,返回给定集合中的最小元素。
Object min(Collection coll,Comparator comp)
:根据Comparator指定的顺序,返回给定集合中的最小元素。
void fill(List list,Object obj)
:使用指定元素obj替换指定List集合中的所有元素。
int frequency(Collection c,Objecto)
:返回指定集合中指定元素的出现次数。
int indexOfSubList(List source,List target)
:返回子List对象在父List对象中第一次出现的位置索引;如果父List中没有出现这样的子List,则返回□-1。
int lastIndexOfSubList(List source,List target)
:返回子List对象在父List对象中最后一次出现的位置索引;如果父List中没有出现这样的子List,则返回□-1。
boolean replaceAll(List list,Object oldVal,Object newVal)
:使用一个新值newVal 替换List对象的所有旧值oldVal。
8.8.3 同步控制
- Collections类中提供了多个
synchronizedXxx()
方法,该方法可以将指定集合 包装成线程同步 的集合,从而可以解决多线程并发访问集合时的线程安全问题。 - Java中常用的集合框架中的实现类HashSet、TreeSet、ArrayList、ArrayDeque、LinkedList、HashMap和TreeMap都是线程不安全的。
public class SynchronizedTest{
public static void main(String[] args){
//下面程序创建了4个线程安全的集合对象
Collection c = Collections.synchronizedCollection(new ArrayList());
List list = Collections.synchronizedList(new ArrayList());
Set s = Collections.synchronizedSet(new HashSet());
Map m = Collections.synchronizedMap(new HashMap());
}
}
8.8.4 设置不可变集合
- 不可变的集合对象:只能访问集合元素,不可修改集合元素。
- Collections提供了三类方法来返回一个不可变的集合:(三类方法的参数是原有的集合对象,返回值是该集合的“只读”版本。)
emptyXxx()
:返回一个空的、不可变的集合对象,此处的集合既可以是List,也可以是SortedSet、Set,还可以是Map、SortedMap等。
singletonXxx()
:返回一个只包含指定对象(只有一个或一项元素)的、不可变的集合对象,此处的集合既可以是List,还可以是Map。
unmodifiableXxx()
:返回指定集合对象的不可变视图,此处的集合既可以是List,也可以是Set、SortedSet,还可以是Map、SorteMap等。
//创建一个空的、不可改变的List对象
List unmodifiableList = Collections.emptyList();
//创建一个只有一个元素、不可改变的Set对象
Set unmodifiableSet = Collections.singleton("java");
//返回一个普通的Map对象对应的比可变版本
Map unmodifiableMap = Collectins.unmodifialeMap(scores);
8.8.5 java 9 新增的不可变集合
- 程序直接调用Set、List、Map的
of()
方法即可创建包含N个元素的不可变集合,这样一行代码就可创建包含N个元素的集合。 - 不可变意味着程序不能向集合中添加元素,也不能从集合中删除元素。
//创建包含4个元素的Set集合
Set set = Set.of("java","kotlin","go","swift");
//创建包含4个元素的List集合
List list = List.of(34, -25, 67, 231);
//创建包含3个key-value对的Map集合
Map map = Map.of("语文", 89, "数学", 82, "英语", 92);
//使用Map.entry()方法显示构建key-value对
Map map2 = Map.ofEntries(Map.entry("语文", 89),
Map.entry("数学", 82),
Map.entry("英语", 92));
8.9 烦琐的接口:Enumeration
- Enumeration接口是Iterator迭代器的“古老版本”,有两个方法名字很长,不推荐使用。
- 应该尽量采用Iterator迭代器。