首页 > 代码库 > 第八章.Java集合

第八章.Java集合

Java集合类是一种特别有用的工具类,可用于存储数量不等的对象。Java集合大致可分为Set、List、Queue和Map四种体系

Set代表无序、不可重复的集合

List代表有序、重复的集合

Map代表具有映射关系的集合

Java5又增加了Queue代表一种队列集合

java集合概述:

  为了保存数量不确定的数据,以及保存具有映射关系的数据(也被称为关联数组),java提供了集合类。

  集合类主要负责保存、盛装其他数据,因此,集合类也被称为容器类。所有的集合类都在java.util包下,后来为了处理多线程环境下的并发安全问题,Java5在java.util.concurrent

   包下提供了一些多线程支持的集合类。

  集合和数组不一样,数组元素可以是基本类型的值,也可以是对象(实际上保存的是对象的引用变量);而集合里只能保存对象(实际上只是保存对象的引用,但通常习惯上认

   为集合里保存的是对象)

  Java的集合类主要由两个接口派生而出:Collection和Map,Collection和Map是Java集合框架的根接口,这两个接口又包含了一些子接口或实现类。

技术分享

Set和List接口是Collection接口派生的两个子接口,分别表示了无序集合和有序集合;Queue是Java提供的队列实现。

技术分享

  对于Set、List、Queue、Map四种集合,最常用的实现类是:HashSet、TreeSet、ArrayList、ArrayDeque、LinkedList和HashMap、TreeMap。

Collection接口 和 Iterator接口:

  Collection接口是List、Set和Queue接口的父接口

  Collection接口定义了以下操作集合元素的方法:

    1.boolean add(Object o):该方法用于向集合中添加一个元素,若集合对象被添加操作改变了,则返回true。

    2.boolean addAll(Collection c):该方法把集合c里的所有元素添加到指定集合里。若集合对象被添加操作改变了,则返回true。

    3.void clear():清除集合里的所有元素,将集合长度变为0。

    4.boolean contains(Object o):返回集合里是否包含指定元素。

    5.boolean containsAll(Collection c):返回集合里是否包含集合c里的所有元素

    6.boolean isEmpty():返回集合是否为空。当集合长度为0时,返回true,否则返回false。

    7.Iterator iterator():返回一个Iterator对象,用于遍历集合里的元素

    8.boolean remove(Object o):删除集合中的指定元素0,当集合中包含了一个或多个元素o时,该方法只删除第一个符合条件的元素,该方法将返回true。

    9.boolean removeAll(Collection c):从集合中删除集合c中包含的所有元素(相当于把调用该方法的集合减集合c),若删除了一个或一个以上的元素,则该方法返回true

    10.boolean retainAll(Collection c):从集合中删除集合c里不包含的元素(相当于把调用该方法的集合编程该集合和集合c的交集),若该操作改变了调用该方法的集合,则

     该方法返回true。

    11.int size():该方法返回集合里元素的个数

    12.Object[] toArray():该方法把集合转换成一个数组,所有的集合元素变成对应的数组元素。

  上面的方法无需硬性记忆,对于集合的操作无非是:添加对象、删除对象、清空集合、判断集合是否为空等。

技术分享
 1 import java.util.Collection;
 2 import java.util.ArrayList;
 3 import java.util.HashSet;
 4 
 5 public class CollectionTest{
 6     public static void main(String[] args){
 7         Collection c = new ArrayList();
 8         //添加元素
 9         c.add("孙悟空");
10         //虽然集合里不能放基本类型的值,但Java支持自动装箱
11         c.add(6);
12         System.out.println("c集合的元素个数为:" + c.size());//输出2
13         //删除指定元素
14         c.remove(6);
15         System.out.println("c集合的元素个数为:" + c.size());//输出1
16         //判断是否包含指定字符串
17         System.out.println("c集合是否包含\"孙悟空\"字符串:" + c.contains("孙悟空"));//输出true
18         c.add("轻量级Java EE企业应用实战");
19         System.out.println("c集合的元素:" + c);
20         Collection books = new HashSet();
21         books.add("轻量级Java EE企业应用实战");
22         books.add("疯狂Java讲义");
23         System.out.println("c集合是否完全包含books集合?" + c.containsAll(books));//输出false
24         //用c集合减去books集合里的元素
25         c.removeAll(books);
26         System.out.println("c集合的元素:" + c);
27         //删除c集合里所有的元素
28         c.clear();
29         System.out.println("c集合的元素:" + c);
30         //控制books集合里只剩下c集合里也包含的元素
31         books.retainAll(c);
32         System.out.println("books集合的元素:" + books);
33     }
34 }
View Code

技术分享

    使用System.out.println()方法来输出集合对象时,将输出[ele1, ele2, ...]的形式,因为所有Collection实现类都重写了toString()方法,该方法可以一次性的输出集合中的所

     有元素。

使用Lambda表达式遍历集合:

  Java8位Iterable接口新增了一个forEach(Consumer action)默认方法,该方法所需参数的类型是一个函数式接口,而Iterable接口是Collection接口的父接口因此Collection集合也

   可直接调用该方法。

  当程序调用Iterable的forEach(Consumer action)遍历集合时,程序会依次将集合元素传给Consumer的accept(T t)方法(该接口中唯一的抽象方法)。

技术分享
 1 import java.util.Collection;
 2 import java.util.HashSet;
 3 
 4 public class CollectionEach{
 5     public static void main(String[] args){
 6         //创建一个集合
 7         Collection books = new HashSet();
 8         books.add("轻量级Java EE企业应用实战");
 9         books.add("疯狂Java讲义");
10         books.add("疯狂Android讲义");
11         //调用forEach方法遍历集合
12         books.forEach(obj -> System.out.println("迭代集合元素:" + obj));
13     }
14 }
View Code

 技术分享

    使用Java8增强的Iterator遍历集合元素:

      Iterator主要用于遍历(即迭代访问)Collection集合中的元素,Iterator对象也被称为迭代器

      Iterator接口定义的4个方法:

        1.boolean hasNext():若被迭代的集合元素还没有被遍历完,则返回true

        2.Object next():返回集合中的下一个元素

        3.void remove():删除集合中上一次next方法返回的元素。

        4.void forEachRemainIng(Consumer action):这是Java8为Iterator新增的默认方法,该方法可使用Lambda表达式来遍历集合元素

技术分享
 1 import java.util.HashSet;
 2 import java.util.Collection;
 3 import java.util.Iterator;
 4 
 5 public class IteratorTest{
 6     public static void main(String[] args){
 7         //创建集合、添加元素的代码与前一个程序相同
 8         Collection books = new HashSet();
 9         books.add("轻量级Java EE企业应用实战");
10         books.add("疯狂Java讲义");
11         books.add("疯狂Android讲义");
12         //获取books集合对应的迭代器
13         Iterator it = books.iterator();
14         while(it.hasNext()){
15             //it.next()方法返回的数据类型是Object类型,因此需要强制类型转换
16             String book = (String) it.next();
17             System.out.println(book);
18             if(book.equals("疯狂Java讲义")){
19                 //从集合中删除上一次next()方法返回的元素
20                 it.remove();
21             }
22             //对book变量赋值,不会改变集合元素本身
23             book = "测试字符串";
24         }
25         System.out.println(books);
26     }
27 }
View Code

技术分享

  从上面代码可以看出,Iterator仅用于遍历集合,Iterator本身并不提供盛装对象的能力。若需要创建Iterator对象,则必须有一个被迭代的集合。

  Iterator必须依附于Collection对象,若有一个Iterator对象,则必然有一个与之关联的Collection对象。

  使用Iterator迭代访问Collection集合元素时,Collection集合里的元素不能被改变,只有通过Iteratorde remove()方法删除上一次next()方法返回的集合元素才可以;否则将会引发

   java.util.ConcurrentModificationException异常:

技术分享
 1 import java.util.HashSet;
 2 import java.util.Collection;
 3 import java.util.Iterator;
 4 
 5 public class IteratorErrorTest{
 6     public static void main(String[] args){
 7         //创建集合、添加元素的代码与前一个程序相同
 8         Collection books = new HashSet();
 9         books.add("轻量级Java EE企业应用实战");
10         books.add("疯狂Java讲义");
11         books.add("疯狂Android讲义");
12         //获取books集合对应的迭代器
13         Iterator it = books.iterator();
14         while(it.hasNext()){
15             //it.next()方法返回的数据类型是Object类型,因此需要强制类型转换
16             String book = (String) it.next();
17             System.out.println(book);
18             if(book.equals("疯狂Java讲义")){
19                 //使用Iterator迭代过程中,不可修改集合元素,下面代码引发异常
20                 books.remove(book);
21             }
22         }
23         System.out.println(books);
24     }
25 }
View Code

技术分享

没有报错,不知道什么原因。

  使用Lambda表达式遍历Iterator:

    Java8为Iterator新增了一个forEachRemaining(Consumer action)方法,该方法所需Consumer参数同样也是函数式接口:

技术分享
 1 import java.util.HashSet;
 2 import java.util.Collection;
 3 import java.util.Iterator;
 4 
 5 public class IteratorEach{
 6     public static void main(String[] args){
 7         //创建集合、添加元素的代码与前一个程序相同
 8         Collection books = new HashSet();
 9         books.add("轻量级Java EE企业应用实战");
10         books.add("疯狂Java讲义");
11         books.add("疯狂Android讲义");
12         //获取books集合对应的迭代器
13         Iterator it = books.iterator();
14         it.forEachRemaining(obj -> System.out.println("迭代集合元素:" + obj));
15     }
16 }
View Code

技术分享

  使用foreach循环遍历集合元素:

    Java5提供了foreach循环迭代访问集合元素:

技术分享
 1 import java.util.HashSet;
 2 import java.util.Collection;
 3 import java.util.Iterator;
 4 
 5 public class ForeachTest{
 6     public static void main(String[] args){
 7         //创建集合、添加元素的代码与前一个程序相同
 8         Collection books = new HashSet();
 9         books.add("轻量级Java EE企业应用实战");
10         books.add("疯狂Java讲义");
11         books.add("疯狂Android讲义");
12         for(Object obj : books){
13             //此处的book变量也不是集合元素本身
14             String book = (String) obj;
15             System.out.println(book);
16             if(book.equals("疯狂Java讲义")){
17                 //下面代码引发异常
18                 books.remove(book);
19             }
20         }
21         System.out.println(books);
22     }
23 }
View Code

没有报错,不知道为什么?

  使用Java8新增的Predicate操作集合:

    Java8位Collection集合新增了一个removeIf(Predicate filter)方法,该方法会批量删除符合filter条件的所有元素。Predicate也是函数式接口。

技术分享
 1 import java.util.Collection;
 2 import java.util.HashSet;
 3 
 4 public class PredicateTest{
 5     public static void main(String[] args){
 6         //创建books集合、为books集合添加元素的代码
 7         Collection books = new HashSet();
 8         books.add(new String("轻量级Java EE企业应用实战"));
 9         books.add(new String("疯狂Java讲义"));
10         books.add(new String("疯狂IOS讲义"));
11         books.add(new String("疯狂Ajax讲义"));
12         books.add(new String("疯狂Android讲义"));
13         
14         //使用Lambda表达式(目标类型是Predicate)过滤集合
15         //所有长度小于10的字符串元素都会被删除
16         books.removeIf(ele -> ((String) ele).length() < 10);
17         System.out.println(books);
18     }
19 }
View Code

技术分享

    对上面的集合提出三个统计要求:

      1.统计书名中出现“疯狂”字符串的图书数量

      2.统计书命中出现“Java”字符串的图书数量

      3.统计书名长度大于10的图书数量

技术分享
 1 import java.util.Collection;
 2 import java.util.HashSet;
 3 import java.util.function.Predicate;
 4 
 5 public class PredicateTest{
 6     public static void main(String[] args){
 7         //创建books集合、为books集合添加元素的代码
 8         Collection books = new HashSet();
 9         books.add(new String("轻量级Java EE企业应用实战"));
10         books.add(new String("疯狂Java讲义"));
11         books.add(new String("疯狂IOS讲义"));
12         books.add(new String("疯狂Ajax讲义"));
13         books.add(new String("疯狂Android讲义"));
14         
15         //统计书名中包含“疯狂”子串的图书数量
16         System.out.println(calAll(books, ele -> ((String) ele).contains("疯狂")));
17         //统计书名中包含“Java”子串的图书数量
18         System.out.println(calAll(books, ele -> ((String) ele).contains("Java")));
19         //统计书名长度大于10的图书数量
20         System.out.println(calAll(books, ele -> ((String) ele).length() > 10));
21     }
22     
23     public static int calAll(Collection books, Predicate p){
24         int total = 0;
25         for(Object obj : books){
26             //使用Predicate的test()方法判断该对象是否满足指定的条件
27             if(p.test(obj)){
28                 total ++;
29             }
30         }
31         return total;
32     }
33 }
View Code

技术分享

      若采用传统方式完成上面3个需求,需要执行3次循环,但是采用Predicate只需要一个方法即可。

  使用Java8新增的Stream操作集合:

    Java8新增了Stream、IntStream、LongStream、DoubleStream等流式API,这些API代表多个支持串行和并行聚集操作的元素。

    上面四个接口中Stream是通用的流接口,IntStream、LongStream、DoubleStream则代表元素类型为int、long、double的流。

    Java8还为上面每个流式API提供了对应的Builder,如Stream.Builder、IntStream.Builder、LongStream.Builder、DoubleStream.Builder

    独立使用Stream的步骤如下:

    1.使用Stream或XxxStream的builder()类方法创建该Stream对应的Builder

    2.重复调用Builder的add()方法向该流中添加多个元素

    3.调用Builder的build()方法获取对应的Stream

    4.调用Stream的聚集方法

技术分享
 1 import java.util.stream.IntStream;
 2 
 3 public class IntStreamTest{
 4     public static void main(String[] args){
 5         IntStream is = IntStream.builder()
 6             .add(20)
 7             .add(13)
 8             .add(-2)
 9             .add(18)
10             .build();
11         //下面调用聚集方法的代码每次只能执行一行
12         System.out.println("is所有元素的最大值:" + is.max().getAsInt());
13         System.out.println("is所有元素的最小值:" + is.min().getAsInt());
14         System.out.println("is所有元素的总和:" + is.sum());
15         System.out.println("is所有元素的总数:" + is.count());
16         System.out.println("is所有元素的平均值:" + is.average());
17         System.out.println("is所有元素的平方是否都大于10:" + is.allMatch(ele -> ele * ele > 20));
18         System.out.println("is是否包含任何元素的平方大于20:" + is.anyMatch(ele -> ele * ele > 20));
19         //将is映射成一个新Stream,新Stream的每个元素时原Stream元素的2倍 + 1
20         IntStream newIs = is.map(ele -> ele * 2 + 1);
21         //使用方法引用的方式来遍历集合元素
22         newIs.forEach(System.out::println);//输出41 27 -3 37
23     }
24 }
View Code

 技术分享

    上面System.out.println()方法,每次只能执行一行,其余的要注释掉,这就是报错的原因。

技术分享
 1 import java.util.stream.IntStream;
 2 
 3 public class IntStreamTest{
 4     public static void main(String[] args){
 5         IntStream is = IntStream.builder()
 6             .add(20)
 7             .add(13)
 8             .add(-2)
 9             .add(18)
10             .build();
11         //下面调用聚集方法的代码每次只能执行一行
12         //System.out.println("is所有元素的最大值:" + is.max().getAsInt());
13         //System.out.println("is所有元素的最小值:" + is.min().getAsInt());
14         //System.out.println("is所有元素的总和:" + is.sum());
15         //System.out.println("is所有元素的总数:" + is.count());
16         System.out.println("is所有元素的平均值:" + is.average());
17         //System.out.println("is所有元素的平方是否都大于10:" + is.allMatch(ele -> ele * ele > 20));
18         //System.out.println("is是否包含任何元素的平方大于20:" + is.anyMatch(ele -> ele * ele > 20));
19         //将is映射成一个新Stream,新Stream的每个元素时原Stream元素的2倍 + 1
20         //IntStream newIs = is.map(ele -> ele * 2 + 1);
21         //使用方法引用的方式来遍历集合元素
22         //newIs.forEach(System.out::println);//输出41 27 -3 37
23     }
24 }
View Code

技术分享

    注释掉所有聚集操作,只保留一条后,就不会报错了。

    Stream提供了大量的方法进行聚集操作,这些方法有“中间的(intermediate)”,也有“末端的(terminal)”。

      中间方法:中间操作允许流保持打开状态,并允许直接调用后续方法。上面map()方法就是中间方法。中间方法的返回值是另外一个流

      末端方法:末端方法是对流的最终操作。当对某个Stream执行末端方法后,该流将会被“消耗”且不再可用。

      除此之外,流的方法还有如下两个特征:

        1.有状态的方法:这种方法会给流增加一些新的属性,比如元素的唯一性、元素的最大数量、保证元素以排序的方式被处理。有状态的方法往往需要更大的性能

         开销

         2.短路方法:短路方法可以尽早结束对流的操作,不必检查所有的元素。

      介绍一些Stream常用的中间方法:

        1.filter(Predicate predicate):过滤Stream中所有不符合predicate的元素

        2.mapToXxx(ToXxxFunction mapper):使用ToXxxFunction对流中的元素执行一对一的转换,该方法返回的新流中包含了ToXxxFunction转换生成的所有元素

        3.peek(Consumer action):依次对每个元素执行一些操作,该方法返回的流与原有流包含相同的元素。该方法主要用于调试。

        4.distinct():该方法用于排序流中所有重复的元素(判断元素重复的标准是使用equals()比较返回true)。这是一个有状态的方法。

        5.sorted():该方法用于保证流中的元素在后续访问中处于有序状态。这是一个有状态的方法。

        6.limit(long maxSize):该方法用于保证对该流的后续访问中最大允许访问的元素个数。这是一个有状态、短路方法。

      介绍一些Stream常用的末端方法:

        1.forEach(Consumer action):遍历流中所有元素,对每个元素执行action

        2.toArray():将流中所有元素转换为一个数组

        3.reduce():该方法有三个重载的版本,都用于通过某种操作来合并流中的元素

        4.min():返回流中所有元素的最小值

        5.max():返回流中所有元素的最大值

        6.count():返回流中所有元素的数量

        7.anyMatch(Predicate predicate):判断流中是否至少包含一个元素符合Predicate条件

        8.AllMatch(Predicate predicate):判断流中是否每个元素都符合Predicate条件

        9.noneMatch(Predicate predicate):判断流中是否所有元素都不符合Predicate条件

        10.findFirst():返回流中的第一个元素

        11.findAny():返回流中任意一个元素

    java8允许使用流式API来操作集合,Collection接口提供了一个stream()默认方法,该默认方法可返回该集合对应的流,接下来即可通过流式API来操作集合元素。

技术分享
 1 import java.util.Collection;
 2 import java.util.HashSet;
 3 import java.util.stream.Stream;
 4 
 5 public class CollectionStream{
 6     public static void main(String[] args){
 7         //创建books集合、为books集合添加元素的代码
 8         Collection books = new HashSet();
 9         books.add(new String("轻量级Java EE企业应用实战"));
10         books.add(new String("疯狂Java讲义"));
11         books.add(new String("疯狂IOS讲义"));
12         books.add(new String("疯狂Ajax讲义"));
13         books.add(new String("疯狂Android讲义"));
14         //统计书名中包含“疯狂”子串的图书数量
15         System.out.println(books.stream().filter(ele -> ((String) ele).contains("疯狂")).count());
16         //统计书名中包含“Java”子串的图书数量
17         System.out.println(books.stream().filter(ele -> ((String) ele).contains("Java")).count());
18         //统计书名长度大于10的图书数量
19         System.out.println(books.stream().filter(ele -> ((String) ele).length() > 10).count());
20         //先调用Collection对象的stream()方法将集合转换为Stream
21         //再调用Stream的mapToInt()方法获取原有的Stream对应的IntStream
22         books.stream().mapToInt(ele -> ((String) ele).length()).forEach(System.out::println);//输出8 11 16 7 8
23     }
24 }
View Code

技术分享

  Set集合:

    Set集合与Collection基本相同,没有提供任何额外的方法。实际上Set就是Collection,只是行为略有不同(Set不允许包含重复元素)。

    Set集合不允许包含相同的元素,若试图把两个相同元素加入同一个Set集合中,则添加操作失败,add()方法返回false,且新元素不会被加入。

    上面介绍的是Set集合通用的知识,因此完全适合HashSet、TreeSet、EnumSet三个实现类。

  HashSet类:

    HashSet按Hash算法来存储集合中的元素,因此具有良好的存取和查找性能

    HashSet有如下特点:

      1.不能保证元素的排列顺序,顺序可能与添加顺序不同,顺序也有可能发生变化

      2.HashSet不是同步的,若多个线程访问一个HashSet,假设有两个或两个以上的线程同时修改HashSet集合时,则必须通过代码来保证其同步

      3.集合元素值可以是null

    当向HashSet集合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据该hashCode值决定该对象在HashSet中的存储位置

     。若有两个元素通过equals()方法比较返回true,但它们的hashCode()方法返回值不相等,HashSet将会把它们存储在不同的位置,依然可以添加成功。

技术分享
 1 import java.util.HashSet;
 2 
 3 //类A的equals()方法总是返回true,但没有重写hashCode()方法
 4 class A {
 5     public boolean equals(Object obj){
 6         return true;
 7     }
 8 }
 9 
10 //类B的hashCode()方法总是返回1,但没有重写其equals()方法
11 class B{
12     public int hashCode(){
13         return 1;
14     }
15 }
16 
17 //类C的hashCode()方法总是返回2,且重写其equals()方法总是返回true
18 class C{
19     public int hashCode(){
20         return 2;
21     }
22     
23     public boolean equals(Object obj){
24         return true;
25     }
26 }
27 
28 public class HashSetTest{
29     public static void main(String[] args){
30         HashSet books = new HashSet();
31         //分别向books集合中添加两个A对象、两个B对象、两个C对象
32         books.add(new A());
33         books.add(new A());
34         books.add(new B());
35         books.add(new B());
36         books.add(new C());
37         books.add(new C());
38         System.out.println(books);
39     }
40 }
View Code

技术分享

    根据上面结果可知,HashSet集合判断两个元素相等的标准是两个对象通过equals()方法比较相等,并且两个对象的hashCode()方法返回值也相等。

    这里有个注意点:当把一个对象放入HashSet中时,若需要重写该对象对应类的equals()方法,则也应该重写其hashCode()方法。规则是:若两个对象通过equals()方法

     比较返回true,这两个对象的hashCode值也应该相等。

    给出重写hashCode()方法的基本规则:

      1.在程序运行过程中,同一个对象多次调用hashCode()方法应该返回相同的值。

      2.当两个对象通过equals()方法比较返回true时,这两个对象的hashCode()方法应该返回相同的值

      3.对象中用作equals()方法比较标准 的实例变量,都应该用于计算hashCode值

    下面给出hashCode()方法的一般步骤:

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

      2.用第一步计算出来的多个hashCode值组合计算出一个hashCode值返回。

       return f1.hashCode() + (int) f2;

       为了避免直接相加产生偶然相等(两个对象的f1、f2实例变量并不相等,但它们的hashCode的和恰好相等),可以通过为各实例变量的hashCode值乘以任意一个

       质数后再相加

       return f1.hashCode() * 19 + (int) f2 * 31;

       若向HashSet中添加一个可变对象后,后面程序修改了该可变对象的实例变量,则可能导致它与集合中的其他元素相同(即两个对象通过equals()方法比较返

       回true,两个对象的hashCode值也相等),这就可能导致HashSet中包含两个相同的对象。

技术分享
 1 import java.util.HashSet;
 2 import java.util.Iterator;
 3 
 4 class R{
 5     int count;
 6     public R(int count){
 7         this.count = count;
 8     }
 9     public String toString(){
10         return "R[count:" + count + "]";
11     }
12     public boolean equals(Object obj){
13         if(this == obj){
14             return true;
15         }
16         if(obj != null && obj.getClass() == R.class){
17             R r = (R) obj;
18             return this.count == r.count;
19         }
20         return false;
21     }
22     public int hashCode(){
23         return this.count;
24     }
25 }
26 
27 public class HashSetTest2{
28     public static void main(String[] args){
29         HashSet hs = new HashSet();
30         hs.add(new R(5));
31         hs.add(new R(-3));
32         hs.add(new R(9));
33         hs.add(new R(-2));
34         
35         //打印HashSet集合,集合元素没有重复
36         System.out.println(hs);
37         //取出第一个元素
38         Iterator it = hs.iterator();
39         R first = (R) it.next();
40         //为第一个元素的count实例变量赋值
41         first.count = -3;
42         //再次输出HashSet集合,集合元素有重复元素
43         System.out.println(hs);
44         //删除count为-3的R对象
45         hs.remove(new R(-3));
46         //可以看到被删除了一个R元素
47         System.out.println(hs);
48         System.out.println("hs是否包含count为-3的R对象?" + hs.contains(new R(-3)));//输出false
49         System.out.println("hs是否包含count为-2的R对象?" + hs.contains(new R(-2)));
50     }
51 }
View Code

技术分享

       当向HashSet中添加可变对象时,必须十分小心。如果修改HashSet集合中的对象,有可能导致该对象与集合中的其他对象相等,从而导致HashSet无法正确访问该

       对象。

  LinkedHashSet类:

    HashSet类有一个子类:LinkedHashSet。,LinkedHashSet集合也是根据元素的hashCode值来决定元素的存储位置,但它同时使用链表维护元素的次序,这样使元素是

     以插入的顺序保存的,即当遍历LinkedHashSet集合中的元素时,LinkedHashSet将会按元素的添加顺序来访问集合中的元素。

技术分享
 1 import java.util.LinkedHashSet;
 2 
 3 public class LinkedHashSetTest{
 4     public static void main(String[] args){
 5         LinkedHashSet books = new LinkedHashSet();
 6         books.add("疯狂Java讲义");
 7         books.add("轻量级Java EE企业应用实战");
 8         System.out.println(books);
 9         //删除 疯狂Java讲义
10         books.remove("疯狂Java讲义");
11         //重新添加 疯狂Java讲义
12         books.add("疯狂Java讲义");
13         System.out.println(books);
14     }
15 }
View Code

技术分享

    从结果可看出,输出LinkedHashSet集合时,元素的顺序总是和添加顺序一致。

  TreeSet类:

    TreeSet是SortedSet接口的实现类,TreeSet可以确保集合元素处于排序状态。与HashSet集合相比,TreeSet提供了如下几个额外的方法:

      1.Comparator comparator():若TreeSet采用了定制排序,则该方法返回定制排序所使用的Comparator;若TreeSet采用了自然排序,则返回null。

      2.Object first():返回集合中的第一个元素

      3.Object last():返回集合中的最后一个元素

      4.Object lower(Object e):返回集合中位于指定元素之前的元素(即小于指定元素的最大元素,参考元素不需要是TreeSet集合里的元素)。

      5.Object higher(Object e):返回集合中位于指定元素之后的元素(即大于指定元素的最小元素,参考元素不需要是TreeSet集合里的元素)。

      6.SortedSet subSet(Object fromElement, Object toElement):返回此Set的子集合,范围从fromElement(包含)到toElement(不包含)

      7.SortedSet headSet(Object toElement):返回此Set的子集,由小于toElement的元素组成

      8.SortedSet tailSet(Object fromElement):返回此Set的子集,由大于或等于fromElement的元素组成

技术分享
 1 import java.util.TreeSet;
 2 
 3 public class TreeSetTest{
 4     public static void main(String[] args){
 5         TreeSet nums = new TreeSet();
 6         //向TreeSet中添加四个Integer对象
 7         nums.add(5);
 8         nums.add(2);
 9         nums.add(10);
10         nums.add(-9);
11         //输出集合元素,看到集合元素已经处于排序状态
12         System.out.println(nums);
13         //输出集合的第一个元素
14         System.out.println(nums.first());
15         //输出集合里的最后一个元素
16         System.out.println(nums.last());
17         //返回小于4的子集,不包含4
18         System.out.println(nums.headSet(4));
19         //返回大于5的子集,若Set中包含5,子集中还包含5
20         System.out.println(nums.tailSet(5));
21         //返回大于等于-3、小于4的子集
22         System.out.println(nums.subSet(-3, 4));
23     }
24 }
View Code

技术分享

      从结果可知,TreeSet是根据元素实际值大小来排序的,TreeSet采用红黑树的数据结构来存储集合元素。

      TreeSet支持两种排序方法:自然排序和定制排序

      1.自然排序:

        TreeSet会调用集合元素的compareTo(Object obj)方法来比较元素之间的大小关系,然后将集合元素按升序排列,这就是自然排序。

        Java提供了一个Comparable接口,该接口定义了一个compareTo(Object obj)方法。

        Java中一些实现了Comparable接口的类:

        1.BigDecimal、BigInteger以及所有的数值型对应的包装类:按它们对应的数值大小进行比较

        2.Character:按字符的UNICODE值进行比较

        3.Boolean:true对应的包装类实例大于false对应的包装类实例

        4.String:按字符串中字符的UNICODE值进行比较

        5.Date、Time:后面的时间、日期比前面的时间、日期大

      试图把一个对象添加到TreeSet中时,该对象的类必须实现Comparable接口,否则程序会抛出异常:

技术分享
 1 import java.util.TreeSet;
 2 
 3 class Err{}
 4 public class TreeSetErrorTest{
 5     public static void main(String[] args){
 6         TreeSet ts = new TreeSet();
 7         //向TreeSet集合中添加两个Err对象
 8         ts.add(new Err());
 9         ts.add(new Err());
10     }
11 }
View Code

技术分享

      还有一点:大部分类在实现compareTo(Object obj)方法时,都需要将被比较对象obj强制类型转换为相同类型,因为只有相同的两个实例才会比较大小,当试图添加

       一个对象到TreeSet集合中时,TreeSet会调用该对象的compareTo(Object obj)方法与集合中其他元素进行比较,所以向TreeSet集合中添加的应该是同一个类的对象

技术分享
 1 import java.util.TreeSet;
 2 import java.util.Date;
 3 
 4 public class TreeSetErrorTest2{
 5     public static void main(String[] args){
 6         TreeSet ts = new TreeSet();
 7         //向TreeSet集合中添加两个对象
 8         ts.add(new String("疯狂Java讲义"));
 9         ts.add(new Date());
10     }
11 }
View Code

技术分享

      若想TreeSet正常运行,只能添加同一种类型的对象

      当把一个对想添加入TreeSet集合中时,TreeSet会调用该对象的compareTo(Object obj)方法与容器中的其他对象比较大小,然后根据红黑树结构找到它的存储位置

       。若两个对象通过compareTo(Object obj)方法比较后相等,新对象将无法添加到TreeSet集合中。

      对于TreeSet集合而言,判断两个对象是否相等的唯一标准是:两个对象通过compareTo(Object obj)方法比较是否返回0——若通过compareTo(Object obj)方法比较

       返回0,TreeSet会认为它们相等;否则不相等

技术分享
 1 import java.util.TreeSet;
 2 
 3 class Z implements Comparable{
 4     int age;
 5     public Z(int age){
 6         this.age = age;
 7     }
 8     //重写equals()方法,总是返回true
 9     public boolean equals(Object obj){
10         return true;
11     }
12     //重写compareTo(Object obj)方法总是返回1
13     public int compareTo(Object obj){
14         return 1;
15     }
16 }
17 
18 public class TreeSetTest2{
19     public static void main(String[] args){
20         TreeSet set = new TreeSet();
21         Z z1 = new Z(6);
22         set.add(z1);
23         //第二次添加同一个对象,输出true,表明添加成功
24         System.out.println(set.add(z1));
25         //下面输出set集合,将看到有两个元素
26         System.out.println(set);
27         //修改set集合的第一个元素的age变量
28         ((Z) (set.first())).age = 9;
29         //输出set集合的最后一个元素的age变量,将看到也变成了9
30         System.out.println(((Z) (set.last())).age);
31     }
32 }
View Code

技术分享

技术分享
 1 import java.util.TreeSet;
 2 
 3 class R implements Comparable{
 4     int count;
 5     public R(int count){
 6         this.count = count;
 7     }
 8     public String toString(){
 9         return "R[count:" + count + "]";
10     }
11     //重写equals()方法,根据count判断是否相等
12     public boolean equals(Object obj){
13         if(this == obj){
14             return true;
15         }
16         if(obj != null && obj.getClass() == R.class){
17             R r = (R) obj;
18             return r.count == this.count;
19         }
20         return false;
21     }
22     //重写compareTo()方法,根据count来比较大小
23     public int compareTo(Object obj){
24         R r = (R) obj;
25         return count > r.count ? 1 : count < r.count ? -1 : 0;
26     }
27 }
28 
29 public class TreeSetTest3{
30     public static void main(String[] args){
31         TreeSet ts = new TreeSet();
32         ts.add(new R(5));
33         ts.add(new R(-3));
34         ts.add(new R(9));
35         ts.add(new R(-2));
36         //打印TreeSet集合,集合元素是有序排列的
37         System.out.println(ts);
38         //取出第一个元素
39         R first = (R) ts.first();
40         //对第一个元素的count赋值
41         first.count = 20;
42         //取出最后一个元素
43         R last = (R) ts.last();
44         //对最后一个元素的count赋值,与第二个元素的count相同
45         last.count = -2;
46         //再次输出将看到TreeSet里的元素处于无序状态,且有重复元素
47         System.out.println(ts);
48         //删除实例变量被改变的元素,删除失败
49         System.out.println(ts.remove(new R(-2)));
50         System.out.println(ts);
51         //删除实例变量没有被改变的元素,删除成功
52         System.out.println(ts.remove(new R(5)));
53         System.out.println(ts);
54     }
55 }
View Code

技术分享

    从结果看出,一旦改变TreeSet集合里可变元素的实例变量, 当再试图删除该对象时,TreeSet也会删除失败(甚至集合中原有的、实例变量没被修改但与修改后元素相

     等的元素也无法删除)。

    当执行了ts.remove(new R(5)));后,TreeSet会对集合中的元素重新索引(不是重新排序),接下来就可以删除TreeSet中的所有元素了,包括哪些被修改过实例变量的元素

    所以为了程序的健壮性,推荐不要修改放入HashSet和TreeSet集合中元素的关键实例变量。

    2.定制排序:

      若需要实现定制排序,如:降序排列,则可以通过Comparator接口帮助。该接口包含一个int compare(T o1, T o2)方法,该方法用于比较o1和o2大小:o1 > o2方法

       返回正整数;o1 == o2,方法返回0;o1 < o2,方法返回负整数。

      若需要实现定制排序,则需要在创建TreeSet集合对象是,提供一个Comparator对象与该TreeSet集合关联,由该Comparator对象负责集合元素的排序逻辑

       Comparator接口是一个函数式接口,可以使用Lambda表达式来代替Comparator对象:

技术分享
 1 import java.util.TreeSet;
 2 
 3 class M {
 4     int age;
 5     public M(int age){
 6         this.age = age;
 7     }
 8     public String toString(){
 9         return "M[age:" + age + "]";
10     }
11 }
12 
13 public class TreeSetTest4{
14     public static void main(String[] args){
15         TreeSet ts = new TreeSet((o1, o2) -> {
16             M m1 = (M) o1;
17             M m2 = (M) o2;
18             //根据M对象的age属性来决定大小,age越大,M对象反而越小
19             return m1.age > m2.age ? -1 : m1.age < m2.age ? 1 : 0;
20         });
21         ts.add(new M(5));
22         ts.add(new M(-3));
23         ts.add(new M(9));
24         System.out.println(ts);
25     }
26 }
View Code

技术分享

  EnumSet类:

    EnumSet是一个专门为枚举设计的集合类,EnumSet中所有元素都必须是指定枚举类型的枚举值,该枚举类型在创建EnumSet时显式或隐式地指定。EnumSet的集合元素也

     是有序的,EnumSet以枚举值在Enum类中的定义顺序来决定集合元素的顺序

    EnumSet集合不允许假如null元素,若插入null元素,EnumSet将抛出NullPointerException异常。

    EnumSet类没有构造器,通过类方法创建EnumSet对象:

      1.EnumSet allOf(Class elementType):创建一个包含指定枚举类里所有枚举值的EnumSet集合

      2.EnumSet complementOf(EnumSet s):创建一个其元素类型与指定EnumSet里元素类型相同的EnumSet集合,新EnumSet集合包含原EnumSet集合所不包含的、

       此枚举类剩下的枚举值(即新EnumSet集合和原EnumSet集合的集合元素加起来就是该枚举类的所有枚举值)。

      3.EnumSet copyOf(Collection c):使用一个普通集合来创建EnumSet集合

      4.EnumSet copyOf(EnumSet s):创建一个与指定EnumSet具有相同元素类型、相同集合元素的EnumSet集合

      5.EnumSet noneOf(Class elementType):创建一个元素类型为指定枚举类型的空EnumSet

      6.EnumSet of(E first, E... rest):创建一个包含一个或多个枚举值的EnumSet集合,传入的多个枚举值必须属于同一个枚举值

      7.EnumSet range(E from, E to):创建一个包含从from枚举值到to枚举值范围内所有枚举值的EnumSet集合

技术分享
 1 import java.util.EnumSet;
 2 
 3 enum Season{
 4     SPRING, SUMMER, FALL, WINTER
 5 }
 6 
 7 public class EnumSetTest{
 8     public static void main(String[] args){
 9         //创建一个EnumSet集合,集合元素就是Season枚举类的全部枚举值
10         EnumSet es1 = EnumSet.allOf(Season.class);
11         System.out.println(es1);
12         //创建一个EnumSet空集合,指定其集合元素时Season类的枚举值
13         EnumSet es2 = EnumSet.noneOf(Season.class);
14         System.out.println(es2);
15         //手动添加两个元素
16         es2.add(Season.WINTER);
17         es2.add(Season.SPRING);
18         System.out.println(es2);
19         //以指定枚举值创建EnumSet集合
20         EnumSet es3 = EnumSet.of(Season.SUMMER, Season.WINTER);
21         System.out.println(es3);
22         EnumSet es4 = EnumSet.range(Season.SUMMER, Season.WINTER);
23         System.out.println(es4);
24         //新创建的EnumSet集合元素和es4集合元素有相同的类型
25         //es5集合元素 + es4集合元素 = Season枚举类的全部枚举值
26         EnumSet es5 = EnumSet.complementOf(es4);
27         System.out.println(es5);
28     }
29 }
View Code

技术分享

技术分享
 1 import java.util.EnumSet;
 2 import java.util.Collection;
 3 import java.util.HashSet;
 4 
 5 enum Season{
 6     SPRING, SUMMER, FALL, WINTER
 7 }
 8 
 9 public class EnumSetTest2{
10     public static void main(String[] args){
11         Collection c = new HashSet();
12         c.clear();
13         c.add(Season.FALL);
14         c.add(Season.SPRING);
15         //复制Collection集合中的所有元素来创建EnumSet集合
16         EnumSet enumSet = EnumSet.copyOf(c);
17         System.out.println(enumSet);
18         c.add("疯狂Java讲义");
19         c.add("轻量级Java EE企业应用实战");
20         //下面代码异常,因为c集合中元素不是全部都为枚举值
21         enumSet = EnumSet.copyOf(c);
22     }
23 }
View Code

技术分享

从结果中看出,当复制Collection集合中的所有元素来创建新的EnumSet集合时,要求Collection集合中所有元素必须是同一个枚举类的枚举值。

  List集合:

    List集合代表一个元素有序,可重复的集合。集合中的每个元素都有其对应的顺序索引。

    Java8改进的List接口和ListIterator接口

    List作为Collection接口的子接口,可使用Collection接口里的全部方法。且由于List是有序集合,因此List集合里增加了一些根据索引来操作集合的方法

      1.void add(int index, Object element):将元素element插入到List集合的index处

      2.boolean addAll(int index, Collection c):将集合c所包含的所有元素都插入到List集合的index处

      3.Object get(int index):返回集合index索引处的元素

      4.int lastIndexOf(Object o):返回对象o在List集合中最后一次出现的位置索引

      5.Object remove(int index):删除并返回index索引处的元素

      6.Object set(int index, Object element):将index索引处的元素替换成element对象,返回被替换的旧元素

      7.List subList(int fromIndex, int toIndex):返回从索引fromIndex(包含)到索引toIndex(不包含)处所有集合组成子集合

    Java8为List接口还提供了添加了如下两个默认方法:

      1.void replaceAll(UnaryOperator operator):根据operator指定的计算规则重新设置List集合的所有元素

      2.void sort(Comparator c):根据Comparator参数对List集合的元素排序

技术分享
 1 import java.util.ArrayList;
 2 import java.util.List;
 3 
 4 public class ListTest{
 5     public static void main(String[] args){
 6         List books = new ArrayList();
 7         //向books集合中添加三个元素
 8         books.add(new String("轻量级Java EE企业应用实战"));
 9         books.add(new String("疯狂Java讲义"));
10         books.add(new String("疯狂Android讲义"));
11         System.out.println(books);
12         //将新字符串对象插入在第二个位置
13         books.add(1, new String("疯狂Ajax讲义"));
14         for(int i = 0; i < books.size(); i++){
15             System.out.println(books.get(i));
16         }
17         //删除第三个元素
18         books.remove(2);
19         System.out.println(books);
20         //判断指定元素在List集合中的位置:输出1,表明在第二位
21         System.out.println(books.indexOf(new String("疯狂Ajax讲义")));
22         //将第二个元素替换成新的字符串对象
23         books.set(1, new String("疯狂Java讲义"));
24         System.out.println(books);
25         //将books集合的第二个元素(包括)
26         //到第三个元素(不包括)截取成子集合
27         System.out.println(books.subList(1, 2));
28     }
29 }
View Code

技术分享

    List判断两个对象相等只要通过equals()方法比较返回true即可:

技术分享
 1 import java.util.ArrayList;
 2 import java.util.List;
 3 
 4 class A{
 5     public boolean equals(Object obj){
 6         return true;
 7     }
 8 }
 9 
10 public class ListTest2{
11     public static void main(String[] args){
12         List books = new ArrayList();
13         books.add(new String("轻量级Java EE企业应用实战"));
14         books.add(new String("疯狂Java讲义"));
15         books.add(new String("疯狂Android讲义"));
16         System.out.println(books);
17         //删除集合中的A对象,将导致第一个元素被删除
18         books.remove(new A());
19         System.out.println(books);
20         //删除集合中的A对象,再次删除集合中的第一个元素
21         books.remove(new A());
22         System.out.println(books);
23     }
24 }
View Code

技术分享

  从上面结果可看出,程序试图删除一个A对象,List将会调用该A对象的equals方法依次与集合元素进行比较,若该equals()方法以某个集合元素作为参数时返回true,List将会

   删除该元素——A类重写了equals()方法,总是返回true,所以每次从List集合中删除A对象时,总是删除List集合中的第一个元素。

  Java8为List集合增加了sort()和replaceAll()两个常用的默认方法,其中sort()方法需要一个Comparator对象来控制元素排序,而replaceAll()方法则需要一个UnaryOperator来

   替换所有集合元素。Comparator和UnaryOperator都是函数式接口,所以可以用Lambda表达式作为参数:

技术分享
 1 import java.util.ArrayList;
 2 import java.util.List;
 3 
 4 public class ListTest3{
 5     public static void main(String[] args){
 6         List books = new ArrayList();
 7         books.add(new String("轻量级Java EE企业应用实战"));
 8         books.add(new String("疯狂Java讲义"));
 9         books.add(new String("疯狂Android讲义"));
10         books.add(new String("疯狂iOS讲义"));
11         //使用目标类型为Comparator的Lambda表达式对List集合排序
12         books.sort((o1, o2) -> ((String) o1).length() - ((String) o2).length());
13         System.out.println(books); 
14         //使用目标类型为UnaryOperator的Lambda表达式来替换集合中所有元素
15         //该Lambda表达式控制使用每个字符串的长度作为新的集合元素
16         books.replaceAll(ele -> ((String) ele).length());
17         System.out.println(books);
18     }
19 }
View Code

技术分享

  与Set只提供一个Iterator()方法不同,List还额外提供了一个listIterator()方法,该方法返回一个ListIterator对象,ListIterator接口继承了Iterator接口,提供了专门操作List的方法

  ListIterator接口在Iterator接口的基础上增加了如下方法:

    1.boolean hasPrevious():返回该迭代器关联的集合是否还有上一个元素

    2.Object previous():返回该迭代器的上一个元素

    3.void add(Object o):在指定位置插入一个元素

技术分享
 1 import java.util.List;
 2 import java.util.ArrayList;
 3 import java.util.ListIterator;
 4 
 5 public class ListIteratorTest{
 6     public static void main(String[] args){
 7         String[] books = {
 8             "疯狂Java讲义", "疯狂iOS讲义",
 9             "轻量级Java EE企业应用实战"
10         };
11         List bookList = new ArrayList();
12         for(int i = 0; i < books.length; i++){
13             bookList.add(books[i]);
14         }
15         ListIterator lit = bookList.listIterator();
16         while(lit.hasNext()){
17             System.out.println(lit.next());
18             lit.add("------分隔符------");
19         }
20         System.out.println("======下面开始反向迭代======");
21         while(lit.hasPrevious()){
22             System.out.println(lit.previous());
23         }
24     }
25 }
View Code

技术分享

  ArrayList和Vector实现类:

    ArrayList和Vector是List类的经典实现。所以ArrayList和Vector类封装了一个动态的、允许再分配的Object[]数组。

    ArrayList和Vector对象使用initialCapacity参数设置该数组的长度,当向ArrayList和Vector中添加的元素数量超出了该数组的长度,它们的initialCapacity会自动增加。

    若向ArrayList和Vector中添加大量元素时,可以使用ensureCapacity(int minCapacity)方法一次性的增加initialCapacity。这样可以减少重分配次数,提高性能。

    若开始知道ArrayList和Vector集合需要保存多少元素,则可以在创建它们时就指定initialCapacity大小,若创建空的ArrayList和Vector集合不指定initialCapacity,则

     Object[]数组默认长度为0。

    ArrayList和Vector还提供了两个如下方法来重新分配Object[]数组:

      1.void ensureCapacity(int minCapacity):将ArrayList或Vector集合的Object[]数组长度增加大于或等于minCapacity值。

      2.void trimToSize():调整ArrayList或Vector集合的Object[]数组长度为当前元素的个数。调用该方法可减少ArrayList或Vector集合对象占用的存储空间

  Vector提供了一个Stack子类,模拟栈的数据结构,提供了下面几个方法:

    1.Object peek():返回”栈“的第一个元素,但并不将该元素”pop“出栈

    2.Object pop():返回“栈”的第一个元素,并将该元素“pop”出栈

    3.void push(Object item):将第一个元素“push”进栈,最后一个进“栈”的元素总是位于栈顶。

  尽量少用Vector类。

  固定长度的List:

    操作数组的工具类:Arrays,该工具类提供了asList(Object... a)方法,该方法可以把一个数组或指定个数的对象转换成一个List集合,这个List集合既不是ArrayList实现类

     的实例,也不是Vector实现类的实例,而是Arrays的内部类ArrayList的实例

    Arrays.ArrayList是一个固定长度的List集合,程序只能遍历访问该集合里的元素,不可增加、删除该集合里的元素:

技术分享
 1 import java.util.List;
 2 import java.util.Arrays;
 3 
 4 public class FixedSizeList{
 5     public static void main(String[] args){
 6         List fixedList = Arrays.asList("疯狂Java讲义", "轻量级Java EE企业应用实战");
 7         //获取fixedList的实现类,将输出Arrays$ArrayList
 8         System.out.println(fixedList.getClass());
 9         //使用方法引用遍历集合元素
10         fixedList.forEach(System.out::println);
11         //试图增加、删除集合元素都会引发UnsupportedOperationException异常
12         fixedList.add("疯狂Android讲义");
13         fixedList.remove("疯狂Java讲义");
14     }
15 }
View Code

技术分享

  Queue集合:

    用于模拟队列,先进先出(FIFO),方法如下:

      1.void add(Object e):将指定元素加入此队列的尾部

      2.Object element():获取队列头部的元素,但是不删除该元素

      3.boolean offer(Object e):将指定元素加入此队列的尾部。当使用有容量限制的队列时,此方法通常比add(Object e)方法更好

      4.Object peek():获取队列头部的元素,但是不删除该元素。若此队列为空,则返回null

      5.Object poll():获取队列头部的元素,并删除该元素。若此队列为空,则返回null

      6.Object remove():获取队列头部的元素,并删除该元素

  PriorityQueue实现类:

    是比较标准的的队列实现类。因为PriorityQueue保存队列元素的顺序并不是按加入队列的顺序,而是按队列元素的大小进行重新排序。所以PriorityQueue类不是先进先出

技术分享
 1 import java.util.PriorityQueue;
 2 
 3 public class PriorityQueueTest{
 4     public static void main(String[] args){
 5         PriorityQueue pq = new PriorityQueue();
 6         //下面代码依次向pq中加入四个元素
 7         pq.offer(6);
 8         pq.offer(-3);
 9         pq.offer(20);
10         pq.offer(18);
11         //输出队列,并不是按加入顺序排列
12         System.out.println(pq);
13         //访问队列的第一个元素,其实就是队列中最小的元素
14         System.out.println(pq.poll());
15         System.out.println(pq.poll());
16         System.out.println(pq.poll());
17         System.out.println(pq.poll());
18     }
19 }
View Code

技术分享

虽然显示输出结果不是从小到大排序,但是一个一个获取是按从大到小的顺序读取。

    PriorityQueue不允许插入null元素,它需要对队列元素进行排序,PriorityQueue的元素有两种排序方式

      1.自然排序:采用自然排序的PriorityQueue集合中的元素必须实现Comparable接口,且应该是同一个类的多个实例,否则导致ClassCaseException异常。

      2.定制排序:创建PriorityQueue队列时,传入一个Comparator对象。采用定制排序不要求队列中元素实现Comparable接口

  Deque接口与ArrayDeque实现类:

    Deque接口是Queue接口的子接口,代表一个双端队列,提供了一些方法,这些方法允许从两端来操作队列的元素:

    1.void addFirst(Object e):将指定元素插入该双端队列的开头

    2.void addList(Object e):将指定元素插入该双端队列的某位

    3.Iterator descendingIterator():返回该双端队列对应的迭代器,该迭代器将以逆向顺序来迭代队列中的元素

    4.Object getFirst():获取但不删除双端队列的第一个元素

    5.Object getLast()::获取但不删除双端队列的最后一个元素

    6.boolean offerFirst(Object e):将指定元素插入该双端队列的开头

    7.boolean offerLast(Object e):将指定元素插入该双端队列的末尾

    8.Object peekFirst():获取但不删除双端队列的第一个元素;若此双端队列为空,则返回null

    9.Object peekLast():获取但不删除双端队列的最后一个元素;若此双端队列为空,则返回null

    10.Object pollFirst():获取并删除双端队列的第一个元素;若此双端队列为空,则返回null

    11.Object pollLast():获取并删除双端队列的最后一个元素;若此双端队列为空,则返回null

    12.Object pop():pop出该双端队列所表示的栈的栈顶元素,相当于removeFirst()

    13.void push(Object e):讲一个元素push进该双端队列所表示的栈的栈顶。相当于addFirst(e)

    14.Object removeFirst():获取并删除该双端队列的第一个元素

    15.Object removeFirstOccurrence(Object e):删除该双端队列的第一次出现的元素o

    16.removeLast():获取并删除该双端队列的最后一个元素

    17.removeLastOccurrence(Object e):删除该双端队列的最后一次出现的元素o

    Deque不仅可以当成双端队列使用,且可被当作栈来使用,因为该类还包含了pop,push方法

    创建Deque时同样可指定一个numElements参数,该参数用于指定Object[]数组的长度;若不指定numElements参数,Deque底层数组的长度为16

    下面程序把ArrayDuque当成栈使用:

技术分享
 1 import java.util.ArrayDeque;
 2 
 3 public class ArrayDequeStack{
 4     public static void main(String[] args){
 5         ArrayDeque stack = new ArrayDeque();
 6         //依次将三个元素push入栈
 7         stack.push("疯狂Java讲义");
 8         stack.push("轻量级Java EE企业应用实战");
 9         stack.push("疯狂Android讲义");
10         System.out.println(stack);
11         //访问第一个元素,但并不将其出栈
12         System.out.println(stack.peek());
13         System.out.println(stack);
14         //pop出第一个元素
15         System.out.println(stack.pop());
16         System.out.println(stack);
17     }
18 }
View Code

技术分享

    因此当程序中需要使用栈,这种数据结构时,推荐使用ArrayDeque,尽量避免使用Stack

    ArrayDeque也可用作队列:

技术分享
 1 import java.util.ArrayDeque;
 2 
 3 public class ArrayDequeQueue{
 4     public static void main(String[] args){
 5         ArrayDeque queue = new ArrayDeque();
 6         //依次将三个元素加入队列
 7         queue.offer("疯狂Java讲义");
 8         queue.offer("轻量级Java EE企业应用实战");
 9         queue.offer("疯狂Android讲义");
10         System.out.println(queue);
11         //访问第一个元素,但并不将其出栈
12         System.out.println(queue.peek());
13         System.out.println(queue);
14         //poll出第一个元素
15         System.out.println(queue.poll());
16         System.out.println(queue);
17     }
18 }
View Code

技术分享

  LinkedList实现类:

    LinkedList类是List接口的实现类——它是一个List集合,可以根据索引来随机访问集合中的元素。

    LinkedList还实现了Deque接口,也可用于双端队列使用,因此可以当成队列和栈使用:

技术分享
 1 import java.util.LinkedList;
 2 
 3 public class LinkedListTest{
 4     public static void main(String[] args){
 5         LinkedList books = new LinkedList();
 6         //将字符串元素加入队列的尾部
 7         books.offer("疯狂Java讲义");
 8         //将一个字符串元素加入栈的顶部
 9         books.push("轻量级Java EE企业应用实战");
10         //将字符串元素添加到队列的头部(相当于栈的顶部)
11         books.offerFirst("疯狂Android讲义");
12         //以List方式(按索引访问的方式)来遍历集合元素
13         for(int i = 0; i < books.size(); i++){
14             System.out.println("遍历中:" + books.get(i));
15         }
16         //访问并不删除栈顶的元素
17         System.out.println(books.peekFirst());
18         //访问并不删除队列的最后一个元素
19         System.out.println(books.peekLast());
20         //将栈顶的元素弹出栈
21         System.out.println(books.pop());
22         //下面输出将看到队列中第一个元素被删除
23         System.out.println(books);
24         //访问并删除队列的最后一个元素
25         System.out.println(books.pollLast());
26         //下面输出:[轻量级Java EE企业应用实战]
27         System.out.println(books);
28     }
29 }
View Code

技术分享

    LinkedList与ArrayList、ArrayDeque的实现机制完全不同,ArrayList、ArrayDeque内部以数组的形式来保存集合中的元素,LinkedList内部以链表的形式保存集合中的元素

     ,因此LinkedList随机访问集合元素时性能较差,但在插入、删除元素时性能比较出色。

  各种线性表的性能分析:

    ArrayList、LinkedList是线性表的两种典型实现:基于数组的线性表和基于链的线性表。Queue代表了队列,Deque代表了双端队列(既可以作为队列,又可以作为栈)

  Java8增强的Map集合:

    Map用于保存具有映射关系的数据:key和value,key不允许重复。key和value存在单向的一对一关系,即通过指定的key可以找到唯一的、确定的value。

    Map接口下定义的常用的方法:

      1.void clear():

      2.boolean containsKey(Object key):

      3.boolean containsValue(Object value):

      4.Set entrySet():

      5.Object get(Object key):

      6.boolean isEmpty():

      7.Set keySet():

      8.Object put(Object key, Object value):

      9.void putAll(Map m):

      10.Object remove(Object key):

      11.boolean remove(Object key, Object value):

      12.int size():

      13.Collection values():

      14.Object getKey():

      15.Object getValue():

      16.Object setValue(V value):

技术分享
 1 import java.util.Map;
 2 import java.util.HashMap;
 3 
 4 public class MapTest{
 5     public static void main(String[] args){
 6         Map map = new HashMap();
 7         //成对放入多个key-value对
 8         map.put("疯狂Java讲义", 109);
 9         map.put("疯狂iOS讲义", 10);
10         map.put("疯狂Ajax讲义", 79);
11         //多次放入的key-value对中value可以重复
12         map.put("轻量级Java EE企业应用实战", 99);
13         //放入重复的key时,新的value会覆盖原有的value
14         //若新的value覆盖了原有的value,该方法返回被覆盖的value
15         System.out.println(map.put("疯狂iOS讲义", 99));
16         System.out.println(map);
17         //判断是否包含指定的key
18         System.out.println("是否包含值为 疯狂iOS讲义 key:" + map.containsKey("疯狂iOS讲义"));
19         //判断是否包含指定的value
20         System.out.println("是否包含值为 99 value:" + map.containsValue(99));
21         //获取Map集合的所有key组成的集合,通过遍历key来实现遍历所有的key-value对
22         for(Object key : map.keySet()){
23             //map.get(key)方法获取指定的key对应的value
24             System.out.println(key + "-->" + map.get(key));
25         }
26         map.remove("疯狂Ajax讲义");
27         System.out.println(map);
28     }
29 }
View Code

技术分享

   Java8 为Map新增的方法:

    Java8 除了为Mapremove(Object key, Object value)默认方法,还增加了如下方法:

      1.Object compute(Object key, Bifunction remappingFunction):

      2.Object computeIfAbsent(Object key, Function mappingFunction):

      3.Object computeIfPresent(Object key, BiFunction remappingFunction):

      4.void forEach(BiConsumer action):

      5.Object getOrDefault(Object key, V defaultValue):

      6.Object merge(Object key, Object value, BiFunctionremappingFunction):

      7.Object putIfAbsent(Object key, Object value):

      8.Object replace(Object key, Object value):

      9.boolean replace(K Key, V oldValue, V newValue):

      10.replaceAll(BiFunction function):

技术分享
 1 import java.util.Map;
 2 import java.util.HashMap;
 3 
 4 public class MapTest2{
 5     public static void main(String[] args){
 6         Map map = new HashMap();
 7         //成对放入多个key-value对
 8         map.put("疯狂Java讲义", 109);
 9         map.put("疯狂iOS讲义", 99);
10         map.put("疯狂Ajax讲义", 79);
11         //尝试替换Key为“疯狂XML讲义”的value,由于原Map中没有对应的key
12         //因此Map没有改变,不会添加新的key-value对
13         map.replace("疯狂XML讲义", 66);
14         System.out.println(map);
15         //使用原value与传入参数计算出来的结果覆盖原有的value
16         map.merge("疯狂iOS讲义", 10, (oldVal, param) -> (Integer)oldVal + (Integer)param);
17         System.out.println(map);
18         //当key为“Java”对应的value为null(或不存在)时,使用计算的结果作为新value
19         map.computeIfAbsent("Java", (key) -> ((String) key).length());
20         System.out.println(map);
21         //当key为“Java”对应的value存在时,使用计算机的结果作为新value
22         map.computeIfPresent("Java", (key, value) -> (Integer) value * (Integer) value);
23         System.out.println(map);
24     }
25 }
View Code

技术分享

  Java8 改进的HashMap和Hashtable实现类:

    HashMap和Hashtable是Map接口典型的实现类,Hashtable 是一个古老的Map实现类。

    Java8改进了HashMap实现,使用HashMap存在key冲突时依然具有较好的性能

    HashMap和Hashtale存在的区别:

      1.Hashtable是一个线程安全的Map实现,HashMap是线程不安全的实现。若多线程访问同一个Map对象时,使用Hashtable实现类会更好。

      2.Hashtable不允许使用null作为key和value,若试图把null值放入Hashtable中,将会引发NullPointerException异常;但HashMap可以使用null作为key或value

    因为HashMap中的key不能重复,所以HashMap里最多只有一个key-value对的key为null,但可以有无数多个key-value对的value为null:

技术分享
 1 import java.util.HashMap;
 2 
 3 public class NullInHashMap{
 4     public static void main(String[] args){
 5         HashMap hm = new HashMap();
 6         //试图将两个key为null值的key-value对放入HashMap中
 7         hm.put(null, null);
 8         hm.put(null, null);
 9         //将一个value为null值的key-value对放入HashMap中
10         hm.put("a", null);
11         //输出Map对象
12         System.out.println(hm);
13     }
14 }
View Code

技术分享

    HashMap和Hashtable怎样判断两个key相等:判断标准是1.两个key通过equals()方法比较返回true;2.两个key的hashCode值也相等

    HashMap和Hashtable怎样判断两个value相等:判断标准是只要两个对象通过equals()方法比较返回true即可。

技术分享
 1 import java.util.Hashtable;
 2 
 3 class A{
 4     int count;
 5     public A(int count){
 6         this.count = count;
 7     }
 8     //根据count的值来判断两个对象是否相等
 9     public boolean equals(Object obj){
10         if(obj == this){
11             return true;
12         }
13         if(obj != null && obj.getClass() == A.class){
14             A a = (A) obj;
15             return this.count == a.count;
16         }
17         return false;
18     }
19     
20     //根据count来计算hashCode值
21     public int hashCode(){
22         return this.count;
23     }
24 }
25 
26 class B{
27     //重写equals()方法,B对象与任何对象通过equals()方法比较都返回true
28     public boolean equals(Object obj){
29         return true;
30     }
31 }
32 
33 public class HashtableTest{
34     public static void main(String[] args){
35         Hashtable ht = new Hashtable();
36         ht.put(new A(6000), "疯狂Java讲义");
37         ht.put(new A(87563), "轻量级Java EE企业应用实战");
38         ht.put(new A(1232), new B());
39         System.out.println(ht);
40         //只要两个对象通过equals()方法返回true
41         //Hashtable就认为它们是相等的value
42         //由于Hashtable中有一个B对象
43         //它与任何对象通过equals()方法比较都相等,所以下面输出true
44         System.out.println(ht.containsValue("测试字符串"));
45         //只要两个A对象的count相等,它们通过equals()方法比较返回true,且hashCode值相等
46         //Hashtable即认为它们是相同的key,所以下面输出true
47         System.out.println(ht.containsKey(new A(87563)));
48         //下面语句可以删除最后一个key-value对
49         ht.remove(new A(1232));
50         System.out.println(ht);
51     }
52 }
View Code

技术分享

    与HashSet类似,若使用可变对象作为HashMap、Hashtable的key,并且程序修改了作为key的可变对象,那么程序再也无法准确访问到Map中被修改过的key:

技术分享
 1 import java.util.HashMap;
 2 import java.util.Iterator;
 3 
 4 class A{
 5     int count;
 6     public A(int count){
 7         this.count = count;
 8     }
 9     //根据count的值来判断两个对象是否相等
10     public boolean equals(Object obj){
11         if(obj == this){
12             return true;
13         }
14         if(obj != null && obj.getClass() == A.class){
15             A a = (A) obj;
16             return this.count == a.count;
17         }
18         return false;
19     }
20     
21     //根据count来计算hashCode值
22     public int hashCode(){
23         return this.count;
24     }
25 }
26 
27 public class HashMapErrorTest{
28     public static void main(String[] args){
29         HashMap ht = new HashMap();
30         //此处A类与前一个程序的A类是同一个类
31         ht.put(new A(60000), "疯狂Java讲义");
32         ht.put(new A(87563), "轻量级Java EE企业应用实战");
33         //获得Hashtable的key Set集合对应的Iterator迭代器
34         Iterator it =  ht.keySet().iterator();
35         //取出Map中第一个key,并修改它的count值
36         A first = (A) it.next();
37         first.count = 87563;
38         System.out.println(ht);
39         //只能修改没有被修改过的key所有对应的key-value对
40         ht.remove(new A(87563));
41         System.out.println(ht);
42         //无法获取剩下的value,下面两行代码都将输出null
43         System.out.println(ht.get(new A(87563)));
44         System.out.println(ht.get(new A(60000)));
45     }
46 }
View Code

技术分享

  LinkedHashMap实现类:

    HashMap有一个LinkedHashMap子类;LinkedHashMap也是用双向链表来维护key-value对的次序,会按照添加key-value对的顺序保存数据:

技术分享
 1 import java.util.LinkedHashMap;
 2 public class LinkedHashMapTest{
 3     public static void main(String[] args){
 4         LinkedHashMap scores = new LinkedHashMap();
 5         scores.put("语文", 80);
 6         scores.put("英文", 82);
 7         scores.put("数学", 76);
 8         //调用forEach()方法遍历scores里的所有key-value对
 9         scores.forEach((key, value) -> System.out.println(key + "-->" + value));
10     }
11 }
View Code

技术分享

  使用Properties读写属性文件:

    Properties类是Hashtable类的子类,该对象处理属性文件时特别方便。

    Properties类提供了三个方法修改Properties中的key、value值:

      1.String getProperties(String key):获取Properties中指定的属性名对应的属性值

      2.String getProperty(String key, String defaultValue):获取Properties中指定的属性名对应的属性值,若Properties中不存在指定的key时,则该方法指定默认值

      3.Object setProperty(String key, String value):设置属性值

    Properties类还提供了2个读写属性文件的方法:

      1.void load(InputStream inStream):从属性文件(以输入流表示)中加载key-value对,把加载到的key-value对追加到Properties里(Properties是Hashtable的子烈,

       它不保证key-value对之间的次序

      2.void store(OutputStream out, String comments):将Properties中的key-value对输出到指定的属性文件(以输出流表示)中。

技术分享
 1 import java.util.Properties;
 2 import java.io.FileOutputStream;
 3 import java.io.FileInputStream;
 4 
 5 public class PropertiesTest{
 6     public static void main(String[] args) throws Exception{
 7         Properties props = new Properties();
 8         //向Properties中添加属性
 9         props.setProperty("username", "yeeku");
10         props.setProperty("password", "123456");
11         //将Properties中的key-value对保存到a.ini文件中
12         props.store(new FileOutputStream("a.ini"), "comment line");
13         //新建一个Properties对象
14         Properties props2 = new Properties();
15         //向Properties中添加属性
16         props2.setProperty("gender", "male");
17         //将a.ini文件中的key-value对追加到props2中
18         props2.load(new FileInputStream("a.ini"));
19         System.out.println(props2);
20     }
21 }
View Code

技术分享

  SortedMap接口和TreeMap实现类:

    Map接口派生出一个SortedMap子接口,SortedMap接口也有一个TreeMap实现类

    TreeMap有两种排序方式:

      1.自然排序:TreeMap所有key必须实现Comparable接口

      2.定制排序:创建TreeMap时,传入一个Comparator对象,该对象负责对TreeMap中的所有key进行排序。采用定制排序不要求Map的key实现Comparable接口

    TreeMap判断两个key相等的标准:两个key通过compareTo()方法返回0。

    TreeMap提供了一系列根据key顺序访问key-value对的方法:

      1.Map.Entry firstEntry()

      2.Object firstKey()

      3.Map.Entry lastEntry()

      4.Object lastKey()

      5.Map.Entry highterEntry(Object key)

      6.Object higherKey(Object key)

      7.Map.Entry lowerEnty(Object key)

      8.Object lowerKey(Object key)

      9.NavigableMap subMap(Object fromKey, boolean fromInclusive, Object toKey, boolean toInclusive)

      10.SortedMap subMap(Object fromKey, Object toKey)

      11.SortedMap tailMap(Object fromKey)

      12.NavigableMap tailMap(Object fromKey, boolean inclusive)

      13.SortedMap headMap(Object toKey)

      14.NavigableMap headMap(Object toKey, boolean inclusive)

    下面以自然排序为例,介绍TreeMap的基本用法:

技术分享
 1 import java.util.TreeMap;
 2 
 3 class R implements Comparable{
 4     int count;
 5     public R(int count){
 6         this.count = count;
 7     }
 8     public String toString(){
 9         return "R[count:" + count + "]";
10     }
11     //根据count来判断两个对象是否相等
12     public boolean equals(Object obj){
13         if(this == obj){
14             return true;
15         }
16         if(obj != null && obj.getClass() == R.class){
17             R r = (R) obj;
18             return r.count == this.count;
19         }
20         return false;
21     }
22     //根据count属性值来判断两个对象的大小
23     public int compareTo(Object obj){
24         R r = (R) obj;
25         return count > r.count ? 1 : count < r.count ? -1 : 0;
26     }
27 }
28 
29 public class TreeMapTest{
30     public static void main(String[] arga){
31         TreeMap tm = new TreeMap();
32         tm.put(new R(3), "轻量级Java EE企业应用实战");
33         tm.put(new R(-5), "疯狂Java讲义");
34         tm.put(new R(9), "疯狂Android讲义");
35         System.out.println(tm);
36         //返回该TreeMap的第一个Entry对象
37         System.out.println(tm.firstEntry());
38         //返回该TreeMap的最后一个key值
39         System.out.println(tm.lastKey());
40         //返回该TreeMap的比new R(2)大的最小key值
41         System.out.println(tm.higherKey(new R(2)));
42         //返回该TreeMap的比new R(2)小的最大的key-value对
43         System.out.println(tm.lowerEntry(new R(2)));
44         //返回该TreeMap的子TreeMap
45         System.out.println(tm.subMap(new R(-1), new R(4)));
46     }
47 }
View Code

技术分享

   WeakHashMap实现类:

    与HashMap相比,WeakHashMap的key只保留了对实际对象的弱引用,这意味着若WeakHashMap对象的key所引用的对象没有被其他强引用变量所引用,则这些key所

     引用的对象可能被垃圾回收,WeakHashMap也可能自动删除这些key所对应的key-value对。

    WeakHashMap中的每个key对象只持有对实际对象的弱引用,因此,当垃圾回收了该key所对应的实际对象后,WeakHashMap会自动删除该key对应的key-value对:

技术分享
 1 import java.util.WeakHashMap;
 2 
 3 public class WeakHashMapTest{
 4     public static void main(String[] args){
 5         WeakHashMap whm = new WeakHashMap();
 6         //向WeakHashMap中添加三个key-value队
 7         //三个key都是匿名字符串对象(没有其他引用)
 8         whm.put(new String("语文"), new String("良好"));
 9         whm.put(new String("数学"), new String("及格"));
10         whm.put(new String("英文"), new String("中等"));
11         //向WeakHashMap中添加一个key-value对
12         //该key是一个系统缓存的字符串对象
13         whm.put("java", new String("中等"));
14         //输出whm对象,将看到4个key-value对
15         System.out.println(whm);
16         //通知系统立即进行垃圾回收
17         System.gc();
18         System.runFinalization();
19         //在通常情况下,将只看到一个key-value队
20         System.out.println(whm);
21     }
22 }
View Code

技术分享

  IdentityHashMap实现类:

    在IdentityHashMap实现类中,两个key相等的标准是:当且仅当两个key严格相等(key1 == key2)时,IdentityHashMap才认为两个key相等;对于普通HashMap而言,只

     要key1和key2通过equals()方法比较返回true,且它们的hashCode值相等即可。

技术分享
 1 import java.util.IdentityHashMap;
 2 
 3 public class IdentityHashMapTest{
 4     public static void main(String[] args){
 5         IdentityHashMap ihm = new IdentityHashMap();
 6         //下面两行代码将会向IdentityHashMap对象中添加两个key-value对
 7         ihm.put(new String("语文"), 89);
 8         ihm.put(new String("语文"), 78);
 9         //下面两行代码只会向IdentityHashMap对象中添加一个key-value队
10         ihm.put("java", 93);
11         ihm.put("java", 98);
12         System.out.println(ihm);
13     }
14 }
View Code

技术分享

  EnumMap实现类:

    EnumMap是一个与枚举类一起使用的Map实现。

    EnumMap在内部以数组形式保存。

    EnumMap根据key的自然顺序(即枚举值在枚举类中的定义顺序)来维护key-value对的顺序。

    EnumMap不允许使用null作为key,但允许使用null作为value。

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

技术分享
 1 import java.util.EnumMap;
 2 
 3 enum Season{
 4     SPRING, SUMMER, FALL, WINTER
 5 }
 6 
 7 public class EnumMapTest{
 8     public static void main(String[] args){
 9         //创建EnumMap对象,该EnumMap对象的所有key都是Season枚举类的枚举值
10         EnumMap enumMap = new EnumMap(Season.class);
11         enumMap.put(Season.SUMMER, "夏日炎炎");
12         enumMap.put(Season.SPRING, "春暖花开");
13         System.out.println(enumMap);
14     }
15 }
View Code

技术分享

  操作集合的工具类:Collections

    java提供了一个操作Set、List和Map等集合的工具类:Collections,该工具类提供了大量的方法对集合元素进行排序、查询和修改等操作,还提供了将集合对象设置为不

     可变、对集合对象实现同步控制

    排序操作:

      Collections提供了如下常用的类方法用于对List集合进行排序:

        1.void reverse(List list):反转指定List集合中的元素的顺序

        2.void shuffle(List list):对List集合元素进行随机排序(shuffle方法模拟了“洗牌”动作)

        3.void sort(List list):根据元素的自然顺序对指定List集合的元素按升序进行排序

        4.void sort(List list, Comparator c):根据指定Comparator产生的顺序对List集合元素进行排序

        5.void swap(List list, int i, int j):将指定List集合中的i处元素和j处元素进行交换

        6.void rotate(List list, int distance):当distance为整数是,将list集合的后distance个元素“整体”移到前面;当distance为负数时,将list集合的前distance个元素“

         整体“移到后面。该方法不会改变集合长度。

技术分享
 1 import java.util.Collections;
 2 import java.util.ArrayList;
 3 
 4 public class SortTest{
 5     public static void main(String[] args){
 6         ArrayList nums = new ArrayList();
 7         nums.add(2);
 8         nums.add(-5);
 9         nums.add(3);
10         nums.add(0);
11         System.out.println(nums);
12         Collections.reverse(nums);
13         System.out.println(nums);
14         Collections.sort(nums);
15         System.out.println(nums);
16         Collections.shuffle(nums);
17         System.out.println(nums);
18     }
19 }
View Code

技术分享

    下面编写了一个梭哈游戏来演示List集合、Collections工具类的强大功能:

技术分享
  1 import java.util.List;
  2 import java.util.LinkedList;
  3 import java.util.Collections;
  4 
  5 class ArrayUtils
  6 {
  7     /**
  8      * 定义一个工具方法,工具方法从字符串数组中找到对应的字符串元素的位置
  9      * @param array 搜索的数组
 10      * @param target 搜索的字符串
 11      * @return 目标字符串出现的位置,-1表明找不到
 12      */
 13     public static int search(String[] array, String target)
 14     {
 15         for (int i = 0 ; i < array.length ; i++ )
 16         {
 17             if (array[i] != null && array[i].equals(target))
 18             {
 19                 return i;
 20             }
 21         }
 22         return -1;
 23     }
 24 }
 25 
 26 public class ShowHand{
 27     //定义该游戏最多支持多少个玩家
 28     private final int PLAY_NUM = 5;
 29     
 30     //定义扑克牌的所有花色和数值
 31     private String[] types = {"方块", "草花", "红心", "黑桃"};
 32     private String[] values = {"2", "3", "4", "5", "6", "7", "8", "9", "J", "Q", "K", "A"};
 33     
 34     //cards是一局游戏中剩下的扑克牌
 35     private List<String> cards = new LinkedList<String>();
 36     
 37     //定义所有的玩家
 38     private String[] players = new String[PLAY_NUM];
 39     
 40     //定义所有玩家手上的扑克牌
 41     private List<String>[] playersCards = new List[PLAY_NUM];
 42     
 43     /**
 44     *初始化扑克牌,放入52张扑克牌
 45     *并且使用shuffle方法将它们按随机顺序排序
 46     */
 47     public void initCards(){
 48         for(int i = 0; i < types.length; i++){
 49             for(int j = 0; j < values.length; j++){
 50                 cards.add(types[i] + values[j]);
 51             }
 52         }
 53         //随机排列
 54         Collections.shuffle(cards);
 55     }
 56     
 57     /**
 58     *初始化玩家,为每个玩家分派用户
 59     */
 60     public  void initPlayer(String... names){
 61         if(names.length > PLAY_NUM || names.length < 2){
 62             //校验玩家数量,此处使用异常机制更合理
 63             System.out.println("玩家数量不对");
 64             
 65             return ;
 66         }else{
 67             //初始化玩家姓名
 68             for(int i = 0; i < names.length; i++){
 69                 players[i] = names[i];
 70             }
 71         }
 72     }
 73     
 74     /**
 75     *初始化玩家手上的扑克牌,开始游戏时每个玩家手上的扑克牌为空
 76     *程序使用一个长度为0的LinkedList来表示
 77     */
 78     public void initPlayerCards(){
 79         for(int i = 0; i < players.length; i++){
 80             if(players[i] != null && !players[i].equals("")){
 81                 playersCards[i] = new LinkedList<String>();
 82             }
 83         }
 84     }
 85     
 86     /**
 87     *输出全部扑克牌,该方法没有实际作用,仅用作测试
 88     */
 89     public void showAllCards(){
 90         for(String card : cards){
 91             System.out.println(card);
 92         }
 93     }
 94     
 95     /**
 96     *分发扑克牌
 97     *@param first 最先分发给谁
 98     */
 99     public void deliverCard(String first){
100         //调用ArrayUtils工具类的search方法
101         //查询出指定元素在数组中的索引
102         int firstPos = ArrayUtils.search(players, first);
103         //依次给位于该玩家之后的每个玩家分发扑克牌
104         for(int i = firstPos; i < PLAY_NUM; i++){
105             if(players[i] != null){
106                 playersCards[i].add(cards.get(0));
107                 cards.remove(0);
108             }
109         }
110         //依次给位于该指定玩家之前的每个玩家分发扑克牌
111         for(int i = 0; i < firstPos; i++){
112             if(players[i] != null){
113                 playersCards[i].add(cards.get(0));
114                 cards.remove(0);
115             }
116         }
117     }
118     
119     /**
120     *输出玩家手上的扑克牌
121     *实现该方法时,应该控制每个玩家看不到别人的第一张牌,但此处没有增加该功能
122     */
123     public void showPlayerCard(){
124         for(int i = 0; i < PLAY_NUM; i++){
125             //当该玩家不为空时
126             if(players[i] != null){
127                 //输出玩家
128                 System.out.print(players[i] + ": ");
129                 //遍历输出玩家手上的扑克牌
130                 for(String card : playersCards[i]){
131                     System.out.print(card + "\t");
132                 }
133             }    
134             System.out.print("\n");
135         }
136     }
137     
138     public static void main(String[] args){
139         ShowHand sh = new ShowHand();
140         sh.initPlayer("电脑玩家", "孙悟空");
141         sh.initCards();
142         sh.initPlayerCards();
143         //下面测试所有扑克牌,没有实际作用
144         sh.showAllCards();
145         System.out.println("----------");
146         //下面从"孙悟空"开始分发牌
147         sh.deliverCard("孙悟空");
148         sh.showPlayerCard();
149         /*
150         这个地方需要增加处理
151         1.牌面最大的玩家下注
152         2.其他玩家是否跟注
153         3.游戏是否只剩下一个玩家?如果是,则他胜利了
154         4.若已经是最后一张牌,则需要比较剩下玩家的牌面大小
155         */
156         //再次从"电脑玩家"开始分发牌
157         sh.deliverCard("电脑玩家");
158         sh.showPlayerCard();
159     }
160 }
View Code

技术分享

  查找、替换操作:

    Collections还提供了如下常用的用于查找、替换集合元素的类方法:

      1.int binarySearch(List list, Object key)

      2.Object max(Collection coll)

      3.Object max(Collection coll, Comparator comp)

      4.Object min(Collection coll)

      5.Object min(Collection coll, Comparator comp)

      6.void fill(List list, Object obj)

      7.int frequency(Collection c, Object o)

      8.int indexOfSubList(List source, List target)

      9.int lastIndexOfSubList(List source, List target)

      10.boolean replaceAll(List list, Object oldVal, Object newVal)

技术分享
 1 import java.util.ArrayList;
 2 import java.util.Collections;
 3 
 4 public class SearchTest{
 5     public static void main(String[] args){
 6         ArrayList nums = new ArrayList();
 7         nums.add(2);
 8         nums.add(-5);
 9         nums.add(3);
10         nums.add(0);
11         System.out.println(nums);
12         System.out.println(Collections.max(nums));//输出最大元素,将输出3
13         System.out.println(Collections.min(nums));//输出最小元素,将输出-5
14         Collections.replaceAll(nums, 0, 1);//将nums中的0使用1来代替
15         System.out.println(nums);
16         //判断-5在List集合中出现的次数,返回1
17         System.out.println(Collections.frequency(nums, -5));
18         Collections.sort(nums);//对nums集合排序
19         System.out.println(nums);//输出:[-5, 1, 2, 3]
20         //只有排序后的List集合才可用二分法查询,输出3
21         System.out.println(Collections.binarySearch(nums, 3));
22     }
23 }
View Code

技术分享

  同步控制:

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

    Java中常用的集合框架中的实现类HashSet、TreeSet、ArrayList、ArrayDeque、LinkedList、HashMap、TreeMap都是线程不安全的。

    Collections提供了多个类方法可以把它们包装成线程同步的集合:

技术分享
 1 import java.util.Collection;
 2 import java.util.Collections;
 3 import java.util.ArrayList;
 4 import java.util.HashSet;
 5 import java.util.HashMap;
 6 import java.util.List;
 7 import java.util.Set;
 8 import java.util.Map;
 9 
10 public class SynchronizedTest{
11     public static void main(String[] args){
12         //下面创建了4个线程安全的集合对象
13         Collection c = Collections.synchronizedCollection(new ArrayList());
14         List list = Collections.synchronizedList(new ArrayList());
15         Set s = Collections.synchronizedSet(new HashSet());
16         Map m = Collections.synchronizedMap(new HashMap());
17     }
18 }
View Code

  设置不可变的集合

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

      1.emptyXxx():返回一个空的、不可变的集合对象,此处的集合既可以是List,也可以是SortedSet、Set还可以是Map、SortedMap等

      2.singletonXxx():返回一个只包含指定对象(只有一个或一项元素)的、不可变的集合对象,此处的集合既可以是List,还可以是Map

      3.unmodifiableXxx():返回指定集合对象的不可变视图,此处的集合既可以是List,也可以是Set、SortedSet,还可以是Map、SortedMap等

    上面三类方法的参数时原有的集合对象,返回值是该集合的“只读”版本。

技术分享
 1 import java.util.List;
 2 import java.util.Collections;
 3 import java.util.HashMap;
 4 import java.util.Set;
 5 import java.util.Map;
 6 
 7 public class UnmodifiableTest{
 8     public static void main(String[] args){
 9         //创建一个空的、不可改变的List对象
10         List unmodifiableList = Collections.emptyList();
11         //创建一个只有一个元素,且不可改变的Set对象
12         Set unmodifiableSet = Collections.singleton("疯狂Java讲义");
13         //创建一个普通的Map对象
14         Map scores = new HashMap();
15         scores.put("语文", 80);
16         scores.put("Java", 82);
17         //返回普通的Map对象对应的不可变版本
18         Map unmodifiableMap = Collections.unmodifiableMap(scores);
19         //下面任意一行代码都将引发UnsupportedOperationException异常
20         unmodifiableList.add("测试元素");
21         unmodifiableSet.add("测试元素");
22         unmodifiableMap.put("语文", 90);
23     }
24 }
View Code

技术分享

第八章.Java集合