第8章 Java集合
Java集合类是一种特别有用的工具类,用于存储数量不等的对象,并可以实现常用的数据结构。
8.1 Java集合概述
在编程时,常常需要集中存放多个数据,使用数组可以存放多个对象,但数组的长度不可变。而且数组无法报错具有映射关系的数据,比如成绩表:语文-80。
为了保存数量不确定,以及具有映射关系的数据,Java提供了集合类。集合类主要保存、盛装其他数据,因此也称为容器类。
集合类和数组不同,数组元素可以是基本类型,也可以是对象。
。集合里只能保存对象(实际保存的是对象的引用变量)。
Java集合类主要由两个接口派生而出:Collection和Map。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4bESKdnq-1588248416657)(media/image1.png)]{width=“7.074179790026247in” height=“4.385655074365705in”}
8.2 Collection接口和Iterator接口
Collection接口是List、Set和Queue接口的父接口,该接口里定义的方法可用于操作List、Set和Queue。
Collection定义的方法:
小结:
添加: add,addAll,
清除 clear,
包含元素: contains, containsAll
是否为空:isEmpty
返回Iterator: iterator
删除:remove, removeAll, retainAll(Collection c)//从集合中删除集合c不包含的元素
返回元素个数:size
转换数组:toArray()
//Collection测试 package ch8; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; public class CollectionTest { public static void main(String[] args) { Collection c = new ArrayList(); c.add(“孙悟空”); c.add(6); System.out.println(“元素个数”+c.size()); c.remove(6); System.out.println(“元素个数”+c.size()); System.out.println(“c集合包含字符串(孙悟空)?”+c.contains(“孙悟空”)); c.add(“javaEE”); System.out.println(“c集合中的元素”+c); Collection books = new HashSet(); books.add(“JavaEE”); books.add(“疯狂java”); System.out.println(“c集合完全包含books集合?”+c.containsAll(books)); c.removeAll(books); System.out.println(“c集合元素:”+c); c.clear(); System.out.println(“c集合元素:”+c); books.retainAll©; System.out.println(“books集合元素:”+books); } }
8.2.1 使用Lambda表达式
Java8为 Iteratable接口新增了一个forEach(Consumer Action)默认方法,参数为一个函数式接口。
而Iteratable接口是Collection接口的父接口,因此Collection集合也可以用调用此方法。
调用forEach方法时,程序会依次将集合元素传给Consumer的accept(T t)方法。
可以使用Lambda表达式来实现。:
package ch8; import java.util.Collection; import java.util.HashSet; public class CollectionEach { public static void main(String[] args) { Collection books = new HashSet(); books.add(“javaEE”); books.add(“疯狂java”); books.add(“疯狂Android”); books.forEach(obj->System.out.println(“迭代元素”+obj)); //使用forEach } }
8.2.2 使用Java 8 增强的Iterator遍历集合元素
Iterator接口主要用来遍历(迭代访问)Collection集合中的元素,Iterator对象也被称为迭代器。
Iterator定义的4个方法:
Boolean hasNext(); //还没遍历完 Object next(); //返回下一个元素 void remove(); //删除上一次next方法返回的元素 void forEachRemaining(Consumer action); //Java8新增的默认方法,该方法可用Lambda表达式遍历集合元素。 package ch8; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; public class IteratorTest { public static void main(String[] args) { Collection books = new HashSet(); books.add(“javaEE”); books.add(“疯狂java”); books.add(“疯狂Android”); books.forEach(obj->System.out.println(“迭代元素”+obj)); Iterator it = books.iterator(); while(it.hasNext()) { String book = (String)it.next(); System.out.println(book); if(“疯狂java”.equals(book)) { it.remove(); } book = “测试字符串”;//对book赋值,不会改变元素本身 } System.out.println(books); } }
对迭代变量book进行赋值,并没有改变集合元素:iterator并没有把元素本身传递给迭代变量,而是传递值。
使用Iterator迭代访问Collection集合元素时,Collection集合里的元素不能被改变,只能通过Iterator的remove()方法删除上一次next()返回的集合元素。
8.2.3 使用Lambda表达式遍历Iterator
package ch8; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; public class IteratorEachTest { public static void main(String[] args) { Collection books = new HashSet(); books.add(“javaEE”); books.add(“疯狂java”); books.add(“疯狂Android”); Iterator it = books.iterator(); it.forEachRemaining(obj->System.out.println(“迭代元素”+obj)); } }
8.2.4 使用foreach循环遍历集合元素
使用foreach循环访问集合元素:
package ch8; import java.util.Collection; import java.util.HashSet; public class ForeachTest { public static void main(String[] args) { Collection books = new HashSet(); books.add(“javaEE”); books.add(“疯狂java”); books.add(“疯狂Android”); for(Object obj : books) { String book = (String)obj; System.out.println(book); //这里不能修改集合:books.remove(book);该语句会引起异常 } } }
8.2.5 使用Java8 新增的Predicate操作集合
Java8为Collection集合新增了一个removeIf(Predicate filter)方法,该方法会批量删除符合filter条件的元素。
Pridicate 也是函数式接口,可以用Lambda表达式来代替。
package ch8; import java.util.Collection; import java.util.HashSet; public class PredicateTest { public static void main(String[] args) { Collection books = new HashSet(); books.add(new String(“轻量级jave EE企业应用实战”)); books.add(new String(“疯狂Java讲义”)); books.add(new String(“疯狂IOS讲义”)); books.removeIf(ele->((String)ele).length()<10); //使用removeIf System.out.println(books); } }
使用Predicate可以简化集合的运算,假设有3个统计需求:1、统计出现疯狂的图书数量;2、统计包含Java的图书数量;3、统计名字长度大于10的图书数量
使用Predicate只需要一个方法:
package ch8; import java.util.Collection; import java.util.HashSet; import java.util.function.Predicate; public class PredicateTest2 { public static void main(String[] args) { Collection books = new HashSet(); books.add(new String(“轻量级jave EE企业应用实战”)); books.add(new String(“疯狂Java讲义”)); books.add(new String(“疯狂IOS讲义”)); System.out.println(calAll(books, ele->((String)ele).contains(“疯狂”))); System.out.println(calAll(books, ele->((String)ele).contains(“Java”))); System.out.println(calAll(books, ele->((String)ele).length()>10 )); } public static int calAll(Collection books,Predicate p) { int total = 0; for(Object obj : books) { if(p.test(obj)) { //调用Predicate的test方法,判断对象是否满足条件p。 total++; } } return total; } }
8.2.6 使用Java8新增的Stream操作集合
Java8 新增了Stream、IntStream、LongStream、DoubleStream等流式API,代表多个支持串型和并行聚集操作的元素。
Stream是通用的流接口,而剩下3个则是对应元素的流。
Java8 还为流式API提供了对应的Builder,如Stream.Builder,可以通过调用Builder来创建对应的流。
创建Stream的步骤:
1、使用Stream类的builder()类方法,创建Builder。
2、重复调用Builder的add()方法向流中添加元素
3、调用Builder的build()方法获取对应的流
4、调用Stream的聚集方法。
//对于大部分聚集办法而言,每个Stream只能执行一次聚集方法。
package ch8; import java.util.stream.IntStream; public class IntStreamTest { public static void main(String[] args) { IntStream is = IntStream.builder().add(20).add(13).add(-2).add(18).build(); //大部分聚集方法只能用一次,下面的语句每次只能执行一行 //System.out.println(“最大值:”+is.max().getAsInt()); //System.out.println("最小值: "+is.min().getAsInt()); //System.out.println(“总和:”+is.sum()); //System.out.println(“元素数”+is.count()); //System.out.println(“平均值”+is.average()); //System.out.println(“is所有元素平方>20?”+is.allMatch(ele->ele*ele>20)); //System.out.println(“is包含平方>20的元素?”+is.anyMatch(ele->ele*ele>20)); //将is映射成一个新Stream。 IntStream newIs = is.map(ele->ele*2+1); newIs.forEach(System.out::println); } }
聚集方法可以是中间的,也可以是末端的。
中间方法:允许流保持打开,并调用后续方法。如上述的map()方法。中间方法返回值是另一个流。
末端方法:对流的最终操作,将流结束,使流不可再用。 如上述的sum(),count()方法。
常用中间方法小结:
过滤,映射,逐个操作测试,排序所有重复元素,排序,限制最大访问元素个数。
常用末端方法:
遍历执行,转换数组,合并,最大/小值,数量,Match,返回元素
Java8运行使用流式API操作集合。Collection接口提供一个stream()默认方法,返回对应的流。
package ch8; import java.util.Collection; import java.util.HashSet; public class CollectionStream { public static void main(String[] args) { Collection books = new HashSet(); books.add(new String(“轻量级jave EE企业应用实战”)); books.add(new String(“疯狂Java讲义”)); books.add(new String(“疯狂IOS讲义”)); System.out.println(books.stream().filter(ele->((String)ele).contains(“疯狂”)).count()); System.out.println(books.stream().filter(ele->((String)ele).contains(“Java”)).count()); System.out.println(books.stream().filter(ele->((String)ele).length()>10).count()); books.stream().mapToInt(ele->((String)ele).length()).forEach(System.out::println); } }
8.3 Set集合
Set集合类似于一个罐子,可以把对象丢进Set集合中,而Set不会记住元素的顺序。
Set集合和Collection基本相似。只是Set集合不允许包含重复元素。
8.3.1 HashSet类
HashSet是Set类接口的典型实现,大多数使用Set集合时就是使用这个实现类。
HashSet按Hash算法存储集合中的元素,具有很好的存取和查找性能。
HashSet具有如下特点:
1、不保证元素的排列顺序
2、不是同步的。多个线程访问一个HashSet时,需要程序员保证其同步。
3、集合元素值可以是null。
HashSet集合判断元素相等的标准是 两个对象equals()相等,且hashCode()相等。
@如果需要将某个类的对象保存到HashSet中,重写equals()方法和hashCode()方法时,尽量保证两个对象通过equals()返回true时,hashCode()返回值也相等。
HashSet中每个能存储元素的"槽位"(slot)通常被称为"桶"(bucket),如果多个元素hashCode相等,而equals返回false,就需要在一个桶中放多个元素,会导致性能下降。
@重写hashCode()的原则:同一个对象hashCode()返回值始终相等; 两个对象equals()返回true时,hashCode()返回相等的值。
对象中equals()方法用来比较的实例变量,应该用于计算hashCode值。
重写hashCode()的一般步骤:
1、把对象内每个有意义的实例变量都计算出一个int类型的hashCode值。计算方法见表8.1。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QHd8pjVb-1588248416665)(media/image2.png)]{width=“8.300698818897638in” height=“1.710492125984252in”}
2、用第1步计算出的多个hashCode组合计算出一个hashCode返回:
return f1.hashCode()*19 +(int) f2 *31 //19,31是质数,用来避免直接相加偶然相等。
如果向HashSet添加一个可变对象后,后面程序修改了该可变对象的实例变量,则可能导致它与集合中其它元素相同。
这就可能导致HashSet中包含两个相同的对象。这会导致该集合行为混乱。
8.3.2 LinkedHashSet类
HashSet子类。它会使用链表维护元素的次序,使得元素看起来是以插入顺序保存的。
输出LinkedHashSet集合的元素时,元素的顺序总是和添加顺序一致。
8.3.3 TreeSet类
TreeSet类是Sorted接口的实现类。可以确保元素处于排序状态(默认升序)。
TreeSet类的额外方法:
访问第一个、最后一个、前一个、后一个。以及3个获取子集的方法。
package ch8; import java.util.TreeSet; public class TreeSetTest { public static void main(String[] args) { TreeSet nums = new TreeSet(); nums.add(5); nums.add(2); nums.add(1); nums.add(4); System.out.println(nums); //输出1,2,4,5 System.out.println(nums.first()); System.out.println(nums.last()); System.out.println(nums.headSet(4));//小于4的子集,不含4 System.out.println(nums.tailSet(4));//大于等于4的子集,含4 System.out.println(nums.subSet(2, 4));//大于等于2,小于4 } }
TreeSet默认采用自然排序(升序排序)。调用元素的compareTo(Object obj)方法比较大小,然后升序排序。
TreeSet内的对象必须实现Compareable接口,(实现compareTo()方法)。
TreeSet内的对象应该是一个类的对象,否则无法调用compareTo()方法。//大部分compareTo()方法都要进行强制类型转换。即使用户自定义类实现Compareable接口,compareTo()不使用强制类型转换,但取出元素时,仍然会发生ClassCastException。总之, TreeSet里只能添加一种类型的对象。
@compareTo()和equals()方法:compareTo()返回0时,equals()应该返回true;
@ 与HashSet类似,建议不要修改放入TreeSet集合中元素的关键实例变量。
2.定制顺序
如果需要定制顺序,则需要在创建TreeSet集合对象时,提供一个Comparator对象与该集合关联。Comparator对象负责对集合元素排序。
Comparator接口包含一个int compare(T o1,T o2)方法。返回正整数则o1大于o2,返回0,相等,返回负数,o1小于o2.
package ch8; import java.util.TreeSet; class M { int age; public M(int age) { this.age = age; } public String toString() { return “M[age:”+age+"]"; } } public class TreeSetTest4 { public static void main(String[] args) { TreeSet ts = new TreeSet( (o1,o2)->{ //Comparator ,使用Lambda表达式简写。 M m1 = (M)o1; M m2 = (M)o2; return m1.age>m2.age ? -1 m1.age<m2.age?1 :0; }); ts.add(new M(5)); ts.add(new M(9)); ts.add(new M(3)); System.out.println(ts); } }
8.3.4 EnumSet类
专门为枚举可设计的集合类。
元素必须是指定枚举类型。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KORJELFO-1588248416667)(media/image3.png)]{width=“8.586475284339457in” height=“3.3018438320209973in”}
package ch8; import java.util.EnumSet; enum Season{ SPRING,SUMMER,FAIL,WINTERL } public class EnumSetTest { public static void main(String[] args) { EnumSet es1 = EnumSet.allOf(Season.class); System.out.println(es1); EnumSet es2 = EnumSet.noneOf(Season.class); System.out.println(es2); es2.add(Season.WINTERL); System.out.println(es2); EnumSet es3 = EnumSet.of(Season.FAIL,Season.WINTERL); System.out.println(es3); EnumSet es4 = EnumSet.range(Season.SUMMER, Season.WINTERL); System.out.println(es4); EnumSet es5 = EnumSet.complementOf(es4); System.out.println(es5); } } /可以使用EnumSet.copyOf© 复制一个Collection集合中的元素,前提是c中元素必须是同一个枚举类的枚举值。
8.3.5各Set实现类的性能分析
…
HashSet性能好
TreeSet 可以排序。
LinkedHashSet 遍历快。
EnumSet专为枚举设计。
8.4 List集合
List集合代表代表一个元素有序、可重复的集合。
List默认按索引的添加顺序设置元素索引(从0开始)。
8.4.1 Java8改进的List接口和ListIterator接口
List中增加了根据索引操作集合的方法。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Iqts9foy-1588248416669)(media/image4.png)]{width=“7.973155074365704in” height=“3.7639337270341207in”}
package ch8; import java.util.ArrayList; import java.util.List; public class ListTest { public static void main(String[] args) { List books = new ArrayList(); books.add(new String(“轻量级jave EE企业应用实战”)); books.add(new String(“疯狂Java讲义”)); books.add(new String(“疯狂IOS讲义”)); System.out.println(books); books.add(1,new String(“疯狂Ajax讲义”)); for(int i = 0 ; i < books.size() ; i++ ) { System.out.println(books.get(i)); } books.remove(2); System.out.println(books); System.out.println(books.indexOf(new String(“疯狂Ajax讲义”))); books.set(1, new String(“疯狂java讲义”)); System.out.println(books); System.out.println(books.subList(1, 2)); } }
List 判断对象相等的标准是equals()方法返回true。
//sort和replace方法 package ch8; import java.util.ArrayList; import java.util.List; public class ListTest3 { public static void main(String[] args) { List books = new ArrayList(); books.add(new String(“轻量级jave EE企业应用实战”)); books.add(new String(“疯狂Java讲义”)); books.add(new String(“疯狂IOS讲义”)); books.add(new String(“疯狂Ajax讲义”)); //sort books.sort((o1,o2)->((String)o1).length()-((String)o2).length()); System.out.println(books); //replaceAll books.replaceAll(ele->((String)ele).length()); System.out.println(books); } }
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ioufkGkk-1588248416673)(media/image5.png)]{width=“7.838729221347331in” height=“1.4366797900262467in”}
ListIterator增加了向前迭代和添加元素。
package ch8; import java.util.ArrayList; import java.util.List; import java.util.ListIterator; public class ListIteratorTest { public static void main(String[] args) { String[] books = {“疯狂java讲义”,“疯狂IOS讲义”,“疯狂Java EE 企业应用实战”}; List bookList = new ArrayList(); for(int i = 0 ; i < books.length ; i++ ) { bookList.add(books[i]); } ListIterator lit = bookList.listIterator(); while(lit.hasNext()) { System.out.println(lit.next()); lit.add("----------------"); } System.out.println(“现在开始反向迭代”); while(lit.hasPrevious()) { System.out.println(lit.previous()); } } }
[笔记] 疯狂JAVA讲义(第3版)第8章 Java集合(二):https://developer.aliyun.com/article/1537905