1)Collection接口,子接口及其实现类的继承树。
其中粗线圈出的Set和List接口是Collection派生出的子接口,它们分别代表无序集合和有序集合;Queue是java提供的队列实现。
2)Map接口的众多实现类,这些实现类在功能,用法上存在一些差异,但它们都有一个功能特征:Map保存的每项数据都是key-value对。Map中的key是不可重复的,key用于标识集合里的每项元素,如果需要查阅Map中的数据,总是通过key值。
3)Iterator接口是Collection接口的父接口。Iterator接口也是Java集合框架的成员,但它与Collection系列,Map系列的集合不一样:Collection系列集合和Map系列集合主要用于盛装其他对象,而Iterator则主要用于遍历Collection集合中的元素,Iterator对象也被称为迭代器。
Iterator定义了如下四个方法:
4)Iterator迭代访问Collection集合元素时,Collection集合里的元素不能被改变,只有通过Iterator的remove()方法删除上一次next()方法返回的元素才可以;否则会引发java.util.ConcurrentModificationException异常。下面程序示范了这一点:
5) 使用Lambda表达式遍历Iterator。
java8起为Iterator新增了一个forEachRemaining(Consumer action)方法,该方法所需的Conumer是一个函数式接口。当程序调用Iterator的forEachRemaining(Consumer action)遍历集合元素时,程序会依次将集合元素传给Consumer的accept(T t)方法。
如下程序示范了使用Lambda表达式来遍历集合元素。
6)使用foreach循环遍历集合元素。
foreach循环中的迭代变量也不是集合元素本身,系统只是把集合元素的值赋给了迭代变量,因此在foreach循环中修改迭代变量的值也没有任何实际意义。同样,当使用foreach循环迭代访问集合元素时。该集合也不能被改变。如下
7)Java8起为Collection集合新增了一个removeIf(Predicate filter)方法,该方法会批量删除符合filter条件的所有元素。该方法需要一个Predicate对象作为参数,Predicate是函数式接口,因此可使用Lambda表达式。
如下程序示例了用Predicate来过滤集合
使用Predicate可以充分简化集合的运算,如下程序所示:
public class PredicateTest{
public static void main(String[] args){
//创建一个books集合,为books集合添加元素的代码与前一个程序相同
//...
//统计书名包含“疯狂”字串的图书数
System.out.println(calAll(books,ele->((String)ele).contains("疯狂")));
//统计书名包含“Java”字串的图书数
System.out.println(calAll(books,ele->((String)ele).contains("Java")));
//统计书名字符串长度大于10的图书数
System.out.println(calAll(books,ele->((String)ele).length()>10));
}
private static int calAll(Collection books,Predicate p){
int total=0;
for(Object o:books){
if(p.test(o)){
total++;
}
}
return total;
}
}
8)关于Lambda表达式,如下代码等价:
for (Object n : list) { System.out.println(n); }
list.forEach(n -> System.out.println(n));
list.forEach(System.out::println);
9)HashSet具有如下特点:
1.不能保证元素的排列顺序,顺序可能与添加顺序不同,顺序也有可能发生变化。
2.HashSet不是同步的,如果多个线程同时访问一个HashSet,假设有两个或两个以上线程同时修改了HashSet集合时,则必须通过代码来保证其同步。
3.集合元素值可以是null
10)HashSet集合判断两个元素相等的标准是两个对象通过equals()方法比较相等,并且两个对象的hashCode()方法返回值相同
11)LinkedHashSet可以保证元素的排列顺序与添加顺序一致。
12)HashSet集合采用hash算法来决定元素的存储位置,TreeSet采用红黑树的数据结构来存储集合元素。
13)TreeSet是SortedSet接口的实现类,可以确保各元素处于排序状态。TreeSet有两种排序方式:自然排序和定制排序。
1.TreeSet会调用集合元素的compareTo(Object obj)方法比较元素之间的大小关系,然后将集合元素按升序排列,这种方式 就是自然排序。因此将一个对象添加到TreeSet中时,该对象的类必须实现了Comparable接口,否则抛出运行时异常: ClassCastException。
2.要实现定制排序,则需要在创建TreeSet集合对象时,提供一个Comparator对象与该TreeSet集合关联,由该Comparator 对象负责元素的排序逻辑。由于Comparator是一个函数式接口,因此可以使用Lambda表达式代替Comparator对象。
14)EnumSet是一个专门为枚举类设计的集合类,EnumSet的所有元素都必须是指定枚举类型的枚举值。EnumSet的元素是有序的,以枚举值在Enum类中定义顺序来决定集合元素的顺序。它有如下类方法来创建对象:
示例如下:
enum Season
{
SPRING,SUMMER,FALL,WINTER
}
public class EnumSetTest
{
public static void main(String[] args)
{
// 创建一个EnumSet集合,集合元素就是Season枚举类的全部枚举值
EnumSet es1 = EnumSet.allOf(Season.class);
System.out.println(es1); // 输出[SPRING,SUMMER,FALL,WINTER]
// 创建一个EnumSet空集合,指定其集合元素是Season类的枚举值。
EnumSet es2 = EnumSet.noneOf(Season.class);
System.out.println(es2); // 输出[]
// 手动添加两个元素
es2.add(Season.WINTER);
es2.add(Season.SPRING);
System.out.println(es2); // 输出[SPRING,WINTER]
// 以指定枚举值创建EnumSet集合
EnumSet es3 = EnumSet.of(Season.SUMMER , Season.WINTER);
System.out.println(es3); // 输出[SUMMER,WINTER]
EnumSet es4 = EnumSet.range(Season.SUMMER , Season.WINTER);
System.out.println(es4); // 输出[SUMMER,FALL,WINTER]
// 新创建的EnumSet集合的元素和es4集合的元素有相同类型,
// es5的集合元素 + es4集合元素 = Season枚举类的全部枚举值
EnumSet es5 = EnumSet.complementOf(es4);
System.out.println(es5); // 输出[SPRING]
}
}
15)List接口的常用方法:
16)与Set只提供一个iterator方法不同,List还额外提供了一个listItarator()方法,该方法返回一个ListIterator对象,ListIterator接口继承了Iterator接口,提供了专门操作List的方法。ListIterator接口在Iterator接口基础上增加了如下方法:
如下代码示范了ListIterator的使用方法:
public class ListIteratorTest
{
public static void main(String[] args)
{
String[] books = {
"疯狂Java讲义", "疯狂iOS讲义",
"轻量级Java EE企业应用实战"
};
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());
}
}
}
17)Arrays工具类提供了asList(Object o)方法,该方法可以把一个数组或指定个数的对象转换成一个List集合,这个List集合既不是ArrayList实现类的实例,也不是Vector实现类的实例,而是Arrays的内部类ArrayList的实例。Arrays.ArrayList是一个固定长度的List集合,程序只能遍历访问该集合里的元素,不可增加,删除该集合里的元素。如下所示:
18)Queue接口有一个实现类PriorityQueue实现类,还有一个Deque接口。Deque代表一个双端队列,双端队列可以同时从两端来添加,删除元素,因此Deque实现类既可以当成队列使用,也可当成栈使用。Java为Deque提供了ArrayDeque和LinkedList两个实现类。
19)PriorityQueue违反队列基本规则,它将加入队列的元素排序,支持有自然排序和定制排序两种方式,自然排序时使用poll()方法将看到元素从小到大的顺序移出队列。
20)使用栈这种数据结构时,推荐使用ArrayDeque,尽量避免使用Stack,因为Stack是古老的集合,性能较差。
21)ArrayDeque实现类可以当作栈使用(使用posh()和pop()方法),也可以当作队列使用(使用offer()与poll()),两种方式使用同一个peek()方法。
22)LinkedList是List接口的实现类,还实现了Deque接口,因此既可以当作队列,也可以当作栈来使用。
23)LinkedHashMap中key-value节点是有序的,与插入顺序一致。HashMap和Hashtable则不能保证插入元素顺序。
24)Properties类是Hashtable类的子类,其key,value都是String类型。该类有如下5个方法来操作Properties里的key,value值
下面程序示范了Properties类的用法:
1)对于TreeSet集合而言,它判断两个对象是否相等的唯一标准是:两个对象通过compareTo(Object obj)方法比较是否返回0————如果返回0,表示相等;返回1,表示不相等。
以上代码在内存中示意图如下:
2)各Set实现类的性能分析:
HashSet和TreeSet是Set的两个典型实现,HashSet的性能总比TreeSet好,因为TreeSet需要额外的红黑树算法来维护集合元素次序。只有当需要一个保持排序的Set时,才应该使用TreeSet,否则都应该使用HashSet。
HashSet还有一个子类:LinkedHashSet,对于普通的插入,删除操作,LinkdeHashSet比HashSet要略微慢一点,这是由维护链表所带来的额外开销造成的。但由于有了链表,遍历LinkedHashSet会更快。
EnumSet是所有Set实现类里性能最好的,但它只能保存同一个枚举类型的枚举值作为集合元素。
Set的三个实现类HashSet,TreeSet和EnumSet都是线程不安全的。如果有多个线程同时访问一个Set集合,并且有超过一个线程修改了该Set集合,则必须手动保证该Set集合的同步性。通常可以通过Collections工具类的synchronizedSortedSet方法来包装该Set集合。例如:
3)ArrayList集合是线程不安全的,Vector集合则是线程安全的。即使为了线程安全,也不推荐使用Vector实现类,使用 Collections工具类可以将一个ArrayList变成线程安全的。
4)LinkedList内部以链表形式来保存集合中的元素,因此随机访问集合元素时性能较差,但在插入,删除元素时性能比较出色。ArrayList,ArrayDeque内部以数组的形式来保存集合中的元素,因此随机访问集合元素时性能较好。虽然Vector也是以数组的形式来存储集合元素,但因为它实现了线程同步功能(实现机制并不好),所以各方面性能很差。
5)关于List集合的使用建议:
1.如果需要遍历集合元素,对于ArrayList,Vector集合,应采用随机访问方法(get())来遍历集合元素,这样性能更好;对于LinkedList集合,则应该使用迭代器(Iterator或ListIterator)来遍历集合元素。
2.如果需要经常执行插入,删除操作来改变包含大量数据List集合的大小,优先考虑使用LinkedList集合。使用ArrayList,Vector集合可能需要经常重新分配内部数组的大小,效果可能较差。
3.如果有多个线程需要同时访问List集合中的元素,开发者可优先考虑使用Collections将集合包装成线程安全的集合。
6)Hashtable(t不大写)和HashMap的两点典型区别:
1.Hashtable线程安全,HashMap线程不安全。
2.Hashtable不可以使用null作为key或value的值,HashMap可以使用null作为key或value。
7)各Map实现类性能分析。
HashMap与Hashtable实现机制几乎一样,但由于Hashtable是一个古老的,线程安全的集合,因此HashMap通常运行更快。
TreeMap通常比HashMap,Hashtable要慢(尤其在插入,删除时),因为TreeMap底层采用红黑树管理key-value对。
对于一般应用场景,程序应多考虑使用HashMap,因为HashMap正是为快速查询设计的(HashMap底层也是采用数组来存储key-value对)。但如果程序需要一个总是排好序的Map时,则优先考虑使用TreeMap。