第八章.Java集合

Posted lanshanxiao

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第八章.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将会把它们存储在不同的位置,依然可以添加成功。