第八章.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 }
使用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 }
使用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 }
从上面代码可以看出,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 }
没有报错,不知道什么原因。
使用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 }
使用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 }
没有报错,不知道为什么?
使用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 }
对上面的集合提出三个统计要求:
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 }
若采用传统方式完成上面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 }
上面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 }
注释掉所有聚集操作,只保留一条后,就不会报错了。
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 }
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 java第八章:集合容器之Set接口