Java讲义第八章学习笔记
chapter 8 Java集合
8.1 类的加载、连接和初始化
集合类主要负责保存、盛装其他数据,因此集合类也称为容器类。所有的集合类都位于java.util包下,后来为了处理多线程环境下的并发安全问题,Java 5还在java.util.concurrent包下提供了一些多线程支持的集合类。
Java的集合类主要由两个接口派生而出:Collection和Map,Collection和Map是Java集合框架的根接口,这两个接口又包含了一些子接口或实现类。
8.2 Java 11增强的Collection和Iterator接口
public class CollectionTest { public static void main(String[] args) { Collection c = new ArrayList(); //添加元素 c.add("孙悟空"); //虽然集合里不能放基本类型的值,但Java支持自动装箱 c.add(6); System.out.println("c集合的元素个数为:" + c.size()); //2 //删除指定元素 c.remove(6); System.out.println("c集合的元素个数为:" + c.size()); //1 //判断是否包含字符串 System.out.println("c集合是否包含\"孙悟空\"字符串:" +c.contains("孙悟空")); //true c.add("轻量级java ee企业应用实战"); System.out.println("c集合的元素:" + c); Collection books = new HashSet(); books.add("轻量级java ee企业应用实战"); books.add("疯狂Java讲义"); System.out.println("c集合是否包含books集合?" + c.containsAll(books));//false //用c集合减去books集合里的元素 c.removeAll(books); System.out.println("c集合的元素:" + c); //删除c集合里的素有元素 c.clear(); System.out.println("c集合里的元素:" + c); //控制books集合里只剩下c集合里也包含的元素 books.retainAll(c); System.out.println("books 集合的元素:" + books); } }
输出结果:
c集合的元素个数为:2 c集合的元素个数为:1 c集合是否包含"孙悟空"字符串:true c集合的元素:[孙悟空, 轻量级java ee企业应用实战] c集合是否包含books集合?false c集合的元素:[孙悟空] c集合里的元素:[] books 集合的元素:[]
--8.2.1 使用Lambda表达式遍历集合
Java 8为Iterable接口新增了一个forEach(Consumer action)默认方法,该方法所需参数的类型是一个函数式接口。正因为Consumer是函数式接口,因此可以使用Lambda表达式来遍历集合元素。
public class CollectionEach { public static void main(String[] args) { //创建一个集合 Collection books = new HashSet(); books.add("轻量级java ee企业应用实战"); books.add("疯狂Java讲义"); books.add("疯狂Android讲义"); //调用forEach()方法遍历集合 books.forEach(obj -> System.out.println("迭代集合元素:" + obj)); } }
运行结果:
迭代集合元素:疯狂Android讲义 迭代集合元素:轻量级java ee企业应用实战 迭代集合元素:疯狂Java讲义
--8.2.2 使用Iterator遍历集合元素
public class IteratorTest { public static void main(String[] args) { //创建集合、添加元素的代码与钱一个程序相同 Collection books = new HashSet(); books.add("轻量级java ee企业应用实战"); books.add("疯狂Java讲义"); books.add("疯狂Android讲义"); Iterator it = books.iterator(); while (it.hasNext()){ //it.next()方法返回的数据类型是Object类型,因此需要强制类型转换 String book = (String)it.next(); System.out.println(book); if(book.equals("疯狂Java讲义")){ //从集合中删除上一次next()方法返回的元素 it.remove(); } //对book 变量赋值,不会改变集合元素本身 book = "测试字符串"; //1 } System.out.println(books); } }
结果:
疯狂Android讲义 轻量级java ee企业应用实战 疯狂Java讲义 [疯狂Android讲义, 轻量级java ee企业应用实战]
从上面代码可以看出,Iterator 仅用于遍历集合,Iterator本身并不提供盛装对象的能力,如果需要创建Iterator对象,则必须有一个被迭代的集合。没有集合的Iterator仿佛无本之木,没有存在价值。
当使用Iterator对集合元素进行迭代时,Iterator并不是把集合元素本身传递给了迭代变量,而是把集合元素的值传递给了迭代变量。
当使用Iterator 迭代访问 Collection 集合元素时,Collection 集合里的元素不能被改变,只能通过Iterator 的 remove()方法删除上一次 next()方法返回的集合元素才可以;否则将会引发java.util.ConcurrentModificationException异常。
public class IteratorErrorTest { public static void main(String[] args) { Collection books = new HashSet(); books.add("轻量级java ee企业应用实战"); books.add("疯狂Java讲义"); books.add("疯狂Android讲义"); Iterator it = books.iterator(); while (it.hasNext()) { //it.next()方法返回的数据类型是Object类型,因此需要强制类型转换 String book = (String) it.next(); System.out.println(book); if (book.equals("疯狂Android讲义")) { //使用 Iterator 迭代过程中,不可修改集合元素,下面代码引发异常 books.remove(book); } } } }
运行结果:
疯狂Android讲义 Exception in thread "main" java.util.ConcurrentModificationException at java.util.HashMap$HashIterator.nextNode(HashMap.java:1445) at java.util.HashMap$KeyIterator.next(HashMap.java:1469) at IteratorErrorTest.main(IteratorErrorTest.java:15)
--8.2.3 使用Lambda表达式遍历Iterator
Java 8为Iterator新增了一个forEachRemaining(Consumer action)方法,该方法所需的Consumer参数同样也是函数式接口。当程序调用Iterator的forEachRemaining(Consumeraction)遍历集合元素时,程序会依次将集合元素传给Consumer的accept(T t)方法(该接口中唯一的抽象方法)。
public class IteratorEach { public static void main(String[] args) { Collection books = new HashSet(); books.add("轻量级java ee企业应用实战"); books.add("疯狂Java讲义"); books.add("疯狂Android讲义"); Iterator it = books.iterator(); //使用Lambda表达式 it.forEachRemaining(obj -> System.out.println("迭代集合元素:" + obj)); } }
结果:
迭代集合元素:疯狂Android讲义 迭代集合元素:轻量级java ee企业应用实战 迭代集合元素:疯狂Java讲义
--8.2.4 使用foreach循环遍历集合元素
Java 5提供的foreach循环迭代访问集合元素更加便捷。
public class ForeachTest { public static void main(String[] args) { Collection books = new HashSet(); books.add("轻量级java ee企业应用实战"); books.add("疯狂Java讲义"); books.add("疯狂Android讲义"); for (Object obj : books){ //此处的book变量也不是集合元素本身 String book = (String)obj; System.out.println(book); if(book.equals("疯狂Android讲义")){ //下面代码会引发ConcurrentModificationException异常 books.remove(book); } } } }
--8.2.5 使用Predicate操作集合
Java 8为Collection集合新增了一个removeIf(Predicate filter)方法,该方法将会批量删除符合filter条件的所有元素。该方法需要一个Predicate(谓词)对象作为参数,Predicate也是函数式接口,因此可使用Lambda表达式作为参数。
--8.2.6 使用Stream操作集合
public class InputStreamTest { public static void main(String[] args){ IntStream is = IntStream.builder() .add(20) .add(13) .add(-2) .add(18) .build(); //下面调用聚集方法的代码每次只能执行一行 //其他需要注释掉 System.out.println("is所有元素的最大值:" + is.max().getAsInt()); System.out.println("is所有元素的最小值:" + is.min().getAsInt()); System.out.println("is所有元素的总和:" + is.sum()); System.out.println("is所有元素的总数:" + is.count()); System.out.println("is所有元素的平均值:" + 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,新Stream的每个元素是原Stream元素的2倍+1 IntStream newIs = is.map(ele ->ele * 2 + 1); //使用方法引用的方式来遍历集合元素 newIs.forEach(System.out::println); } }
public class CollectionStream { public static void main(String[] args) { //创建 books集合 Collection books = new HashSet(); books.add(new String("轻量级Java EE企业应用实战")); books.add(new String("疯狂Java讲义")); books.add(new String("疯狂IOS讲义")); books.add(new String("疯狂Ajax讲义")); books.add(new String("疯狂Android讲义")); //统计书名包含”疯狂“字串的图书数量 System.out.println(books.stream().filter(ele -> ((String)ele).contains("疯狂")) .count()); //4 //统计书名包含"Java"字串的图书数量 System.out.println(books.stream().filter(ele -> ((String)ele).contains("Java")) .count()); //2 //统计书名字符长度大于10的图书数量 System.out.println(books.stream().filter(ele -> ((String)ele).length() > 10) .count()); //输出2 //先调用Collection对象的stream()方法将集合转换为Stream //再调用Stream的mapToInt()方法获取原有的Stream对应的IntStream books.stream().mapToInt(ele -> ((String)ele).length()).forEach(System.out::print); //Lambda表达式,方法引用 } }
输出结果:
4 2 2 8711168
8.3 Set集合
Set集合不允许包含相同的元素,如果试图把两个相同的元素加入同一个Set集合中,则添加操作失败,add()方法返回false,且新元素不会被加入。
--8.3.1 HashSet类
//类A的equals()方法总是返回true,但没有重写其hashCode()方法 class A { public boolean equals(Object obj){ return true; } } //类 B的hashCode()方法总是返回1,但没有重写其equals()方法 class B { public int hashCode(){ return 1; } } //类c 的hashCode()方法总是返回2,其重写equals()方法总是返回true class C{ public int hashCode(){ return 2; } public boolean equals(Object obj){ return true; } } public class HashSetTest{ public static void main(String[] args) { HashSet books = new HashSet(); //分别向books集合中添加两个A对象、两个B对象、两个C对象 books.add(new A()); books.add(new A()); books.add(new B()); books.add(new B()); books.add(new C()); books.add(new C()); System.out.println(books); } }
存储结果:
[B@1, B@1, C@2, A@7f31245a, A@14ae5a5]
上述结果看出 A 类存储两个, B类存储两个,C类只存储一个。
class R{ int count; public R(int count){ this.count = count; } public String toString(){ return "R[count:" + count + "]"; } public boolean equals(Object obj){ if(this == obj) return true; if (obj != null && obj.getClass() == R.class) { R r = (R)obj; return this.count == r.count; } return false; } public int hashCode(){ return this.count; } } public class HashSetTest2 { public static void main(String[] args) { HashSet hs = new HashSet(); hs.add(new R(5)); hs.add(new R(-3)); hs.add(new R(9)); hs.add(new R(-2)); //打印HashSet集合,集合元素没有重复 System.out.println(hs); //取出第一个元素 Iterator it = hs.iterator(); R first = (R)it.next(); //为第一个元素的count实例变量赋值 first.count = -3; //1 //再次输出HashSet集合,集合元素有重复元素 System.out.println(hs); //删除count为-3的R对象 hs.remove((new R(-3))); //2 //可以看到删除了一个R元素 System.out.println(hs); System.out.println("hs是否包含count 为-3的R对象?" + hs.contains(new R(-3))); //输出false System.out.println("hs是否包含count 为-2的R对象?" + hs.contains(new R(-2))); //输出false } }
运行结果:
[R[count:-2], R[count:-3], R[count:5], R[count:9]] [R[count:-3], R[count:-3], R[count:5], R[count:9]] [R[count:-3], R[count:5], R[count:9]] hs是否包含count 为-3的R对象?false hs是否包含count 为-2的R对象?false
上面程序提供了R类,R类重写了equals(Object obj)方法和hashCode()方法,这两个方法都是根据R对象的count 实例变量来判断的。上面程序 //1 处改变了Set集合中第一个R对象的count实例变量的值,这将导致该R对象与集合中的其他对象相同。
如运行结果,HashSet集合中第一个元素和第二个元素完全相同,这表明两个元素已经重复。此时HashSet会比较混乱:当试图删除count为 -3 的R对象时,HashSet会计算出该对象的HashCode值,从而找出该对象在集合中的保存位置,然后把此处的对象与count为-3的R对象通过equals()方法进行比较,如果相等则删除该对象 ——HashSet 只有第2个元素才满足该条件(第一个元素实际上保存在count = -2的R对象对应的位置),所以第二个元素被删除。至于第一个count为-3的R对象,他保存在count为-2的R对象对应的位置,但使用equals()方法拿他和count()为-2的R对象比较时有返回false —— 这将导致HashSet 不可能准确访问该元素。
--8.3.2 LinkedHashSet类
HashSet还有一个子类LinkedHashSet,LinkedHashSet集合也是根据元素的hashCode值来决定元素的存储位置,但它同时使用链表维护元素的次序,这样使得元素看起来是以插入的顺序保存的。也就是说,当遍历LinkedHashSet集合里的元素时,LinkedHashSet将会按元素的添加顺序来访问集合里的元素。LinkedHashSet需要维护元素的插入顺序,因此性能略低于HashSet的性能,但在迭代访问Set里的全部元素时将有很好的性能,因为它以链表来维护内部顺序。
public class LinkedHashSetTest { public static void main(String[] args) { LinkedHashSet books = new LinkedHashSet(); books.add("疯狂Java讲义"); books.add("轻量级java ee企业应用实战"); System.out.println(books); //删除 疯狂Java讲义 books.remove("疯狂Java讲义"); //重新添加 疯狂Java讲义 books.add("疯狂Java讲义"); System.out.println(books); } }
输出结果:
[疯狂Java讲义, 轻量级java ee企业应用实战] [轻量级java ee企业应用实战, 疯狂Java讲义]
输出LinkedHashSet集合的元素时,元素的顺序总是与添加顺序一致。
--8.3.3 TreeSet类
public class TreeSetTest { public static void main(String[] args) { TreeSet nums = new TreeSet(); //向 TreeSet 中添加四个 Integer 对象 nums.add(5); nums.add(2); nums.add(10); nums.add(-9); //输出集合元素,看到集合元素已经处于排序状态 System.out.println(nums); //输出集合里第一个元素 System.out.println(nums.first()); //-9 //输出集合里的最后一个元素 System.out.println(nums.last()); //10 //返回小于4的子集,不包含4 System.out.println(nums.headSet(4)); //输出[-9,2] //返回大于5的子集,如果Set中包含5,子集中还包含5 System.out.println(nums.tailSet(5)); //[5,10] //返回大于等于-3,小于4的子集 System.out.println(nums.subSet(-3,4)); //[2] } }
与HashSet集合采用hash算法来决定元素的存储位置不同,TreeSet采用红黑树的数据结构来存储集合元素。TreeSet支持两种排序方法:自然排序和定制排序。在默认情况下,TreeSet采用自然排序。
1.自然排序
TreeSet会调用集合元素的compareTo(Object obj)方法来比较元素之间的大小关系,然后将集合元素按升序排列,这种方式就是自然排序。
Java提供了一个Comparable 接口,该接口里定义了一个compareTo(Object obj)方法,该方法返回一个整数值,实现该接口的类必须实现该方法,实现了该接口的类的对象就可以比较大小。当一个对象调用该方法与另一个对象进行比较时,例如obj1.compareTo(obj2),如果该方法返回0,则表明这两个对象相等;如果该方法返回一个负整数,则表明obj1 小于 obj2。
如果试图把一个对象添加到TreeSet时,则该对象的类必须实现Comparable接口,否则程序将会抛出异常。
public class Err { public static void main(String[] args) { TreeSet ts = new TreeSet(); //向TreeSet集合中添加两个Err对象 ts.add(new Err()); ts.add(new Err()); } }
上面程序试图向TreeSet集合中添加两个Err对象,添加第一个对象时,TreeSet里没有任何元素,所以不会出现任何问题;当添加第二个Err对象时,TreeSet就会调用该对象的compareTo(Object obj)方法与集合中的其他元素进行比较——如果其对应的类没有实现Comparable接口,则会引发ClassCastException异常。
还有一点:大部分类在实现compareTo(Object obj)方法时,都需要将被比较对象obj强制类型转换成相同类型,因为只有相同类的两个实例才会比较大小。
如果向TreeSet中添加一个可变对象后,并且后面程序修改了该可变对象的实例变量,这将导致它与其他对象的大小顺序发生了改变,但TreeSet不会再次调整他们的顺序,甚至可能导致TreeSet 中保存的这两个对象通过compareTo(Object obj)方法比较返回0。
class R1 implements Comparable{ int count; public R1(int count){ this.count = count; } public String toString(){ return "R[count:" + count + "]"; } //重写equals()方法,根据count来判断是否相等 public boolean equals(Object obj){ if(this == obj){ return true; } if(obj != null && obj.getClass() == R1.class){ R1 r = (R1)obj; return r.count == this.count; } return false; } public int compareTo(Object obj) { R1 r = (R1)obj; return count > r.count ? 1 : count < r.count ? -1 : 0; } } public class TreeSetTest3{ public static void main(String[] args) { TreeSet ts = new TreeSet(); ts.add(new R1(5)); ts.add(new R1(-3)); ts.add(new R1(9)); ts.add(new R1(-2)); //打印TreeSet 集合,集合元素是有序排列的 System.out.println(ts); //1 //取出第一个元素 R1 first = (R1)ts.first(); //对第一个元素的count赋值,与第二个元素的count相同 first.count = 20; //取出最后一个元素 R1 last = (R1)ts.last(); //对最后一个元素的count赋值,与第二个元素的count相同 last.count = -2; //再次输出将看到TreeSet里的元素处于无序状态,且有重复元素 System.out.println(ts); //2 //删除实例变量被改变的元素,删除失败 System.out.println(ts.remove(new R1(-2))); //3 System.out.println(ts); //删除实例变量没有被改动的元素,删除成功 System.out.println(ts.remove(new R1(5))); //4 System.out.println(ts); } }
运行结果:
[R[count:-3], R[count:-2], R[count:5], R[count:9]] [R[count:20], R[count:-2], R[count:5], R[count:-2]] false [R[count:20], R[count:-2], R[count:5], R[count:-2]] true [R[count:20], R[count:-2], R[count:-2]]
通过上面程序得知,尽量不要修改TreeSet,HashSet中的实例变量。
2.定制排序
TreeSet 的自然排序是根据集合元素的大小,TreeSet将他们以升序排序。如果需要实现定制排序,例如以降序排序,则可以通过Comparator接口的帮助。该接口里包含一个 int compare(T o1,T o2)方法,该方法用于比较o1和o2的大小:如果该方法返回正整数,则表明o1等于o2;如果该方法返回负整数,则表明o1小于o2。
如果需要实现定制排序,则需要在创建TreeSet 集表达式来代替Comparator对象。
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) { //此处Lambda表达式的目标类型是Comparator TreeSet ts = new TreeSet((o1,o2) -> { M m1 = (M)o1; M m2 = (M)o2; //根据M对象的age属性来决定大小,age越大,M对象反而越小 return m1.age > m2.age ? -1 : m1.age < m2.age ? 1 : 0; }); ts.add(new M(5)); ts.add(new M(-3)); ts.add(new M(9)); System.out.println(ts); } }
运行结果:
[M[age:9], M[age:5], M[age:-3]]
--8.3.4 EnumSet类
enum Season { SPRING,SUMMER,FALL,WINTER } public class EnumSetTest { public static void main(String[] args) { //创建一个EnumSet集合,集合元素就是Season枚举类的全部枚举值 EnumSet es1 = EnumSet.allOf(Season.class); System.out.println(es1); //输出[SPRING,SUMMER,FALL,WINTER] //创建一个 EnumSet 空集合,指定其集合元素是Season类的枚举值 EnumSet es2 = EnumSet.noneOf(Season.class); System.out.println(es2); //输出[] //手动添加两个元素 es2.add(Season.WINTER); es2.add(Season.SPRING); System.out.println(es2); //输出[SPRING,WINTER] //以指定枚举值创建EnumSet集合 EnumSet es3 = EnumSet.of(Season.SUMMER,Season.WINTER); System.out.println(es3); //输出[SUMMER,WINTER] EnumSet es4 = EnumSet.range(Season.SUMMER,Season.WINTER); System.out.println(es4); //输出[SUMMER,FALL,WINTER] //新创建的 EnumSet 集合元素和 es4 集合元素有相同的类型 //es5 集合元素 + es4 集合元素 = Season枚举类的全部枚举值 公有 EnumSet es5 = EnumSet.complementOf(es4); System.out.println(es5); //输出[SPRING] } }
复制:
public class EnumSetTest2 { public static void main(String[] args) { Collection c = new HashSet(); c.clear(); c.add(Season.FALL); c.add(Season.SPRING); //复制Collection集合中的所有元素来创建EnumSet集合 EnumSet enumSet = EnumSet.copyOf(c); //1 System.out.println(enumSet); //输出[SPRING,FALL] c.add("疯狂Java讲义"); c.add("轻量级 JAVA EE 企业应用实战"); //下面代码出现异常:因为c集合里的元素不是全部都为枚举值 enumSet = EnumSet.copyOf(c); //2 } }
运行结果:
[SPRING, FALL] Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Enum at java.util.RegularEnumSet.add(RegularEnumSet.java:36) at java.util.EnumSet.copyOf(EnumSet.java:179) at EnumSetTest2.main(EnumSetTest2.java:17)
--8.3.5 各Set实现类的性能分析
public class ListTest { public static void main(String[] args) { List books = new ArrayList(); //向books集合中添加三个元素 books.add(new String("轻量级Java EE企业应用实战")); books.add(new String("疯狂Java讲义")); books.add(new String("疯狂Android讲义")); 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); //判断指定元素在List集合中的位置:输出1,表明位于第二位 System.out.println(books.indexOf(new String("疯狂Ajax讲义"))); //1 //将第二个元素替换成新的字符串对象 books.set(1,new String("疯狂Java讲义")); System.out.println(books); //将books集合的第二个元素(包括) //到第三个元素(不包括)截取成子集合 System.out.println(books.subList(1,2)); } }
运行结果:
[轻量级Java EE企业应用实战, 疯狂Java讲义, 疯狂Android讲义] 轻量级Java EE企业应用实战 疯狂Ajax讲义 疯狂Java讲义 疯狂Android讲义 [轻量级Java EE企业应用实战, 疯狂Ajax讲义, 疯狂Android讲义] 1 [轻量级Java EE企业应用实战, 疯狂Java讲义, 疯狂Android讲义] [疯狂Java讲义]
Java 8为List集合增加了sort()和replaceAll()两个常用的默认方法,其中sort()方法需要一个Comparator对象来控制元素排序,程序可使用Lambda表达式来作为参数;而replaceAll()方法则需要一个UnaryOperator来替换所有集合元素,UnaryOperator也是一个函数式接口,因此程序也可使用Lambda表达式作为参数。
public class ListTest3 { public static void main(String[] args) { List books = new ArrayList(); //向books 集合中添加4个元素 books.add(new String("轻量级Java EE企业应用实战")); books.add(new String("疯狂Java讲义")); books.add(new String("疯狂Android讲义")); books.add(new String("疯狂IOS讲义")); //使用目标类型为Comparator的Lambda表达式对List集合排序 books.sort((o1,o2)->((String)o1).length() - ((String)o2).length()); System.out.println(books); //使用目标类型为UnaryOperator的Lambda表达式来替换集合中所有元素 //该Lambda表达式 控制使用每个字符串的长度作为新的集合元素 books.replaceAll(ele ->((String)ele).length()); System.out.println(books); //输出[7,8,11,16] } }
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讲义 疯狂IOS讲义 轻量级Java EE企业应用实战 ======下面开始反向迭代======= -------分隔符-------- 轻量级Java EE企业应用实战 -------分隔符-------- 疯狂IOS讲义 -------分隔符-------- 疯狂Java讲义
--8.4.2 ArrayList和Vector实现类
public class FixedSizeList { public static void main(String[] args) { List fixedList = Arrays.asList("疯狂Java讲义","轻量级Java EE企业应用实战"); //获取 fixedList 的实现类,将输出Arrays$ArrayList System.out.println(fixedList.getClass()); //使用方法引用遍历集合元素 fixedList.forEach(System.out::println); //试图增加、删除元素都会引发UnsupportedOperationException异常 fixedList.add("疯狂Android讲义"); fixedList.remove("疯狂Java讲义"); } }
8.5 Queue集合
public class PriorityQueueTest { public static void main(String[] args) { PriorityQueue pq = new PriorityQueue(); //下面代码依次向pq中加入四个元素 pq.offer(6); pq.offer(-3); pq.offer(20); pq.offer(18); //输出pq队列,并不是按元素的加入顺序排列 System.out.println(pq); //输出[-3,6,20,18] //访问队列的第一个元素,其中队列中最小的元素:-3 System.out.println(pq.poll()); } }
运行结果:
[-3, 6, 20, 18] -3
从上面方法中可以看出,Deque不仅可以当成双端队列使用,而且可以被当成栈来使用,因为该类里还包含了pop(出栈)、push(入栈)两个方法。
Deque的方法与Queue的方法对照表如表8.2所示。
Deque的方法与Stack的方法对照表如表8.3所示。
public class ArrayDequeStack { public static void main(String[] args) { ArrayDeque stack = new ArrayDeque(); //依次将三个元素 push 入"栈" stack.push("疯狂Java讲义"); stack.push("轻量级 Java EE 企业应用实战"); stack.push("疯狂Android讲义"); //输出:[疯狂Java讲义,轻量级 Java EE 企业应用实战,疯狂Android讲义] System.out.println(stack); //访问第一个元素,但并不将其pop出“栈”,输出:疯狂Android讲义 System.out.println(stack.peek()); //依然输出:[疯狂Android讲义,疯狂Java讲义,轻量级 Java EE 企业应用实战] System.out.println(stack); //pop出第一个元素,输出:疯狂Android讲义 System.out.println(stack.pop()); //输出:[轻量级Java EE企业应用实战,疯狂Java讲义] System.out.println(stack); } }
队列:
public class ArrayDequeQueue {
public static void main(String[] args) {
ArrayDeque queue = new ArrayDeque();
//依次将三个元素 push 入队列
queue.offer("疯狂Java讲义");
queue.offer("轻量级 Java EE 企业应用实战");
queue.offer("疯狂Android讲义");
//输出:[疯狂Java讲义,轻量级 Java EE 企业应用实战,疯狂Android讲义]
System.out.println(queue);
//访问第一个元素,但并不将其poll,疯狂Java讲义
System.out.println(queue.peek());
//依然输出:[疯狂Android讲义,疯狂Java讲义,轻量级 Java EE 企业应用实战]
System.out.println(queue);
//pop出第一个元素,输出:疯狂Java讲义
System.out.println(queue.poll());
//输出:[轻量级 Java EE 企业应用实战, 疯狂Android讲义]
System.out.println(queue);
}
}
public class LinkListTest { public static void main(String[] args) { LinkedList books = new LinkedList(); //将字符串元素加入队列的尾部 books.offer("疯狂Java讲义"); //将一个字符串元素加入到栈的顶部 books.push("轻量级 Java EE 企业应用实战"); //将字符串元素添加到队列头部(相当于栈的顶部) books.offerFirst("疯狂Android讲义"); //以List的方式 (按索引访问的方式) 来遍历集合元素 for(int i = 0; i < books.size() ; i++){ System.out.println("遍历中:" + books.get(i)); } //访问并不删除栈顶元素 System.out.println(books.peekFirst()); //访问不删除队列的最后一个元素 System.out.println(books.peekLast()); //将栈顶的元素弹出 "栈" System.out.println(books.pop()); //下面输出将看到队列中第一个元素被删除 System.out.println(books); //访问并删除队列的最后一个元素 System.out.println(books.pollLast()); //下面输出:[轻量级 Java EE 企业应用实战] System.out.println(books); } }
输出结果:
遍历中:疯狂Android讲义 遍历中:轻量级 Java EE 企业应用实战 遍历中:疯狂Java讲义 疯狂Android讲义 疯狂Java讲义 疯狂Android讲义 [轻量级 Java EE 企业应用实战, 疯狂Java讲义] 疯狂Java讲义 [轻量级 Java EE 企业应用实战]
对比:
ArrayList、ArrayDeque内部以数组的形式来保存集合中的元素,因此随机访问集合元素时有较好的性能;而LinkedList内部以链表的形式来保存集合中的元素,因此随机访问集合元素时性能较差,但在插入、删除元素时性能比较出色(只需改变指针所指的地址即可)。需要指出的是,虽然Vector也是以数组的形式来存储集合元素的,但因为它实现了线程同步功能(而且实现机制也不好),所以各方面性能都比较差。
--8.5.4 各种线性表的性能分析
public class MapTest { public static void main(String[] args) { Map map = new HashMap(); //成对放入多个key-value对 map.put("疯狂Java讲义",109); map.put("疯狂IOS讲义",10); map.put("疯狂Ajax讲义",79); //多次放入的key-value对中value可以重复 map.put("轻量级 Java EE 企业应用实战",99); //放入重新的key时,新的value会覆盖原有的value //如果新的value覆盖了原有的value,该方法返回被覆盖的value System.out.println(map.put("疯狂IOS讲义",99)); //输出10 System.out.println(map); //输出的Map集合包含4个key-value对 //判断是否包含指定key System.out.println("是否包含值为 疯狂IOS讲义 key: " + map.containsKey("疯狂IOS讲义")); //true //判断是否包含指定value System.out.println("是否包含值为 99 value: "+map.containsValue(99)); //true //获取Map集合的所有key组成的集合,通过遍历key来实现遍历所有的key-value对 for(Object key : map.keySet()){ //map.get(key)方法获取指定key对应的value对 System.out.println(key + "-->" + map.get(key)); } map.remove("疯狂Ajax讲义"); //根据 key 来删除key-value对 System.out.println(map); //输出结果中不在包含 疯狂Ajax讲义=79 的key-value对 } }
运行结果:
10 {疯狂Ajax讲义=79, 轻量级 Java EE 企业应用实战=99, 疯狂IOS讲义=99, 疯狂Java讲义=109} 是否包含值为 疯狂IOS讲义 key: true 是否包含值为 99 value: true 疯狂Ajax讲义-->79 轻量级 Java EE 企业应用实战-->99 疯狂IOS讲义-->99 疯狂Java讲义-->109 {轻量级 Java EE 企业应用实战=99, 疯狂IOS讲义=99, 疯狂Java讲义=109}
--8.6.1 Java 8为Map新增的方法
- Object compute(Object key, BiFunction remappingFunction)
- Object computeIfAbsent(Object key, Function mappingFunction)
- Object computeIfPresent(Object key, BiFunction remappingFunction)
- void forEach(BiConsumer action)
- Object getOrDefault(Object key, V defaultValue)等。
public class MapTest2 { public static void main(String[] args) { Map map = new HashMap(); //成对放入多个key-value对 map.put("疯狂Java讲义",109); map.put("疯狂IOS讲义",99); map.put("疯狂Ajax讲义",79); //尝试替换key为"疯狂XML讲义"的value,由于Map中没有对应的key //因此Map没有改变,不会添加新的key-value对 map.replace("疯狂XML讲义",66); System.out.println(map); //使用原value与传入参数计算出来的结果覆盖原有的value map.merge("疯狂IOS讲义",10,(oldVal , param)->(Integer)oldVal + (Integer)param); System.out.println(map); //"疯狂IOS讲义"的value增大了10 //当key为"Java"对应的value为null(或不存在时),使用计算的结果作为新的value map.computeIfAbsent("Java",(key)->((String)key).length()); System.out.println(map); //map中添加了 Java=4 这组key-value对 //当key为"Java"对应的value存在时,使用计算的结果作为新的value map.computeIfPresent("Java",(key,value)->(Integer)value * (Integer)value); System.out.println(map); //map中的Java=4变成 Java=16 } }
运行结果:
{疯狂Ajax讲义=79, 疯狂IOS讲义=99, 疯狂Java讲义=109} {疯狂Ajax讲义=79, 疯狂IOS讲义=109, 疯狂Java讲义=109} {Java=4, 疯狂Ajax讲义=79, 疯狂IOS讲义=109, 疯狂Java讲义=109} {Java=16, 疯狂Ajax讲义=79, 疯狂IOS讲义=109, 疯狂Java讲义=109}
--8.6.2 改进的HashMap和Hashtable实现类
public class NullInHashMap { public static void main(String[] args) { HashMap hm = new HashMap(); //试图将两个key为null值的key-value对放入HashMap中 hm.put(null,null); hm.put(null,null); //1 无法放入 //将一个value 为null值的key-value对放入HashMap中 hm.put("a",null); //2 可以放入 //输出Map对象 System.out.println(hm); } }
运行结果:
{null=null, a=null}
为了成功的在HashMap、Hashtable中存储、获取对象,用做key的对象必须实现hashCode()方法和equals()方法。
与HashSet集合不能保证元素的顺序一样,HashMap、Hashtable也不能保证其中key-value对的顺序。类似于HashSet,HashMap,Hashtable判断两个key相等的标准也是:两个key通过equals()方法比较返回true,两个key的hashCode值也相等。
class A{ int count; public A(int count){ this.count = count; } //根据count的值来判断两个对象是否相等 public boolean equals(Object obj){ if(obj == this){ return true; } if(obj != null && obj.getClass() == A.class){ A a = (A)obj; return this.count == a.count; } return false; } //根据count来计算hashcode值 public int hashCode(){ return this.count; } } class B { //重写equals()方法,B对象与任何对象通过equals()方法比较都返回true public boolean equals(Object obj){ return true; } } public class HashtableTest { public static void main(String[] args) { Hashtable ht = new Hashtable(); ht.put(new A(60000),"疯狂Java讲义"); ht.put(new A(87563),"轻量级Java EE企业应用实战"); ht.put(new A(1232),new B()); System.out.println(ht); //只要两个对象通过equals()方法比较返回true //Hashtable就认为他们是相等的value //由于Hashtable中有一个B对象 //它与任何对象通过equals()方法比较都相等,所以下面输出true System.out.println(ht.containsValue("测试字符串")); //1输出true //只要两个A对象的count相等,它们通过equals()方法比较返回true,且hashCode值相等 //Hashtable即认为他们是相同的key,所以下面输出true System.out.println(ht.containsValue(new A(87563))); //输出true //下面语句可以删除最后一个 key-value对 ht.remove(new A(1232)); //3 System.out.println(ht); } }
运行结果:
{lfw.A@ea60=疯狂Java讲义, lfw.A@1560b=轻量级Java EE企业应用实战, lfw.A@4d0=lfw.B@14ae5a5} true true {lfw.A@ea60=疯狂Java讲义, lfw.A@1560b=轻量级Java EE企业应用实战}
Hashtable判断value相等的标准是:value与另外一个对象通过equals()方法比较返回true即可。
--8.6.3 LinkedHashMap实现类
HashSet有一个LinkedHashSet子类,HashMap也有一个LinkedHashMap子类;LinkedHashMap也使用双向链表来维护key-value对的顺序(其实只需要考虑key的顺序),该链表负责维护Map的迭代顺序,迭代顺序与key-value对的插入顺序保持一致。
LinkedHashMap可以避免对HashMap、Hashtable里的key-value对进行排序(只要插入key-value对时保持顺序即可),同时又可避免使用TreeMap所增加的成本。
LinkedHashMap需要维护元素的插入顺序,因此性能略低于HashMap的性能;但因为它以链表来维护内部顺序,所以在迭代访问Map里的全部元素时将有较好的性能。
public class LinkedHashMapTest { public static void main(String[] args) { LinkedHashMap scores = new LinkedHashMap(); scores.put("语文",80); scores.put("英文",82); scores.put("数学",76); //调用forEach()方法遍历 scores里的所有key-value对 scores.forEach((key,value)-> System.out.println(key+"--->"+value)); } }
运行结果:
语文--->80 英文--->82 数学--->76
LinkedHashMapTest可以记住Map添加顺序。
--8.6.4 使用Properties读写属性文件
public class PropertiesTest { public static void main(String[] args) throws Exception{ Properties props = new Properties(); //向Properties中添加属性 props.setProperty("username","yeeku"); props.setProperty("password","123456"); //将Properties 中的 key-value 对保存到a.ini文件中 props.store(new FileOutputStream("a.ini"),"comment line"); //1 //新建一个Properties对象 Properties prop2 = new Properties(); //添加属性 prop2.setProperty("gender","male"); //将a.ini文件中的key-value对追加到props2中 prop2.load(new FileInputStream("a.ini")); //2 System.out.println(prop2); } }
该程序会在当前目录下生成:{password=123456, gender=male, username=yeeku} a .ini文件
--8.6.5 SortedMap接口和TreeMap实现类
![](https://img2020.cnblogs.com/blog/1497668/202007/1497668-20200715105506494-482103073.png)
class R implements Comparable{ int count; public R(int count) { this.count = count; } public String toString() { return "R[" + "count=" + count + ']'; } //根据count来判断两个对象是否相等 public boolean equals(Object obj) { if (this == obj) return true; if (obj == null && obj.getClass() == R.class) { R r = (R)obj; return r.count == this.count; } return false; } //根据count属性值来判断两个对象的大小 public int compareTo(Object obj){ R r = (R)obj; return count > r.count ? 1 : count < r.count ? -1 :0; } } public class TreeMapTest { public static void main(String[] args) { TreeMap tm = new TreeMap(); tm.put(new R(3),"轻量级Java EE 企业应用实战"); tm.put(new R(-5),"疯狂Java讲义"); tm.put(new R(9),"疯狂Android讲义"); System.out.println(tm); //返回该TreeMap的第一个Entry对象 System.out.println(tm.firstEntry()); //返回该TreeMap的最后一个key值 System.out.println(tm.lastKey()); //返回该TreeMap的比new R(2)大的最小key值 System.out.println(tm.higherKey(new R(2))); //返回该TreeMap的比new R(2)小的最大的key-value值 System.out.println(tm.lowerEntry(new R(2))); //返回该TreeMap的子TreeMap System.out.println(tm.subMap(new R(-1),new R(4))); } }
运行结果:
{R[count=-5]=疯狂Java讲义, R[count=3]=轻量级Java EE 企业应用实战, R[count=9]=疯狂Android讲义} R[count=-5]=疯狂Java讲义 R[count=9] R[count=3] R[count=-5]=疯狂Java讲义 {R[count=3]=轻量级Java EE 企业应用实战}
--8.6.6 WeakHashMap实现类
WeakHashMap与HashMap的用法基本相似。与HashMap的区别在于,HashMap的key保留了对实际对象的强引用,这意味着只要该HashMap对象不被销毁,该HashMap的所有key所引用的对象就不会被垃圾回收,HashMap也不会自动删除这些key所对应的key-value对;但WeakHashMap的key只保留了对实际对象的弱引用,这意味着如果WeakHashMap对象的key所引用的对象没有被其他强引用变量所引用,则这些key所引用的对象可能被垃圾回收,WeakHashMap也可能自动删除这些key所对应的key-value对。
public class WeakHashMapTest { public static void main(String[] args) { WeakHashMap whm = new WeakHashMap(); //向WeakHashMap中添加三个 key-value对 //三个key都是匿名字符串对象(没有去其他引用) whm.put(new String("语文"),new String("良好")); whm.put(new String("数学"),new String("及格")); whm.put(new String("英文"),new String("中等")); //向WeakHashMap中添加一个key-value对 //该key 是一个系统缓存的字符串对象 whm.put("java",new String("中等")); //1 //输出whm对象,将看到4个key-value对 System.out.println(whm); //通知系统立即进行垃圾回收 System.gc(); System.runFinalization(); //在通常情况下,将只看到一个key-value对 System.out.println(whm); } }
运行结果:
{英文=中等, java=中等, 数学=及格, 语文=良好} {java=中等}
从上面运行结果可以看出,当系统进行垃圾回收时,删除了WeakHashMap对象的前三个key-value对。这是因为添加前三个 key-value对时,这三个key都是匿名的字符串对象,WeakHashMap只保留了对他们的弱引用,这样垃圾回收时会自动删除这三个key-value对。
WeakHashMap对象中第4个组key-value对的key是一个字符串直接量。(系统会自动保留对该字符串对象的强引用),所以垃圾回收时不会回收它。
--8.6.7 IdentityHashMap实现类
public class IdentityHashMapTest { public static void main(String[] args) { IdentityHashMap ihm = new IdentityHashMap(); //下面两行代码将会向IdentityHashMap对象中添加两个key-value对 ihm.put(new String("语文"),89); ihm.put(new String("语文"),78); //下面两行代码只会向IdentifyHashMap对象中添加一个key-value对 ihm.put("java",93); ihm.put("java",98); System.out.println(ihm); } }
运行结果:
{语文=89, java=98, 语文=78}
上面程序中前两个key-value是匿名字符串对象,通过==比较不相等。后面两个是常量池管理的字符串直接量,通过==比较返回true。
--8.6.8 EnumMap实现类
enum Season{ SPRING,SUMMER,FALL,WINTER } public class EnumMapTest { public static void main(String[] args) { //创建 EnumMap 对象,该EnumMap的所有key都是Season枚举类的枚举值 EnumMap enumMap = new EnumMap(Season.class); enumMap.put(Season.SUMMER,"夏日炎炎"); enumMap.put(Season.SPRING,"春暖花开"); System.out.println(enumMap); //{SPRING=春暖花开, SUMMER=夏日炎炎} } }
--8.6.9 各Map实现类的性能分析
![](https://img2020.cnblogs.com/blog/1497668/202007/1497668-20200715141133090-1523957544.png)
public class SortTest { public static void main(String[] args) { ArrayList nums = new ArrayList(); nums.add(2); nums.add(-5); nums.add(3); nums.add(0); System.out.println(nums); //输出:[2,-5,3,0] Collections.reverse(nums); //反转 System.out.println(nums);//输出:[0,3,-5,2] Collections.sort(nums); //自然排序 System.out.println(nums);//输出:[-5,0,2,3] Collections.shuffle(nums); //随机排序 System.out.println(nums); //每次输出的次序不固定 } }
public class SearchTest { public static void main(String[] args) { ArrayList nums = new ArrayList(); nums.add(2); nums.add(-5); nums.add(3); nums.add(0); System.out.println(nums); //输出[2,-5,3,0] System.out.println(Collections.max(nums)); //输出最大元素 3 System.out.println(Collections.min(nums)); //输出最小元素 -5 Collections.replaceAll(nums,0,1); //将nums中的0使用1替换 System.out.println(nums); //[2,-5,3,1] //判断-5在List集合中出现的次数,返回1 System.out.println(Collections.frequency(nums,-5)); Collections.sort(nums); //排序 System.out.println(nums);//[-5,1,2,3] //只有排序后的List集合才可用二分法查询,输出 3(索引) System.out.println(Collections.binarySearch(nums,3)); } }
public class SynchronizedTest { public static void main(String[] args) { //下面程序创建了4个线程安全的集合对象 Collection c = Collections.synchronizedCollection(new ArrayList<>()); List list = Collections.synchronizedList(new ArrayList<>()); Set s = Collections.synchronizedSet(new HashSet<>()); Map m = Collections.synchronizedMap(new HashMap<>()); } }
public class UnmodifiableTest { public static void main(String[] args) { //创建一个空的、不可变的List对象 List unmodifiableList = Collections.emptyList(); //创建一个只有一个元素,且不可变的Set对象 Set unmodifiableSet = Collections.singleton("疯狂Java讲义"); //创建一个普通的Map对象 Map scores = new HashMap(); scores.put("语文",80); scores.put("Java",82); //返回普通的Map对象对应的不可变版本 Map unmodifiableMap = Collections.unmodifiableMap(scores); //下面任意一行代码都会引发UnsupportedOperationException异常 unmodifiableList.add("测试技术");//1 unmodifiableSet.add("测试元素");//2 unmodifiableMap.put("语文",90); //3 } }