Java集合大致分为四大类,Set,List,Queue,Map。Set代表不记录插入顺序,不可重复的集合;List代表记录顺序,可重复的集合;Map代表具有映射关系的集合;Queue代表了队列的集合。集合可以存储数量不等的对象,实现常用的数据结构。集合就像是一种容器,在Java5之前,将对象的引用放入后,集合会丢失所有对象的数据类型,都当作Object类处理。Java5之后增加泛型,Java集合可以记住容器中对象的数据类型。

集合概述

 在初期阶段,要保存数据,通常使用数组。但是这种结构的灵活性很差比如,保存数量不确定的数据和具有映射关系的数据。Java的集合类也称为容器类,位于java.util下,java.util.concurrent下还提供了一些多线程支持的集合类。集合中只能保存对象(引用变量)。集合类主要由两个接口派生:Collection和Map。
Collection
 Set和List接口分别代表了不记录插入顺序和记录插入顺序的集合,Queue时Java提供的队列实现,与List相似。
Map
 Map接口的实现类非常多,主要的特点都是保存数据要用key-value对,key是数据的唯一标识,不可重复。对于粗线框起来的四个接口,大致可以看成三类,List的可以记住顺序,List长度可变,根据下表访问;Map的访问需要通过key值;Set无法记住顺序,所以是通过元素本身访问,不能重复。Queue有点特殊,不在此列。

Collection和Iterator接口

 Collection接口是List,Set和Queue接口的父接口,它自生定义了一些方法:

  • boolean add(Object o):该方法用于向集合添加一个元素,如果集合对象被添加操作改变了,返回true
  • boolean addAll(Collection c):该方法用于将集合c中的所有元素添加到指定集合,返回值同上
  • void clear():清除集合内的所有元素,将长度变为0
  • boolean contains(Object o):返回集合里是否包含指定元素
  • boolean containsAll(Collection c):返回集合里是否包含集合c里的所有元素
  • boolean isEmpty():返回集合是否为空
  • Iterator iterator():返回一个Iterator对象(迭代器),用于遍历集合里的元素
  • boolean remove(Object o):删除集合中的指定元素o,当包含一个或多个元素o时,将删除第一个,返回true
  • boolean removeAll(Collection c):从集合中删除集合c里包含的所有元素,如果删除了一个或以上的元素,返回true
  • boolean retain(Collection c):从集合中删除集合c里不包含的元素,如果该操作改变了调用方法的集合,则返回true
  • int size():返回集合中元素的个数
  • Object[] toArray():把集合转换为一个数组

 所有的Collection实现类都重写了toString()方法,System.out.println(c)将把集合中全部元素打印出来。

使用Lambda表达式遍历集合

 查看Collection接口的源码,发现它继承了Iterable接口。Java8为Iterable接口增加了一个forEach(Consumer action)的默认方法,该方法所需参数的类型是一个函数式接口。因此Collection集合也可以调用该方法。Iterable接口中的该方法的具体代码如下,Consumer是另一个接口,这里的语法涉及到泛型,先不做解释。

1
2
3
4
5
6
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}

 Consumer接口中相关代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@FunctionalInterface
public interface Consumer<T> {

/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);

/**
* Returns a composed {@code Consumer} that performs, in sequence, this
* operation followed by the {@code after} operation. If performing either
* operation throws an exception, it is relayed to the caller of the
* composed operation. If performing this operation throws an exception,
* the {@code after} operation will not be performed.
*
* @param after the operation to perform after this operation
* @return a composed {@code Consumer} that performs in sequence this
* operation followed by the {@code after} operation
* @throws NullPointerException if {@code after} is null
*/
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}

 当调用forEach(Consumer action)方法时,程序会将集合元素传给Consumer的accept(T t)方法,因为该接口只有这一个抽象方法,所以这是个函数式接口,所以可以用Lambda表达式。

1
2
3
4
5
6
7
8
9
10
11
12
13
import java.util.Collection;
import java.util.HashSet;

public class CollectionTest {
public static void main(String[] args) {
Collection books = new HashSet();
books.add("天国之秋");
books.add("阿拉伯的劳伦斯");
books.add("全球通史");
books.add("极端年代");
books.forEach(obj -> System.out.println("迭代集合元素")+obj );
}
}

 上面的代码会有warning,因为没用泛型,可以忽略。

使用Java8增强的Iterator遍历元素

 Iterator接口也是Java集合框架的成员,但不用与容器,只作为迭代器。注意与Iterable接口的区别。Iterator接口中定义了如下四个方法:

  • boolean hasNext():如果被迭代集合元素还没有被遍历完,则返回true
  • Object next():返回集合里的下一个元素
  • void remove():删除集合里上一次next方法返回的元素
  • void forEachRemaining(Consumer action):Java8新增的默认方法,该方法可以使用Lambda表达式来遍历集合元素

 前面的Iterable接口就是包含了Iterator<T> iterator()方法,所以实现了迭代。那么为什么要这样用一个接口包含迭代相关的方法,再用另一个接口包含呢?参看链接:https://blog.csdn.net/github_39424631/article/details/75048955

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.util.Iterator;

public class IteratorTest {
public static void main(String[] args) {
Iterator it = books.iterator();
while (it.hasNext()) {
String book = (String) it.next();
System.out.println(book);
if (book.equals("天国之秋")){
it.remove();
}
book = "测试字符串";
}
System.out.println(books);
}
}

 Iterator对象必须依靠一个集合对象。这段代码由两个地方值得注意,一是book = "测试字符串";这句并不会修改集合中的值,因为是值传递。二是it.remove();删除的是上一次的next返回值。如果改books.remove(book);修改当前的next返回值就会出java.util.ModificationException异常。因为是不允许在迭代过程中集合的,后面的HashSet,ArrayList都是如此。因为如果可以修改可能会产生因为资源共享而引发的潜在问题。而且,Iterator采用的是快速失败(fail-fast)机制,一旦检测到集合修改就立即引发异常。

使用Lambda表达式遍历Iterator

 与使用Iterable类似。forEachRemaining(Consumer action)的代码:

1
2
3
4
5
default void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}

使用foreach循环遍历集合元素

 相较于用Iterator接口迭代访问,foreach反而更加方便。使用类似for(Object obj : books)的语句就可以,但是任然不可以在迭代中修改集合的元素。参考链接:https://segmentfault.com/q/1010000003775035?_ea=362485

用Java8新增的Predicate操作集合

 Java8为Collection集合新增了一个removeIf(Predicate filter)方法,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
default boolean removeIf(Predicate<? super E> filter) {
Objects.requireNonNull(filter);
boolean removed = false;
final Iterator<E> each = iterator();
while (each.hasNext()) {
if (filter.test(each.next())) {
each.remove();
removed = true;
}
}
return removed;
}
1
books.removeIf(ele -> ((String)ele).lenght() < 3);

 这句代码的意思是删除集合中符合条件的元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.util.function.Predicate;

public class PredicateTest2 {
public static void main(String[] args) {
System.out.println(calAll(books, ele -> ((String) ele).contains("年")));
System.out.println(calAll(books, ele -> ((String) ele).contains("之")));
System.out.println(calAll(books, ele -> ((String) ele).length() > 10));
}

public static int calAll(Collection books, Predicate p) {
int total = 0;
for (Object obj : books) {
if (p.test(obj)) {
total++;
}
}
return total;
}
}

ele -> ((String) ele).length() > 10)这句产生了一个Predicate接口的实例,这个对象被传入calAll方法。然后迭代books,将里面的元素传入这个实例的test()方法,具体形参就是ele,test()方法的实现过程就是Lambda表达式的过程。

使用Java8新增的Stream操作集合

 关于Stream的详细内容,单开一篇写,书上写的没头没尾非常糟糕。

Set集合

 Set集合的最大特点就是不能重复,Set集合通常不能记住元素的添加顺序,Set集合与Collection基本相同,没有提供任何额外的方法。当试图添加一个重复的元素,add()方法会返回false。这些事Set集合的通用知识,HashSet,TreeSet,EnumSet三个实现类又各有不同。

HashSet类

 HashSet是Set的典型接口,大多数就是使用这个类HashSet按Hash算法来存储集合中的元素。具有如下特点:

  • 不能保证元素的排列顺序
  • HashSet不是同步的,如果由多个线程同时访问一个HashSet,必须通过代码来保证同步
  • 集合元素值可以是null

 当向HashSet中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该元素的hashCode值。HashSet判断元素相等的条件是:equals()方法结果为true,hashCode相同。只有这样才算做两个一样的元素。在重写hashCode()方法和equals()方法时,应注意,equals()返回true时,hashCode也应相同。因为如果不同,那么就会添加成功,使得存在两个相同的元素。(这里的相同指按照自定义规则的equals方法的结果,本来Object中要求同一个对象才能返回true,但是重写后规则改变,可能是不同对象内容相同。)

 上面说如果equals方法返回true,hashCode却不同会出现两个一样的值;反过来,如果hashCode相同,equals却不同,HashSet会试着将两个元素保存在一个地方,实际就会用一种链式结构(?)来保存多个对象。HashSet访问元素是根据hashCode值快速定位的,如果具有两个相同的hashCode值,就会导致性能下降。

 总之不论为什么,重写equals和重写hashCode必须同时进行。

 hash被译为哈希,散列。hash算法的价值在于速度,该算法可以直接根据hashCode值算出元素的存储位置,从而快速定位该元素。类似于数组的索引,只是hashCode的值不是连续的。

 因为默认的hashCode方法就是返回当前对象的地址。所以一般需要重写,规则如下:

  • 程序中多次调用同一个对象的hashCode方法应该返回相同值
  • 当equals返回了true,那么hashCode应该相同(equals返回false,hashCode也可以相同,但是应竭力避免)
  • 对象中用于equals方法作比较标准的实例变量,都应该被用于计算hashCode值

 具体步骤:

1.把对象内每个有意义的实例变量(即参与equals()方法比较标准的实例变量)计算出一个int类型的hashCode值,计算方式如下表:

实例变量类型 计算方式
boolean hashCode=(f?0:1);
int,char,short,byte hashCode=(int)f;
long hashCode=(int)(f^(f>>>32));
float hashCode=Float.floatToIntBits(f);
double long L=Double.doubleToLongBits(f); hashCode=(int)(L^(L>>>32));
引用类型 (null==f?0:hashCode=f.hashCode());

2.用第一步计算出来的多个hashCode值组成一个hashCode值,代码如下:
return f1.hashCode() + (int)f2;

 这句代码的意义不明,Effective Java一书中也不是这么写的,书上的代码是:result 31 * result + c,完整的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class User {
private String name;
private int age;
private String passport;
//getters and setters, constructor
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof User)) {
return false;
}
User user = (User) o;
return user.name.equals(name) &&
user.age == age &&
user.passport.equals(passport);
}
//Idea from effective Java : Item 9
@Override
public int hashCode() {
int result = 17;
result = 31 * result + name.hashCode();
result = 31 * result + age;
result = 31 * result + passport.hashCode();
return result;
}
}

3.当向集合中添加可变对象时必须十分小心,如果修改了这个对象的内容,有可能会导致算出来的hashCode与其他的一样,这样就会出现无法正确访问到该对象。

LinkedHashSet类

 HashSet类还有一个子类LinkedHashSet,与HashSet基本类似,但是它是有顺序的,是通过链表来实现记住添加顺序。在性能上略低于HashSet,但是在迭代时会有很好的性能。

TreeSet

 TreeSet是SortedSet接口的实现类,正如名字一样,它可以确保元素处于排序状态。与HashSet相比,该类还提供了其他几个额外方法:

  • Comparator comparator():如果TreeSet采用了定制排序,则返回定制排序使用的comparator,如果是自然排序,则返回null
  • Object first():返回集合第一个元素
  • Object last():返回集合最后一个元素
  • Object lower(Object e):返回指定元素的前一个元素,指定元素可以不是集合中的
  • Object higher(Object e):返回指定元素的后一个元素,指定元素可以不是集合中的
  • SortedSet subSet(Object fromElement,Object toElement):返回此Set中的子集,范围是fromElement(包含)到toElement(不包含)
  • SortedSet headSet(Object toElement):返回子集,由从头到toElement的元素组成
  • SortSet tailSet(Object fromElement):返回子集,有fromElement到尾组成

 TreeSet的插入顺序又元素大小决定。HashSet采用hash算法决定存储位置,TreeeSet则是采用红黑树的数据结构来存储元素,具体的存储规则有两种。

自然排序

 TreeSet会调用集合元素的compareTo(Object obj)方法来比较元素大小,然后按升序排列。这就是自然排序。Java提供了一个Compareable接口,接口里定义了一个compareTo(Object obj)方法,该方法返回一个整数值,0代表相等,正数代表调用者大于被比较者,负数代表小于。一些常用的类已经实现了这个接口:

  • BigDecimal,BigInteger以及所有的数值型对应的包装类:按数据大小比较
  • Character:按字符的unicode值比较
  • Boolean:true对应的大于false
  • String:按字符的unicode值比较
  • Data,Time:比较日期时间

 当将一个对象添加到这种集合时,必须实现了Comparable()接口,不然无法比较的话,也没法正确的加入元素,会引发ClassCastException异常。但是第一个可以不实现这个接口,因为它没什么可以比较的。不过将这唯一一个元素取出来时,仍就会引发上面的异常。

 另一点需要注意的是,只有对象类型相同才有比较的意义。若类型不相同,就会强制转换。也就是说,TreeSet中的元素都是一个类型的,当类型不同时,comparaTo方法没法正确执行。会抛出ClassCastException异常。

 对于这种集合来说,判断是否相同的唯一标准是compareTo判断是否相等。如果相等就没法添加到集合中。也就是说,哪怕equals方法返回的是true,只要compareTo返回的不是0,该元素仍旧可以添加进去。所以equals和compareTo方法应该保持一致性,避免这种不合理情况。反过来,equals判断不相等,compareTo判断相等的情况也要避免。

 对于添加可变对象也要非常注意。假定集合{1,2,3,4},1被改为4(这里简写,实际集合中加入的必须是对象)。这就导致了有两个一样的元素,对这两个一样的,你都没法删除,只能等待重新索引,例如删掉2之后。然后就会是采用链式结构将两个一样的元素特殊处理。这种情况也要竭力避免。

定制排序

 如果需要实现降序排列等特别的排序,则需要借助Comparator接口的int compare(T o1,T o2)抽象方法。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import java.util.TreeSet;

class M {
int age;

public M(int age) {
this.age = age;
}

@Override
public String toString() {
return "M[age:" + age + "]";
}
}

public class TreeSetTest4 {
public static void main(String[] args) {
//TreeSet ts2=new TreeSet();
TreeSet ts = new TreeSet((o1, o2) -> {
M m1 = (M) o1;
M m2 = (M) o2;
return m1.age > m2.age ? -1 : m1.age < m2.age ? 1 : 0;
});
ts.add(new M(5));
ts.add(new M(8));
ts.add(new M(-1));
ts.add(new M(3));
ts.add(new M(9));
//System.out.println(ts);
System.out.println(ts.headSet(new M(4)));
}
}

 在new一个ts时,调用了另一个与上面方式不同的构造器,代码如下:

1
2
3
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}

 该构造器接受一个Comparator接口的实现。而Comparator接口只有一个int compare()方法,所以是一个函数式接口。而且还new了一个TreeMap,再追更溯源到TreeMap中,代码如下:

1
2
3
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}

 由此可见 compatator这个东西最终被内含在了TreeSet中。但是这里如何实现了add时将add的参数传递过去,我还是不明白。

EnumSet类

 看名字就能知道这是什么集合,该集合中的所有元素都是指定枚举类型的枚举值。该枚举类型在创建EnumSet时显式或隐式指定。EnumSet类的顺序由枚举值在枚举类中的定义顺序来决定集合元素的顺序。

 EnumSet内部以位向量形式存储,这种形式非常经凑,高效。尤其是在批量操作时。该集合不允许加入null,但是可以查找null和删除null。

 该类不提供构造器,需通过类方法创建EnumSet对象。常用方法如下:

  • EnumSet allOf(Class elementType):创建一个包含指定枚举类里所有枚举值的集合
  • EnumSet complemetOf(EnumSet s):创建一个枚举类中的所有枚举值与指定集合的元素的差集
  • EnumSet copyOf(Collection c):使用一个普通集合创建一个EnumSet集合
  • EnumSet copyOf(EnumSet s):复制一个EnumSet集合
  • EnumSet noneOf(Class elementType):创建一个元素类型为指定枚举类型的空EnumSet
  • EnumSet of(E first,E…rest):创建一个包含一个或多个枚举值的EnumSet集合,传入的多个枚举值必须是同一枚举类
  • EnumSet range(E from,E to):创建一个从from到to枚举值范围内所有枚举值的EnumSet集合

各Set实现类的性能分析

 Hashset的性能要优于TreeSet,特别是常用的添加,查询等,因为TreeSet需要额外的红黑树。Hashset的子类LinkedHahSet有一个链表,所以遍历会很快。EnumSet是性能最好的,但是元素的类型受限。

List集合

 List代表有序,可重复的集合,每个元素都有类似于数组下标的索引。

Java8改进的List接口和ListIterator接口

 List接口作为Collection接口的子接口,可以使用Collecion中的全部方法,同时,由于是有序集合,所以有一些根据索引操作集合的方法。

  • 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在集合中第一次出现的索引
  • int lastIndexOf(Object o):同上,返回最后一次出现的索引
  • Object remove(int index):删除并返回index处的元素
  • Object set(int index,Object element):将index处的元素替换成element对象,返回被替换的旧元素
  • List subList(int fromIndex,int toIndex):返回从fromIndex(包含)到toIndex(不包含)的子集

 除此之外,Java8还为List接口增加了两个默认方法:

  • void replaceAll(UnaryOperator operator):根据operator指定的计算规则重新设置List集合的所有元素
  • void sort(Comparator c):根据Comparator参数对集合重新排序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.util.List;

public class Listtest {
public static void main(String[] args) {
List books=new java.util.ArrayList();
books.add(new String("天国之秋"));
books.add(new String("追忆似水年华"));
books.add(new String("果壳中的宇宙"));
books.add(new String("罗马人的故事"));
books.add(new String("天朝的崩溃"));
System.out.println(books);
books.add(1,new String("编译原理"));
for (int i = 0; i < books.size(); i++) {
System.out.println(books.get(i));
}
System.out.println(books);
System.out.println(books.indexOf(new String("编译原理"))); //(1)
books.set(1,new String("C++标准库"));
System.out.println(books);
System.out.println(books.subList(1,2));
}
}
1
2
3
4
5
6
7
8
9
10
11
[天国之秋, 追忆似水年华, 果壳中的宇宙, 罗马人的故事, 天朝的崩溃]
天国之秋
编译原理
追忆似水年华
果壳中的宇宙
罗马人的故事
天朝的崩溃
[天国之秋, 编译原理, 追忆似水年华, 果壳中的宇宙, 罗马人的故事, 天朝的崩溃]
1
[天国之秋, C++标准库, 追忆似水年华, 果壳中的宇宙, 罗马人的故事, 天朝的崩溃]
[C++标准库]

 注意在代码(1)处,new了一个新的对象,这个新的“编译原理”与集合中的是不同的,但是从返回值1可以看出:List判断两个对象相等只要通过equals()就行。

 Java8新增的两个方法的代码如下:

1
2
3
4
5
6
7
8
9
default void sort(Comparator<? super E> c) {
Object[] a = this.toArray();
Arrays.sort(a, (Comparator) c);
ListIterator<E> i = this.listIterator();
for (Object e : a) {
i.next();
i.set((E) e);
}
}
1
2
3
4
5
6
7
default void replaceAll(UnaryOperator<E> operator) {
Objects.requireNonNull(operator);
final ListIterator<E> li = this.listIterator();
while (li.hasNext()) {
li.set(operator.apply(li.next()));
}
}

 Comparator和UnaryOperator都是函数式接口。所以都可以使用Lambda表达式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.util.List;

public class Listtest {
public static void main(String[] args) {
List books=new java.util.ArrayList();
books.add(new String("天国之秋"));
books.add(new String("追忆似水年华"));
books.add(new String("果壳中的宇宙"));
books.add(new String("罗马人的故事"));
books.add(new String("天朝的崩溃"));
books.sort((o1,o2)->((String)o1).length()-((String)o2).length());
System.out.println(books);
books.replaceAll(ele->((String)ele).length());
System.out.println(books);
}
}

 与Set只有提供了iterator()方法不同,List还提供了一个listIterator()方法,该方法返回一个ListIterator对象,ListIterator接口继承了Iterator接口,提供了专门操作List的方法。ListIterator接口除了继承Iterator接口的方法,还增加了一些方法:

  • boolean hashPrevious():返回该迭代器关联的集合是否还有上一个元素
  • Object previous():返回该迭代器的上一个元素
  • void add(Object o):在制定位置插入一个元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;

public class Listtest {
public static void main(String[] args) {
String[] books = {"天国之秋", "算法导论","具体数学","深入理解Java虚拟机"};
List bookList=new ArrayList();
for (int i = 0; i < books.length; i++) {
bookList.add(books[i]);
}
ListIterator lit=bookList.listIterator();
while(lit.hasNext()){
System.out.println(lit.next());
lit.add("-------------");
}
System.out.println("========开始反向迭代==========");
while (lit.hasPrevious()){
System.out.println(lit.previous());
}
}
}

ArrayList和Vector实现类

 ArrayList和Vector作为List的两个典型实现,完全支持前面介绍的List接口的全部功能。

 ArrayList和Vector是基于数组实现的List类,所以内部封装了一个动态的,允许再分配的Object[]数组。ArrayList和Vector对象使用initialCapacity参数设置数组的长度,当添加元素超过数组长度时,initialCapacity自动增加。

 由于这种自动的行为,所以大多数时候程序员不需要管这个initialCapacity的。但是一旦多次重复的添加过大的数组,造成多次修改initialCapacity值,这时就应该使用ensureCapacity(int minCapacity)方法一次性增加initialCapacity值。

initialCapacity可以在创建集合时制定,如果不指定,默认值为10.

 除此之外,这两个集合还提供如下两个方法来重新分配Object[]数组:

  • void ensureCapacity(int minCapacity):上面提到的,让数组长度增加大于或等于minCapacity值
  • void trimToSize():将数组长度调整为当前元素的个数,减少浪费的空间

 ArrayList和Vector的显著区别是,ArrayList是线程不安全的,当多个线程访问ArrayList集合时,如果有修改,必须手动同步。此外,Vector是一个非常古老的类,早在集合之前就有了,在集合出现后,Vector被重写,出现了一些方法上的重复,命名不同但功能一样的情况(可能已经有所改进),不建议使用Vector类,即使它有线程安全的优点。

 Vector类还有个Stack子类,但是同样也是非常古老的,所以也不建议使用。要使用栈则应该使用List的实现类ArrayDeque,这个类也实现了Deque接口。

固定长度的List

 在以前的文章中曾经提到过Arrays这个类,专门用于对数组的一些操作。其中有一个asList(Object… a)方法,它可以把一个数组或若干个对象转换为一个List集合。这个转换而来的特殊Lsit既不是Vector也不是ArrayList,而是Arrays中的内部类ArrayList实例(与ArrayList类不是一回事)。

 对于这种特殊的List,不可以增加或删除,尽管在写代码时可以写List.add()或List.remove(),但是在运行时将产生UnsupportedOperationException异常。

Queue集合

 Queue接口中定义了如下几个方法:

  • void add(Object e):将指定元素加入队列的尾部
  • Object element():获取队列的头部,不删除
  • boolean offer(Object e):将指定元素加入队列的队尾,当使用容量有限的队列时,此方法比add方法好
  • Object peek():获取队列的头部元素,若队列为空,返回null
  • Object poll():返回并删除队列的头部元素,队列为空时,返回null
  • Object remove():获取队列的头部元素并删除

PriorityQueue实现类

 PriorityQueue是一个比较标准的队列实现类,其保存顺序是按照元素大小决定的。所以调用peek和poll方法的结果不是先进先出的(FIFO)。从这个意义来说,PriorityQueue已经违反了一个队列最基本的规则。

 PriorityQueue队列不允许插入null元素。同时,由于需要排序,与TreeSet类似有自然排序和定制排序两种。

Deque接口和ArrayDeque实现类

 Deque接口是Queue接口的子接口,它代表一个双端队列,即两端都可出队或入队。该接口该出了以下方法从两端操作队列(不完全):

  • void addFirst(Object e):将指定元素插入头部
  • void addLast(Object e):将指定元素插入尾部
  • Iterator descendingIterator():返回该双端队列对应的迭代器,该迭代器将以逆向顺序来迭代队列中的元素
  • Object getFirst():获取但不删除第一个
  • Object getLast():获取但不删除最后一个
  • boolean offerGirst(Object e):将制定元素插入开头,返回boolean值
  • boolean offerLast(Object e):将指定元素插入末尾,返回boolean值
  • Object peekFirst():获取但不删除该队列的第一个,如果队列为空,返回null
  • Object peekLast():获取但不删除该队列的最后一个,如果队列为空,返回null
  • Object pollFirst():获取并删除该队列的第一个,如果队列为空,返回null
  • Object pollLast():获取并删除该队列的最后一个,如果队列为空,返回null
  • Object pop():(栈方法)pop出该双端队列表示的栈的栈顶元素,相当于removeFirst()
  • void push(Object e):(栈方法)将一个元素插入栈顶,相当于addFirst(Object e)
  • Object removeFirst():获取并删除该双端队列的第一个元素
  • Object removeFirstOccurrence(Object o):删除队列中第一次出现的元素o
  • Object removeLast():获取并删除该双端队列的最后一个元素
  • boolean removeLastOccurrence(Object o):删除队列中最后一次出现的元素o

 从上面的方法可以看出,Deque接口的方法具有栈的“特质”。具体当做栈来使用则是ArrayDeque实现类。他是一个基于数组实现的双端队列。创建Deque时同样可以指定numElements参数,该参数用于指定数组长度。如果不指定,默认为16。

 下面代码示范了把ArrayDeque当做“栈”用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.util.ArrayDeque;

public class ArrayDequeStack{
public static void main(String[] args) {
ArrayDeque stack=new ArrayDeque();
stack.push("Java编程思想");
stack.push("组合数学");
stack.push("现代操作系统");
stack.push("TCP/IP详解");
System.out.println(stack);
System.out.println(stack.peek());
System.out.println(stack.pop());
System.out.println(stack);
}
}

 当然,ArrayDeque仍然可以作为一个队列使用。

LinkedList实现类

 LinkedList实现了Deque接口,也实现了List接口,可以根据索引来访问集合中的元素。而由于实现了Deque接口,所以上面提到的用与双端队列,用于栈,用于队列都是可以的。所有的基于数组的集合,例如ArrayList,ArrayDeque内部都是以数组保存元素的,所以随机访问的性能很好。而LinkedList是用链表实现的,随机访问的效率略差,但插入,删除时性能出色。

各种线性表的性能分析

 List就是一个线性表接口,ArrayList,LinkedList有是线性表的两种典型实现:基于数组和基于链表。Queue代表队列,Deque代表双端队列(也可用于栈)。

 尽管上一节写到了ArrayList,LinkedList之间有着性能差异,但是它的影响对于初级应用来说是微乎其微的。而且总体来说还是ArrayList更胜一筹,只在插入等操作有劣势,所以大部分时候用ArrayList就可以了。

 关于List集合,有如下建议:

  • 如果需要遍历集合元素,对于ArrayList,Vector集合,应该采用随机访问方法(get)来遍历集合;对于LinkedList,则应该采用迭代器(Iterator)来遍历集合。
  • 如果需要经常执行插入,删除操作则应考虑用LinkedList集合

Java8增强的Map集合

 Map用于保存具有映射关系的数据,Map集合里保存着两组值,一组用于保存key,另一组保存了value。key和value可以是任何引用类型的数据。key是不可以重复的。value是可以重复的。注意,value可以重复不是说一个key对应多个value,key和value是单项一对一关系,总能根据一个key找到唯一的,确定的value。

 把key和value分开来看,key构成了一个set,没有顺序,不能重复。实际上Map里面确实包含一个keySet()方法,用于返回key组成的set集合。

 Set和Map的关系非常的紧密,key的存储形式和Set里的元素存储形式很像,Map的子类的名字和Set子类的名字也非常相似,例如Set接口下有HashSet,LinkedSet,SortedSet(接口),TreeSet,EnumSet等子接口和实现类,而Map中也有HashMap,LinkedHashMap,SortedMap(接口),TreeMap,EnumMap等子接口和实现类。正如名字所暗示的,Map的这些实现类和子接口中的key值的存储形式与对应Set集合中的元素的存储形式完全相同。Map提供了一个Entry内部类来封装key-value对,key和value组成了Entry,多个Entry构成了Map。如果把value当做key的附属,Map可以视为一个Set。事实上,在Java中实现Set是实现了一个value值全为null的Map。

 再看value。把所有的value放在一起非常类似List:元素之间可以重复,每个元素可以根据索引来查找。只是Map中不使用整数值做索引值,而是使用key。

 Map可以视作两种集合的映射。

 下面列出了Map接口中定义的常用方法:

  • void clear():删除该Map对象中的所有key-value对
  • boolean containsKey(Object key):查询Map中是否包含指定key
  • boolean containsValue(Object value):查询Map中是否包含一个或多个指定value
  • Set entrySet():返回Map中包含的key-value对组成的Set集合,每个集合元素都是Map.Entry对象
  • Object get(Object key):返回指定key对应的value,如果不包含,则返回null
  • boolean isEmpty():检查Map是否为空,是则返回true
  • Set keySet():返回Map中所有的key组成的Set集合,注意与entrySet区别
  • Object put(Object key,Object value):添加一个key-value对,如果已经存在相同的key,新的value会覆盖旧的
  • void putAll(Map m):将指定Map中的key-value复制到本Map中
  • Object remove(Object key):删除指定key的key-value对,返回指定key的value值
  • boolean remove(Object key,Object value):Java8新增方法,删除指定key和vaule对应的key-value对。如果删除成功返回true
  • 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值

 下面的代码示范了一部分方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.util.HashMap;
import java.util.Map;

public class MapTest {
public static void main(String[] args) {
Map map = new HashMap();
map.put("HTTP权威指南", 1);
map.put("PHP 编程", 2);
map.put("C++ Primer", 3);
map.put("Java核心技术", 1);
System.out.println(map.put("HTTP权威指南", 4));
System.out.println(map);
System.out.println("是否包含C++ Primer:" + map.containsKey("C++ Primer"));
System.out.println("是否包含value为2的key:" + map.containsValue(2));
for (Object key : map.keySet()) {
System.out.println(key + "--->" + map.get(key));
}
}
}

Java为Map新增的方法

 Java8除了增加了remove(Object key,Object value)默认方法外,还增加了如下方法(只列出方法声明):

  • Object compute(Object key, Bi Function 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 computeIfPresent(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。与传统put()方法不同的是,该方法不可能添加新的key-value对。如果尝试替换的key在原Map中不存在,该方法不会添加key-value对,而是返回null。
  • boolean replace(K key, V oldValue, V newValue):将Map中指定key-value对的原value替换成新value。如果在Map中找到指定的key-value对,则执行替换并返回true ,否则返回false。
  • replaceAll(BiFunction function):该方法使用BiFunction对原key-value对执行计算,并将计算结果作为该key-value对的value值。

Java8改进的HashMap和Hashtable实现类

 HashMap和Hashtable实现类是Map接口的典型实现类,它们的关系类似ArrayList和Vector的关系。同样的Hashtable也是非常古老的实现类,有两个繁琐的方法elements()和keys()方法,前者类似于Map接口中定义的values方法,后者类似与Map中的keySet方法。

 Java8改进了HashMap的实现,使得在使用HashMap时,出现了key冲突仍就具有较好的性能。HashMap和Hashtable的典型区别在于:

  • Hashtable是线程安全的,HashMap不是,所以后者性能稍好,但是即使是为了做到线程安全,也不应使用Hashtable
  • Hashtable不允许使用null作为key和value,会引发NullPointerException异常,而HashMap可以,但是因为key是不能重复的,所以只能有一个key为null,value无此限制

 用作key的对象必须实现hashCode()方法和equals()方法。与HashSet方法类似,HashMap和Hashtable不能保证元素的存储顺序。而equals方法和hashCode方法的规则与之类似,equals返回true,hashCode也应返回true。而判断是否包含某个value的方法(containsValue())是通过equals方法。

LinkedHashMap实现类

 LinkedHashMap内部使用双向链表来维护key-value的顺序,该顺序即插入顺序。

1
2
3
4
5
6
7
8
9
10
11
import java.util.LinkedHashMap;

public class LinkedHashMapTest {
public static void main(String[] args) {
LinkedHashMap scores=new LinkedHashMap();
scores.put("语文",87);
scores.put("数学",77);
scores.put("英语",85);
scores.forEach((key,value)-> System.out.println(key+"-->"+value));
}
}

使用Properties读写属性文件

 Properties类是Hashtable的子类,用于处理属性文件,例如ini文件。这个类用于将属性文件的“属性名=属性值”对读取到Map中,或者将MapEntry对写入到属性文件。

  • String getProperty(String key):获取属性文件中指定属性名的属性值
  • String getProperty(String key,String defaultValue):与上面的相似,如果不存在指定属性值,则指定默认值
  • Object setProperty(String key,String value):设置属性值

 除此之外,还有两个读写属性文件的方法:

  • void load(InputStream inStream):从属性文件(以输入流表示)中加载key-value对,把加载的key-value追加到Properties里
  • void store(OutputStream out,String comments):上面的反过来

SortedMap接口和TreeMap实现类

 与Set接口有个子接口SortedSet,SortedSet接口有个TreeSet实现类一样,Map接口也派生出一个SortedMap子接口,SortedMap也有一个TreeMap实现类。

 TreeMap就是一个红黑树数据结构,每个key-value对即作为红黑树的一个节点TreeMap存储key-value对时,需要根据key对节点进行排序,TreeMap可以保证所有的key-value处于有序状态。同样也有两种排序方式,自然排序和定制排序:

  • 自然排序:所有key必须实现Comparable接口,并且所有key应该是同一类,否则抛出ClassCastException
  • 定制排序:创建TreeMap时,传入一个Comparator对象,负责对key排序,key不要求实现Comparable接口

 类似TreeSet中判断两个元素相等的标准,TreeMap的标准是:两个key通过compareTo()方法判断。equals方法的结果应和compareTo的结果相匹配。

  • Map.Entry firstEntry():返回该Map中最小key所对应的key-value对,如果该Map为空,则返 回 null。
  • Object firstKey():返回该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。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import java.util.TreeMap;

class R implements Comparable {
int count;

public R(int count) {
this.count = count;
}

@Override
public String toString() {
return "R[count:]" + count + "]";
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj != null && obj.getClass() == R.class) {
R r = (R) obj;
return r.count == this.count;
}
return false;
}

@Override
public int compareTo(Object obj) {
R r = (R) obj;
return count > r.count ? 1 : count < r.count ? -1 : 0;
}
}

public class TreeMapTest {
public static void main(String[] args) {
TreeMap tm = new TreeMap();
tm.put(new R(3), "高级编译器设计与实现");
tm.put(new R(-5), "算法图解");
tm.put(new R(9), "Java并发编程的艺术");
System.out.println(tm);
System.out.println(tm.firstEntry());
System.out.println(tm.lastEntry());
System.out.println(tm.higherKey(new R(2)));
System.out.println(tm.lowerEntry(new R(2)));
System.out.println(tm.subMap(new R(-1), new R(4)));
}
}

WeakHashMap实现类

 WeakHashMap与HashMap的用法基本相似。与HashMap的区别在于,HashMap的key保留了对实际对象的强引用,而WeakHashMap保留的是弱引用。前者内部的key引用的所有对象不会被垃圾回收机制回收,HashMap也不会删除key对应的key-value;而后者不同,key引用的对象如果没有被其他强引用变量引用,则key引用的对象就有可能被回收,WeakHashMap也有可能删除对应的key-value。

IdentityHashMap实现类

 IdentityHashMap提供了与HashMpa类似的基本相似的方法,也允许用null,也不保证顺序,唯一明显不同的就是,IdentityHashMap比HashMap更严格的比较是否相同,它使用“==”。

EnumMap实现类

 EnumMap是一个与枚举类一起使用的Map实现,EnumMap中的所有key都必须是单个枚举类的枚举值。创建EnumMap时必须显式或隐式指定它对应的枚举类。EnumMap具有如下特征:

  • EnumMap在内部以数组形式保存,所以这种实现形式非常紧凑、高效。
  • EnumMap根据key的自然顺序(即枚举值在枚举类中的定义顺序)来维护key-value对的顺序。 当程序通过keySet()、entrySet()、 values()等方法遍历EnumMap时可以看到这种顺序。
  • EnumMap不允许使用null作为key,但允许使用null作为value。如果试图使用null作为key时将抛出NullPointerException异常。如果只是查询是否包含值为null的key,或只是删除值为null的key,都不会抛出异常。

 在创建EnumMap时必须指定一个枚举类,从而将EnumMap和指定枚举类关联起来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.util.EnumMap;

enum Season {
SPRING, SUMMER, FALL, WINTER
}

public class EnumMapTest {
public static void main(String[] args) {
EnumMap enumMap=new EnumMap(Season.class);
enumMap.put(Season.SUMMER,"炎炎夏日");
enumMap.put(Season.SPRING,"春暖花开");
System.out.println(enumMap);
}
}

各Map实现类的性能分析

 虽然HashMap和Hashtable实现机制几乎一样,但由于Hashtable是一个古老的,线程安全的集合,所以通常它要慢一点。

 TreeMap通常要比HashMap,Hashtable要慢,尤其在插入,删除时。因为它是使用红黑树管理key-value对的。

 使用TreeMap有一个好处:TreeMap中的key-value对总是处于有序状态,无须专门进行排序操作。当TreeMap被填充之后,就可以调用keySet(),取得由key组成的Set,然后使用toArrayQ方法生成key的数组,接下来使用Arrays的binarySearch()方法在已排序的数组中快速地查询对象。

 对于一般的应用场景,程序应该多考虑使用HashMap,因为HashMap正是为快速查询设计的 (HashMap底层其实也是采用数组来存储key-value对)。但如果程序需要一个总是排好序的Map时,则
可以考虑使用TreeMap。

 LinkedHashMap比HashMap慢一点,因为它需要维护链表来保持Map中key-value时的添加顺序。IdentityHashMap性能没有特别出色之处,因为它采用与HashMap基本相似的实现,只是它使用”==” 而不是equalsO方法来判断元素相等。EnumMap的性能最好,但它只能使用同一个枚举类的枚举值作为key。

HashSet和HashMap的性能选项

 对于HashSet及其子类而言,它们采用hash算法来决定集合中元素的存储位置,并通过hash算法来控制集合的大小;对于HashMap、Hashtable及其子类而言,它们采用hash算法来决定Map中key 的存储,并通过hash算法来增加key集合的大小。

 hash表里可以存储元素的位置被称为“桶(bucket)”,在通常情况下,单个“桶”里存储一个元素, 此时有最好的性能:hash算法可以根据hashCode值计 算出“桶”的存储位置,接着从“桶”中取出元素。但hash表的状态是open的:在发生“hash冲突”的情况下,单个桶会存储多个元素,这些元素以链表形式存储,必须按顺序搜索。

 因为HashSet和HashMap、Hashtable都使用hash算法决定其元素(HashMap则只考虑key)的存储,因此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的构造器允许指定一个负载极限,HashSet和HashMap、Hashtable默认的“负载极限”为 0.75,这表明当该hash表的3/4已经被填满时,hash表会发生rehashing。“负载极限”的默认值(0.75)是时间和空间成本上的一种折中:较高的“负载极限”可以降低hash表所占用的内存空间,但会增加查询数据的时间开销,而查询是最频繁的操作(HashMap的get()与put()方法都要用到查询);较低的“负载极限”会提高查询数据的性能,但会增加hash表所占用的内存开销,程序员可以根据实际情况来调整HashSet和HashMap的“负载极限”值。

操作结合的工具类Collections

 Java提供了一个操作Set,List和Map等集合的工具类:Collections.该工具类提供了大量对集合元素进行排序,查询,修改等操作,还提供了讲集合对象设置为不可变,集合对象实现同步控制等方法。

排序操作

 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, int i, int j):将指定List集合中的i处元素和j处元素进行交换。
  • void rotate(List list, int distance):当distance为正数时,将list集合的后distance个元素“整体”移到前面;当distance为负数时,将list集合的前distance个元素“整体”移到后面。该方法不会改变集合的长度。

查找、替换操作

 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, Object o):返回指定集合中指定元素的出现次数。
  • 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。

同步控制

 Collections类中提供了多个synchronizedXxx()方法,该方法可以将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题。

 Java中常用的集合框架中的实现类HashSet、TreeSet、ArrayList、ArrayDeque、LinkedList、HashMap和TreeMap都是线程不安全的。如果有多个线程访问它们,而且有超过一个的线程试图修改它们,则存在线程安全的问题。Collections提供了多个类方法可以把它们包装成线程同步的集合。

1
2
3
4
5
6
7
8
9
10
import java.util.*;

public class SynchronizedTest {
public static void main(String[] args) {
Collection c = Collections.synchronizedCollection(new ArrayList());
List list = Collections.synchronizedList(new ArrayList());
Set s = Collections.synchronizedSet(new HashSet());
Map m = Collections.synchronizedMap(new HashMap());
}
}

设置不可变集合

 Collections提供了如下三类方法来返回一个不可变的集合:

  • emptyXxx():返回一个空的、不可变的集合对象,此处的集合既可以是List,也可以是SortedSet、Set,还可以是Map、SortedMap等。
  • singletonXxx():返回一个只包含指定对象(只有一个或一项元素)的、不可变的集合对象,此处的集合既可以是List,还可以是Map。
  • unmodifiableXxx():返回指定集合对象的不可变视图,此处的集合既可以是List,也可以是Set、SortedSet,还可以是 Map、SorteMap 等。
1
2
3
4
5
6
7
8
9
10
11
12
import java.util.*;

public class UnmodifiableTest {
public static void main(String[] args) {
List unmodifiableList = Collections.emptyList();
Set unmodifiableSet = Collections.singleton("具体数学");
Map scores = new HashMap();
scores.put("语文", 85);
scores.put("数学", 95);
Map unmodifiableMap = Collections.unmodifiableMap(scores);
}
}

Enumeration接口

 Enumeration接口是Iterator迭代器的“古老版本”,从JDK1.0开始,Enumeration接口就已经存在了(Iterator从JDK1.2才出现)。Enumeration接口只有两个名字很长的方法。

  • boolean hasMoreElements():如果此迭代器还有剩下的元素,则返回true。
  • Object nextElement():返回该迭代器的下一个元素,如果还有的话(否则抛出异常)。

总结

 给出一篇文章作为总结:https://www.cnblogs.com/ysocean/p/6555373.html

 这张图也很不错,单独放出来:集合
 全文完,共三万五千余字。