第11章-持有对象

容器类的引入:Java需要有不同的方式来保存对象(或说是对象的引用)。

如数组可以保存一组对象或一组基本类型数据,也推荐使用,但是数组必需有固定的尺寸,但在实际的情况中,可能根本不知道需要多少个对象,所以数组尺寸固定就是一个局限了。

于是Java实用类库提供了一套完整的容器类来解决这个问题,包括List、Set、Queue和Map,容器类可以自动调整自己的尺寸,不需要担心容器的大小问题。

11.1 泛型和类型安全的容器

最基本最可靠的容器ArrayList可以被当作“可以自动扩充自身尺寸的数组”。

使用简单,可以通过add()方法插入对象;使用get()方法访问对象,需要使用索引;使用size方法查看大小尺寸,而不需要担心索引越界的错误。

package 第11章_持有对象.第1节_泛型和类型安全的容器;import java.util.ArrayList;public class AppleAndOrangesWithoutGenerics {@SuppressWarnings("unchecked")public static void main(String[] args) {ArrayList apples = new ArrayList();for (int i = 0; i < 3; i++) {apples.add(new Apple());// 在ArrayList中添加Apple对象}apples.add(new Orange());// 在ArrayList中添加Orange对象for (int i = 0; i < apples.size(); i++) {// 通过size()方法获取ArrayList集合大小((Apple) apples.get(i)).id();// 通过get(index)方法来获取对象}}
}class Apple {private static long counter;private final long id = counter++;public long id() {return id;}
}class Orange {
}

代码解释:

  • 如果一个类(这里是Apple、Orange类)没有显式地声明继承了哪个类,那么自动继承Object类。
  • 由于ArrayList保存的是Object对象,所以既可以添加Apple对象,也可以添加Orange对象,都不会报错。
  • 同样当使用ArrayList的get(index)方法时,获取的也是Object引用,必需将其转型为Apple类型,其中index是对象在ArrayList中的索引,类似于数组的下标。
  • 上面的代码运行会报错,因为在将Orange对象转型为Apple时,就会得到一个错误。

而ArrayList通常和泛型一起使用,如定义用来保存Apple对象的ArrayList,就可以声明ArrayList<Apple>,其中尖括号括起来的是类型参数(可以有多个),其意是指定ArrayList要保存的类型。

使用泛型,可以有效在编译期防止将错误类型的对象放置到容器中。

package 第11章_持有对象.第1节_泛型和类型安全的容器;import java.util.ArrayList;public class ApplesAndOrangesWithGenerics {public static void main(String[] args) {ArrayList<Apple> apples = new ArrayList<Apple>();for (int i = 0; i < 3; i++) {apples.add(new Apple());}// apples.add(new Orange())// 如果apples.add(new Orange()),那么会报错的for (int i = 0; i < apples.size(); i++) {System.out.println(apples.get(i).id());}// 使用foreach循环遍历for (Apple apple : apples) {System.out.println(apple.id());}}
}
/*** 打印结果:* 0* 1* 2* 0* 1* 2*/

代码解释:

  • 可以直接在编译期阻止将Orange放到apples了,因为在写代码时就会检测出来,而不必要等到运行时才报错。
  • 并且通过get(index)方法从List中取元素时也不必进行类型转换,因为List已经知道你保存的什么类型,会在调用get()方法时自动执行转型。
  • 如果不需要使用每个元素的索引,使用foreach语法会更加简洁。

当你指定了某个类型作为泛型参数时,你并不仅限于只能将该确切类型的对象放置到容器中。向上转型也可以像作用于其他类型一样作用于泛型:

package 第11章_持有对象.第1节_泛型和类型安全的容器;import java.util.ArrayList;public class GenericsAndUpcasting {public static void main(String[] args) {ArrayList<Apple> apples = new ArrayList<>();apples.add(new GrannySmith());apples.add(new Gala());apples.add(new Fuji());apples.add(new Braeburn());for (Apple apple : apples) {System.out.println(apple);}}
}class GrannySmith extends Apple {
}class Gala extends Apple {
}class Fuji extends Apple {
}class Braeburn extends Apple {
}/*** 打印结果:* 第11章_持有对象.第1节_泛型和类型安全的容器.GrannySmith@140e19d* 第11章_持有对象.第1节_泛型和类型安全的容器.Gala@17327b6* 第11章_持有对象.第1节_泛型和类型安全的容器.Fuji@14ae5a5* 第11章_持有对象.第1节_泛型和类型安全的容器.Braeburn@131245a*/

代码解释:

  • 因此可以将Apple的子类型添加到被指定为保存Apple对象的容器中。

程序的输出是从Object默认的toString()方法产生的,该方法将打印类名,后面跟随该对象的散列码的无符号十六进制表示(这个散列码是通过hashCode()方法产生的)。

11.2 基本概念

Java容器类类库的用途是“保存对象”,并将其划分为两个不同的概念:

  1. Collection。一个独立元素的序列,这些元素都服从一条或多条规则。List必须按照插入的顺序保存元素,而Set不能有重复元素。Queue按照排队规则来确定对象产生的顺序(通常与它们被插入的顺序相同)。
  2. Map。一组成对的“键值对”对象,允许你使用键来查找值。ArrayList允许你使用数字来查找值,因此在某种意义上讲,它将数字与对象关联在了一起。

Collection接口概括了序列的概念——一种存放一组对象的方式。

下面将演示用Integer对象填充一个Collection(这里用ArrayList表示):

package 第11章_持有对象.第2节_基本概念;import java.util.ArrayList;
import java.util.Collection;public class SimpleCollection {public static void main(String[] args) {Collection<Integer> c = new ArrayList<Integer>();for (int i = 0; i < 10; i++) {c.add(i);}for (Integer i : c) {System.out.print(i + ", ");}}
}
/*** 打印结果:* 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,*/

注意:所有的容器类Collection都可以用foreach语法遍历。

11.3 添加一组元素

在java.util包中的Arrays和Collections类中都有很多实用方法,可以在一个Collection中添加一组元素。

  • Arrays.asList()方法接受一个数组或是一个用逗号分隔的元素列表(使用可变参数),并将其转换为一个List对象。
  • Collections.addAll()方法接受一个Collection对象,以及一个数组或是一个用逗号分割的列表,将元素添加到Collection中。
package 第11章_持有对象.第3节_添加一组元素;import java.util.*;public class AddingGroups {public static void main(String[] args) {Collection<Integer> collection = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));Integer[] moreInts = {6, 7, 8, 9, 10};collection.addAll(Arrays.asList(moreInts));// Collection.addAll()方法只能接受另一个Collection对象作为参数Collections.addAll(collection, 11, 12, 13, 14, 15);Collections.addAll(collection, moreInts);List<Integer> list = Arrays.asList(16, 17, 18, 19, 20);list.set(1, 99);// 修改一个元素// list.add(21); // 不能使用add()方法,因为其底层是一个数组,不能随意更改大小}
}
/*** 打印结果:*/

代码解释:

  • Arrays.asList()可以将其当作List,但是其底层还是表示的是数组,因此不能调整尺寸,也就不能使用add()方法了,否则就会报错。

Arrays.asList()方法的限制是它对List的类型做出了最理想的假设,而没有注意对你对它会赋予什么样的类型。

package 第11章_持有对象.第3节_添加一组元素;import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;public class AsListInference {public static void main(String[] args) {List<Snow> snow1 = Arrays.asList(new Crusty(), new Slush(), new Powder());List<Snow> snow2 = Arrays.asList(new Light(), new Heavy());List<Snow> snow3 = new ArrayList<>();Collections.addAll(snow3, new Light(), new Heavy());List<Snow> snow4 = Arrays.<Snow>asList(new Light(), new Heavy());// 通过插入<Snow>告诉编译器实际产生的目标类型,也称为显式类型参数说明}
}class Snow {
}class Powder extends Snow {
}class Light extends Powder {
}class Heavy extends Powder {
}class Crusty extends Snow {
}class Slush extends Snow {
}

11.4 容器的打印

使用Arrays.toString()来产生数组的可打印表示,打印容器很容易。

package 第11章_持有对象.第4节_容器的打印;import java.util.*;import static net.mindview.util.Print.print;public class PrintingContainers {static Collection fill(Collection<String> collection) {collection.add("rat");collection.add("cat");collection.add("dog");collection.add("dog");return collection;}static Map fill(Map<String, String> map) {map.put("rat", "Fuzzy");map.put("cat", "Rags");map.put("dog", "Bosco");map.put("dog", "Spot");return map;}public static void main(String[] args) {print(fill(new ArrayList<String>()));print(fill(new LinkedList<String>()));print(fill(new HashSet<String>()));print(fill(new TreeSet<String>()));print(fill(new LinkedHashSet<String>()));print(fill(new HashMap<String, String>()));print(fill(new TreeMap<String, String>()));print(fill(new LinkedHashMap<String, String>()));}
}
/*** 打印结果:* [rat, cat, dog, dog]* [rat, cat, dog, dog]* [rat, cat, dog]* [cat, dog, rat]* [rat, cat, dog]* {rat=Fuzzy, cat=Rags, dog=Spot}* {cat=Rags, dog=Spot, rat=Fuzzy}* {rat=Fuzzy, cat=Rags, dog=Spot}*/

注意:Collection打印出来的内容是用方括号括住,每个元素用逗号分隔;Map则用大括号括住,键与值用等号联系,每个元素用逗号分隔。

代码解释

  • Map.put(key,value)方法将增加一个值,并将它与某个键关联起来。
  • Map.get(key)方法将得到与这个键相关联的值。
  • 不必关注Map的尺寸,因为它会自动地调整尺寸。

Java的容器类库的两种主要类型Collection和Map,区别在于容器的每个“槽”保存的元素个数不一样:

  • Collection在每个槽中只能保存一个元素,该类容器包括

    • List,以特定属性保存的一组元素,类似于数组。
    • Set,保存的元素不能重复。
    • Queue,只允许在容器的一端插入对象,从从另一端移除对象。
  • Map在每个槽内保存两个对象,即键和与之关联的值。

11.5 List

List类似于数组,将元素保存在序列中,并且可以自动调整大小。

有两种类型的List:

  • ArrayList:擅长随机访问元素,但是在List中间插入和删除元素较慢。
  • LinkedList:易于在List中间插入和删除,但在随机访问方面较慢。

下面使用了该书的一个typeinfo.pets包,但是由于找不到jar包,所以直接把相关代码写入了类中,可直接查看main方法的内容:

package 第11章_持有对象.第5节_List;import java.util.*;import static net.mindview.util.Print.print;class Individual implements Comparable<Individual> {private static long counter = 0;private final long id = counter++;private String name;public Individual(String name) {this.name = name;}// 'name' is optional:public Individual() {}@Overridepublic String toString() {return getClass().getSimpleName() +(name == null ? "" : " " + name);}public long id() {return id;}@Overridepublic boolean equals(Object o) {return o instanceof Individual &&Objects.equals(id, ((Individual) o).id);}@Overridepublic int hashCode() {return Objects.hash(name, id);}public int compareTo(Individual arg) {// Compare by class name first:String first = getClass().getSimpleName();String argFirst = arg.getClass().getSimpleName();int firstCompare = first.compareTo(argFirst);if (firstCompare != 0)return firstCompare;if (name != null && arg.name != null) {int secondCompare = name.compareTo(arg.name);if (secondCompare != 0)return secondCompare;}return (arg.id < id ? -1 : (arg.id == id ? 0 : 1));}
}class Pet extends Individual {public Pet(String name) {super(name);}public Pet() {super();}
}abstract class PetCreator {private Random rand = new Random(47L);public PetCreator() {}public abstract List<Class<? extends Pet>> types();public Pet randomPet() {int n = this.rand.nextInt(this.types().size());try {return (Pet) ((Class) this.types().get(n)).newInstance();} catch (InstantiationException var3) {throw new RuntimeException(var3);} catch (IllegalAccessException var4) {throw new RuntimeException(var4);}}public Pet[] createArray(int size) {Pet[] result = new Pet[size];for (int i = 0; i < size; ++i) {result[i] = this.randomPet();}return result;}public ArrayList<Pet> arrayList(int size) {ArrayList<Pet> result = new ArrayList();Collections.addAll(result, this.createArray(size));return result;}
}class Dog extends Pet {public Dog(String name) {super(name);}public Dog() {super();}
}class Cat extends Pet {public Cat(String name) {super(name);}public Cat() {super();}
}class Rodent extends Pet {public Rodent(String name) {super(name);}public Rodent() {super();}
}class Mutt extends Dog {public Mutt(String name) {super(name);}public Mutt() {super();}
}class Pug extends Dog {public Pug(String name) {super(name);}public Pug() {super();}
}class EgyptianMau extends Cat {public EgyptianMau(String name) {super(name);}public EgyptianMau() {super();}
}class Manx extends Cat {public Manx(String name) {super(name);}public Manx() {super();}
}class Cymric extends Manx {public Cymric(String name) {super(name);}public Cymric() {super();}
}class Rat extends Rodent {public Rat(String name) {super(name);}public Rat() {super();}
}class Mouse extends Rodent {public Mouse(String name) {super(name);}public Mouse() {super();}
}class Hamster extends Rodent {public Hamster(String name) {super(name);}public Hamster() {super();}
}class LiteralPetCreator extends PetCreator {// No try block needed.@SuppressWarnings("unchecked")public staticfinal List<Class<? extends Pet>> ALL_TYPES =Collections.unmodifiableList(Arrays.asList(Pet.class, Dog.class, Cat.class, Rodent.class,Mutt.class, Pug.class, EgyptianMau.class,Manx.class, Cymric.class, Rat.class,Mouse.class, Hamster.class));// Types for random creation:private static finalList<Class<? extends Pet>> TYPES =ALL_TYPES.subList(ALL_TYPES.indexOf(Mutt.class),ALL_TYPES.size());@Overridepublic List<Class<? extends Pet>> types() {return TYPES;}public static void main(String[] args) {System.out.println(TYPES);}
}class Pets {public static final PetCreator creator = new LiteralPetCreator();public Pets() {}public static Pet randomPet() {return creator.randomPet();}public static Pet[] createArray(int size) {return creator.createArray(size);}public static ArrayList<Pet> arrayList(int size) {return creator.arrayList(size);}
}public class ListFeatures {public static void main(String[] args) {Random rand = new Random(47);List<Pet> pets = Pets.arrayList(7);// 返回一个填充了随机选取的Pet对象的ArrayListprint("1: " + pets);Hamster h = new Hamster();pets.add(h); // 向pets这个集合中添加一个Hamster对象,并且List会自动调整尺寸print("2: " + pets);print("3: " + pets.contains(h));pets.remove(h);// 移除一个元素Pet p = pets.get(2);print("4: " + p + " " + pets.indexOf(p));Pet cymric = new Cymric();print("5: " + pets.indexOf(cymric));print("6: " + pets.remove(cymric));print("7: " + pets.remove(p));print("8: " + pets);pets.add(3, new Mouse());// 按指定索引插入元素print("9: " + pets);List<Pet> sub = pets.subList(1, 4);print("subList: " + sub);print("10: " + pets.containsAll(sub));Collections.sort(sub);// 排序print("sorted subList: " + sub);print("11: " + pets.containsAll(sub));Collections.shuffle(sub, rand);print(" shuffled subList : " + sub);print("12: " + pets.containsAll(sub));List<Pet> copy = new ArrayList<Pet>(pets);sub = Arrays.asList(pets.get(1), pets.get(4));print("sub: " + sub);copy.retainAll(sub);print("13: " + copy);copy = new ArrayList<Pet>(pets);copy.remove(2);print("14: " + copy);copy.removeAll(sub);print("15; " + copy);copy.set(1, new Mouse());print("16: " + copy);copy.addAll(2, sub);print("17:" + copy);print("18: " + pets.isEmpty());pets.clear();print("19: " + pets);print("20: " + pets.isEmpty());pets.addAll(Pets.arrayList(4));print("21: " + pets);Object[] o = pets.toArray();print("22: " + o[3]);Pet[] pa = pets.toArray(new Pet[0]);print("23: " + pa[3].id());}
}
/*** 打印结果:* 1: [Rat, Manx, Cymric, Mutt, Pug, Cymric, Pug]* 2: [Rat, Manx, Cymric, Mutt, Pug, Cymric, Pug, Hamster]* 3: true* 4: Cymric 2* 5: -1* 6: false* 7: true* 8: [Rat, Manx, Mutt, Pug, Cymric, Pug]* 9: [Rat, Manx, Mutt, Mouse, Pug, Cymric, Pug]* subList: [Manx, Mutt, Mouse]* 10: true* sorted subList: [Manx, Mouse, Mutt]* 11: true* shuffled subList : [Mouse, Manx, Mutt]* 12: true* sub: [Mouse, Pug]* 13: [Mouse, Pug]* 14: [Rat, Mouse, Mutt, Pug, Cymric, Pug]* 15; [Rat, Mutt, Cymric, Pug]* 16: [Rat, Mouse, Cymric, Pug]* 17:[Rat, Mouse, Mouse, Pug, Cymric, Pug]* 18: false* 19: []* 20: true* 21: [Manx, Cymric, Rat, EgyptianMau]* 22: EgyptianMau* 23: 14*/

代码解释:

  • contains()方法可以用来确定某个对象是否在列表中。
  • remove()方法可以移除列表中的一个对象。
  • indexOf()方法可以发现某个对象该List中所处位置的索引编号。
  • subList()方法可以在较大的List列表中创建出一个片段。
  • removeAll()方法移除List中的所有元素。

11.6 迭代器

迭代器就是为了获取List等序列中的对象,遍历并进行选择。其方法有:

  • iterator()方法要求容器返回一个Iterator。而Iterator将准备号返回序列的第一个元素
  • next()方法获取序列中的下一个元素。
  • hasNext()检查序列中是否还有元素。
  • remove()将迭代器新近返回的元素删除。
package 第11章_持有对象.第6节_迭代器;import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;public class IteratorTest {public static void main(String[] args) {List<Student> list = Student.createList();Iterator<Student> iterator = list.iterator();
//        System.out.println(iterator.next().id);// 其中next()返回的是当前的第一个元素// 使用while遍历while (iterator.hasNext()) {Student student = iterator.next();System.out.print(student.id + " ");}System.out.println();// 使用foreach遍历for (Student student : list) {System.out.print(student.id + " ");}System.out.println();// 迭代器也可以删除元素
//        System.out.println(iterator.hasNext());// 打印是否还有元素Iterator<Student> it = list.iterator();for (int i = 0; i < 6; i++) {if (it.hasNext()) {it.next();it.remove();// 使用remove()方法删除元素}}for (Student student : list) {// 重新打印System.out.print(student.id + " ");}}
}class Student {int id;static List<Student> createList() {List<Student> students = new ArrayList<>();for (int i = 1; i <= 10; i++) {Student student = new Student();student.id = i;students.add(student);}return students;}
}
/*** 打印结果:* 1 2 3 4 5 6 7 8 9 10* 1 2 3 4 5 6 7 8 9 10* 7 8 9 10*/

注:书上的代码可以查看源码文件,这里是个人所写。

代码解释:

  • 在调用remove()方法之前必须先调用next()方法。

下面创建一个display()方法而不必知晓容器的确切类型:

package 第11章_持有对象.第6节_迭代器;import java.util.*;public class CrossContainerIteration {public static void display(Iterator<Pet> it) {while (it.hasNext()) {Pet p = it.next();System.out.print(p.id() + ":" + p + " ");}System.out.println();}public static void main(String[] args) {ArrayList<Pet> pets = Pets.arrayList(8);LinkedList<Pet> petsLL = new LinkedList<Pet>(pets);HashSet<Pet> petsHS = new HashSet<Pet>(pets);TreeSet<Pet> petsTS = new TreeSet<Pet>(pets);display(pets.iterator());display(petsLL.iterator());display(petsHS.iterator());display(petsTS.iterator());}
}
/*** 打印结果:* 0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx* 0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx* 0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx* 5:Cymric 2:Cymric 7:Manx 1:Manx 3:Mutt 6:Pug 4:Pug 0:Rat*/

书中”能够将遍历序列的操作与序列底层的结构分离“说的就是在display()方法中传入的是一个迭代器Iterator,而不必关心具体是什么容器(如ArrayList、LinkedList、HashSet或TreeSet等),只对迭代器进行遍历操作。

11.6.1 ListIterator

是什么:ListIterator是一个更加强大的Iterator的子类型,它只能用于各种List类的访问。

有什么用

  • ListIterator可以双向移动(尽管Iterator只能向前移动)。
  • 可以产生相对于迭代器在列表中指向的当前位置的前一个和后一个元素的索引,并且可以使用set()方法替换它访问过的最后一个元素。
  • 可以通过调用listIterator()方法产生一个指向List开始处的ListIterator。
  • 可以通过调用listIterator(n)方法创建一个一开始就指向列表索引为n的元素处的ListIterator

示例

import java.util.*;public class ListIteration {public static void main(String[] args) {ArrayList<Pet> pets = Pets.arrayList(8);ListIterator<Pet> it = pets.listIterator();// 通过listIterator()方法产生一个ListIteratorwhile (it.hasNext()) {// 通过next()// 通过nextIndex()// 通过previousIndex()System.out.print(it.next() + ", " + it.nextIndex() + ", " + it.previousIndex() + "; ");}System.out.println();// hasPrevious()可以判断是否前一个元素while (it.hasPrevious()) {// previous()方法可以获取前一个元素System.out.print(it.previous().id() + " ");}System.out.println();System.out.println(pets);// listIterator(n)方法创建一个一开始就指向列表索引为n的元素处的ListIteratorit = pets.listIterator(3);while (it.hasNext()) {it.next();// 使用set()方法替换它访问过的最后一个元素it.set(Pets.randomPet());}System.out.println(pets);}
}/*** 打印结果:* Rat, 1, 0; Manx, 2, 1; Cymric, 3, 2; Mutt, 4, 3; Pug, 5, 4; Cymric, 6, 5; Pug, 7, 6; Manx, 8, 7;* 7 6 5 4 3 2 1 0* [Rat, Manx, Cymric, Mutt, Pug, Cymric, Pug, Manx]* [Rat, Manx, Cymric, Cymric, Rat, EgyptianMau, Hamster, EgyptianMau]*/

Iterator与ListIterator的区别

  Iterator ListIterator
遍历其他集合 Iterator可用来遍历Set和List集合。 但是ListIterator只能用来遍历List。
添加对象 Iterator不能 ListIterator有add()方法,可以向List中添加对象。
逆向遍历 Iterator就不可以,Iterator对集合只能是前向遍历。 ListIterator和Iterator都有hasNext()和next()方法,可以实现顺序向后遍历,但是ListIterator有hasPrevious()和previous()方法,可以实现逆向(顺序向前)遍历。
定位索引位置 Iterator没有此功能。 ListIterator可以定位当前的索引位置,nextIndex()和previousIndex()可以实现。
对对象的修改和删除 Iierator仅能遍历,不能修改。 都可实现删除对象,但是ListIterator可以实现对象的修改,set()方法可以实现。

11.7 LinkedList

LinkedList也如ArrayList实现了List接口,但是在随机访问逊于ArrayList,而插入和删除较高效。

LinkedList添加了可以使其用作栈、队列或双端队列的方法:

  • getFirst()和element0完全一样,它们都返回列表的头(第一个元素),而并不移除它,如果List为空,则抛出NoSuchElement-Exception。

  • peek()方法与这两个方式只是稍有差异,它在列表为空时返回null。

  • removeFirst()与remove()也是完全一样的,它们移除并返回列表的头,而在列表为空时抛出NoSuchElementException。

  • poll()稍有差异,它在列表为空时返回null。

  • addFirst()与add()和addLast()相同,它们都将某个元素插入到列表的尾(端)部。

  • removeLast()移除并返回列表的最后一个元素。

public class LinkedListFeatures {public static void main(String[] args) {LinkedList<Pet> pets = new LinkedList<Pet>(Pets.arrayList(5));print(pets);// 一些方法print("pets.getFirst(): " + pets.getFirst());// getFirst()获取第一个元素print("pets.element(): " + pets.element());// element()获取第一个元素print("pets.peek(): " + pets.peek());// peek()获取第一个元素print("pets.remove(): " + pets.remove());// remove()移除第一个元素,并返回被移除的元素print("pets.removeFirst(): " + pets.removeFirst());// removeFirst()移除第一个元素print("pets.poll(): " + pets.poll());// poll()移除第一个元素print(pets);pets.addFirst(new Rat());// addFirst()在列表的首端添加元素print("After addFirst(): " + pets);pets.offer(Pets.randomPet());// offer()在列表的末尾添加元素print("After offer(): " + pets);pets.add(Pets.randomPet());// add()在列表的末尾添加元素print("After add(): " + pets);pets.addLast(new Hamster());// addLast()在列表的末尾添加元素print("After addLast(): " + pets);print("pets.removeLast(): " + pets.removeLast());// removeLast()删除列表的最后一个元素,并返回被删除元素}
}/*** 打印结果:* [Rat, Manx, Cymric, Mutt, Pug]* pets.getFirst(): Rat* pets.element(): Rat* pets.peek(): Rat* pets.remove(): Rat* pets.removeFirst(): Manx* pets.poll(): Cymric* [Mutt, Pug]* After addFirst(): [Rat, Mutt, Pug]* After offer(): [Rat, Mutt, Pug, Cymric]* After add(): [Rat, Mutt, Pug, Cymric, Pug]* After addLast(): [Rat, Mutt, Pug, Cymric, Pug, Hamster]* pets.removeLast(): Hamster*/

11.8 Stack

栈是指“后进先出”的容器。因为最后入栈的元素,第一个出栈。

LinkedList具有能够直接实现栈的所有功能的方法,因此可以直接将LinkedList作为栈使用。

这里用LinkedList构造一个栈:

package 第11章_持有对象.第8节_Stack;import java.util.LinkedList;public class Stack<T> {// 实例化一个LinkedList来作为栈private LinkedList<T> storage = new LinkedList<>();/*** 将元素入栈** @param v 待入栈的元素*/public void push(T v) {storage.addFirst(v);}/*** 获取栈顶元素** @return 返回栈顶元素*/public T peek() {return storage.getFirst();}/*** 将元素出栈** @return 返回出栈的元素*/public T pop() {return storage.removeFirst();}/*** 判断栈是否为空** @return 返回布尔值*/public boolean empty() {return storage.isEmpty();}/*** 打印** @return 返回toString()*/public String toString() {return storage.toString();}
}

代码解释:

  • 类名之后的<T>告诉编译器这将是一个参数化类型,而其中的类型参数,即在类被使用时将会被实际类型替换的参数,就是T。

下面演示这个Stack类的测试:

package 第11章_持有对象.第8节_Stack;public class StackTest {public static void main(String[] args) {Stack<String> stack = new Stack<String>();for (String s : "My dog has fleas".split(" ")) {stack.push(s);// 入栈}while (!stack.empty()) {// 栈为不空时出栈System.out.print(stack.pop() + " ");// 出栈}}
}
/*** 打印结果:* fleas has dog My*/

可以看到栈的规则“先进后出”。

不过需要注意java.util包中的Stack,否则会发生冲突,下面看看这个例子:

package 第11章_持有对象.第8节_Stack;public class StackCollision {public static void main(String[] args) {Stack<String> stack = new Stack<String>();for (String s : "My dog has fleas".split(" ")) {stack.push(s);// 入栈}while (!stack.empty()) {// 栈为不空时出栈System.out.print(stack.pop() + " ");// 出栈}System.out.println();// 使用java.util包中的Stack类java.util.Stack<String> stack2 = new java.util.Stack<String>();for (String s : "My dog has fleas".split(" ")) {stack2.push(s);// 入栈}while (!stack2.empty()) {// 栈为不空时出栈System.out.print(stack2.pop() + " ");// 出栈}}
}
/*** 打印结果:* fleas has dog My* fleas has dog My*/

较为推荐使用LinkedList产生的Stack。

11.9 Set

Set不保存重复的元素,即[1,2,2,3,3,4]保存为[1,2,3,4]。

Set中查找是最重要的操作,因为很容易询问某个对象是否在Set中,通常选择HashSet来实现,其对快速查找作了优化。

Set具有与Collection完全一样的接口。

package 第11章_持有对象.第9节_Set;import java.util.HashSet;
import java.util.Random;
import java.util.Set;public class SetOfInteger {public static void main(String[] args) {Random rand = new Random(47);Set<Integer> intset = new HashSet<>();for (int i = 0; i < 10000; i++) {intset.add(rand.nextInt(30));// 在0到29之间的10000个随机数添加到Set中,但只保存了不重复的数}System.out.println(intset);}
}
/*** 打印结果:* [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]*/

代码解释

  • Set只保存不重复的元素。
  • 输出的顺序没有规律,是处于速度的考虑,HashSet使用了散列。(这里的无序是指存入的顺序和输出的顺序不一样
  • HashSet所维护的顺序与TreeSet或LinkedHashSet都不同,因为它们的实现具有不同的元素存储方式。

  • TreeSet将元素存储在红-黑树数据结构中,而HashSet使用的是散列函数。

  • LinkedHashList因为查询速度的原因也使用了散列,但是看起来它使用了链表来维护元素的插入顺序。

注意:上面的数字输出顺序看起来是有序的,但实际上HashSet是无序的输出,不会对结果进行排序。因为上面的数据范围太小了,仅仅是hash算法恰巧那样,如果增大数据,那么就不会看起来有序了,如下:

package 第11章_持有对象.第9节_Set;import java.util.HashSet;
import java.util.Random;
import java.util.Set;public class SetOfInteger2 {public static void main(String[] args) {Random rand = new Random(47);Set<String> intset = new HashSet<>();for (int i = 0; i < 100; i++) {// 26个字母和10个数字随机拼接intset.add((char) (rand.nextInt(26) + 'a') + "" + rand.nextInt(10));}System.out.println(intset);}
}
/*** 打印结果:* [r4, n0, n4, r9, n5, j2, n6, j3, j8, b4, f9, b6, b9, w5, s2, s4, o0, w8, w9, s5, o2, s7, o4, o6, k2, o8, g1, g2, g4, c0, k9, g8, c5, c7, c8, x1, x6, t3, x9, p3, t8, l1, p5, l2, p7, h0, h3, d1, h7, h8, d8, y0, y1, y5, q1, u6, q2, u7, q3, u8, u9, q5, q6, m3, m4, m5, m6, e0, i4, i7, e4, e6, a2, e8, a7, z1, z2, z3, v3, z8, z9]*/

如果想对结果排序,可以使用TreeSet来代替HashSet:

package 第11章_持有对象.第9节_Set;import java.util.Random;
import java.util.SortedSet;
import java.util.TreeSet;public class SortedSetOfInteger {public static void main(String[] args) {Random rand = new Random(47);SortedSet<Integer> intset = new TreeSet<Integer>();for (int i = 0; i < 10000; i++) {intset.add(rand.nextInt(30));// 在0到29之间的10000个随机数添加到Set中,但只保存了不重复的数}System.out.println(intset);}
}
/*** 打印结果:* [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]*/

使用字符串测试下:

package 第11章_持有对象.第9节_Set;import java.util.Random;
import java.util.SortedSet;
import java.util.TreeSet;public class SortedSetOfInteger2 {public static void main(String[] args) {Random rand = new Random(47);SortedSet<String> intset = new TreeSet<String>();for (int i = 0; i < 100; i++) {// 26个字母和10个数字随机拼接intset.add((char) (rand.nextInt(26) + 'a') + "" + rand.nextInt(10));}System.out.println(intset);}
}
/*** 打印结果:* [a2, a7, b4, b6, b9, c0, c5, c7, c8, d1, d8, e0, e4, e6, e8, f9, g1, g2, g4, g8, h0, h3, h7, h8, i4, i7, j2, j3, j8, k2, k9, l1, l2, m3, m4, m5, m6, n0, n4, n5, n6, o0, o2, o4, o6, o8, p3, p5, p7, q1, q2, q3, q5, q6, r4, r9, s2, s4, s5, s7, t3, t8, u6, u7, u8, u9, v3, w5, w8, w9, x1, x6, x9, y0, y1, y5, z1, z2, z3, z8, z9]*/

确实排序了和上面的HashSet不一样。

接下来将执行最常见的操作:

package 第11章_持有对象.第9节_Set;import java.util.Collections;
import java.util.HashSet;
import java.util.Set;public class SetOperations {public static void main(String[] args) {Set<String> set1 = new HashSet<>();Collections.addAll(set1, "A B C D E F G H I J K L".split(" "));set1.add("M");System.out.println("H: " + set1.contains("H"));// contains()方法判断Set集合中是否有"H"元素System.out.println("N: " + set1.contains("N"));// contains()方法判断Set集合中是否有"N"元素Set<String> set2 = new HashSet<>();Collections.addAll(set2, "H I J K L".split(" "));System.out.println("set22 in set1: " + set1.containsAll(set2));// containsAll()方法判断一个Set集合中是否包含另一个Set集合set1.remove("H");// remove()方法删除Set集合中的指定元素System.out.println("set1: " + set1);System.out.println("set2 in set1: " + set1.containsAll(set2));// containsAll()方法判断一个Set集合中是否包含另一个Set集合set1.removeAll(set2);// removeAll()方法删除一个Set()集合中的另一个Set集合System.out.println("set2 removed from set1: " + set1);Collections.addAll(set1, "X Y Z".split(" "));System.out.println("'X Y Z' added to set1: " + set1);}
}
/*** 打印结果:* H: true* N: false* set22 in set1: true* set1: [A, B, C, D, E, F, G, I, J, K, L, M]* set2 in set1: false* set2 removed from set1: [A, B, C, D, E, F, G, M]* 'X Y Z' added to set1: [A, B, C, D, E, F, G, M, X, Y, Z]*/

代码解释:

  • contains()方法判断Set集合中是否有指定元素
  • containsAll()方法判断一个Set集合中是否包含另一个Set集合
  • remove()方法删除Set集合中的指定元素
  • removeAll()方法删除一个Set()集合中的另一个Set集合

下例演示每个元素都唯一的列表,而下例就是列出SetOperations.java文件中所有单词:

package 第11章_持有对象.第9节_Set;import net.mindview.util.TextFile;import java.util.Set;
import java.util.TreeSet;public class UniqueWordsAlphabetic {public static void main(String[] args) {// 如果你想要按照字母序排序,那么可以向TreeSet的构造器传入String.CASE_INSENTIVEORDER比较器(比较器就是建立排序顺序的对象)Set<String> words = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);// TextFile继承自List<String>,其构造器将打开文件,并根据正则表达式“IW+”将其断开为单词。words.addAll(new TextFile("src\\第11章_持有对象\\第9节_Set\\SetOperations.java", "\\W+"));System.out.println(words);}
}
/*** 打印结果:* [11, 9, _, _Set, A, add, addAll, added, args, B, C, class, Collections, contains, containsAll, D, E, F, false, from, G, H, HashSet, I, import, in, J, java, K, L, M, main, N, new, out, package, println, public, remove, removeAll, removed, Set, set1, set2, set22, SetOperations, split, static, String, System, to, true, util, void, X, Y, Z]*/

11.10 Map

下面演示一个Map应用的例子,键是由Random产生的数字,而值是该数字出现的次数:

package 第11章_持有对象.第10节_Map;import java.util.HashMap;
import java.util.Map;
import java.util.Random;public class Statistics {public static void main(String[] args) {Random rand = new Random(47);Map<Integer, Integer> m = new HashMap<>();for (int i = 0; i < 10000; i++) {// 产生0到20的随机数字int r = rand.nextInt(20);// 如果键不在容器中,get()方法返回null,也就是说该数字第一次出现// 否则,将获得与之相关联的键值,然后递增Integer freq = m.get(r);m.put(r, freq == null ? 1 : freq + 1);}System.out.println(m);}
}
/*** 打印结果:* {0=481, 1=502, 2=489, 3=508, 4=481, 5=503, 6=519, 7=471, 8=468, 9=549, 10=513, 11=531, 12=521, 13=506, 14=477, 15=497, 16=533, 17=509, 18=478, 19=464}*/

下例展示了containsKey()和containsValue()等常用Map集合方法的使用

import java.util.*;import static net.mindview.util.Print.print;public class PetMap {public static void main(String[] args) {// 为集合中添加值Map<String, Pet> petMap = new HashMap<>();petMap.put("My Cat", new Cat("Molly"));// put(key,value)方法能够添加键值对petMap.put("My Dog", new Dog("Ginger"));petMap.put("My Hamster", new Hamster("Bosco"));print(petMap);// 获取集合中的值Pet dog = petMap.get("My Dog");// get()方法能够根据键获取对应的值print(dog);// 测试集合中是否包含某个键或某个值print(petMap.containsKey("My Dog"));// containsKey()查看Map集合中是否包含某个键print(petMap.containsValue(dog));// containsValue()查看Map集合中是否包含某个值}
}/*** 打印结果:* {My Dog=Dog Ginger, My Cat=Cat Molly, My Hamster=Hamster Bosco}* Dog Ginger* true* true*/

Map可以和数组及其他容器组合成强大的数据结构。

下例演示一个Map<Person, List<Pet>>的使用:

package 第11章_持有对象.第10节_Map;import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;import static net.mindview.util.Print.print;public class MapOfList {public static Map<Person, List<? extends Pet>> petPeople = new HashMap<Person, List<? extends Pet>>();static {petPeople.put(new Person("Dawn"), Arrays.asList(new Cymric("Molly"), new Mutt("Spot")));petPeople.put(new Person("Kate"), Arrays.asList(new Cat("Shackleton"), new Cat("Elsie May"), new Dog("Margrett")));petPeople.put(new Person("Marilyn"), Arrays.asList(new Pug("Louie aks Louis Snorkelstein Dupree"), new Cat("Stanford aka Stinky el Negro"), new Cat("Pinkola")));petPeople.put(new Person("Luke"), Arrays.asList(new Rat("Fuzzy"), new Rat("Fizzy")));petPeople.put(new Person("Isaac"), Arrays.asList(new Rat("Freckly")));}public static void main(String[] args) {print("People: " + petPeople.keySet());print("Pets: " + petPeople.values());for (Person person : petPeople.keySet()) {print(person + " has: ");for (Pet pet : petPeople.get(person)) {print("  " + pet);}}}
}class Person {String name;public Person(String name) {this.name = name;}@Overridepublic String toString() {return "Person "+name;}
}/*** 打印结果:* People: [Person Kate, Person Isaac, Person Luke, Person Dawn, Person Marilyn]* Pets: [[Cat Shackleton, Cat Elsie May, Dog Margrett], [Rat Freckly], [Rat Fuzzy, Rat Fizzy], [Cymric Molly, Mutt Spot], [Pug Louie aks Louis Snorkelstein Dupree, Cat Stanford aka Stinky el Negro, Cat Pinkola]]* Person Kate has:*   Cat Shackleton*   Cat Elsie May*   Dog Margrett* Person Isaac has:*   Rat Freckly* Person Luke has:*   Rat Fuzzy*   Rat Fizzy* Person Dawn has:*   Cymric Molly*   Mutt Spot* Person Marilyn has: *   Pug Louie aks Louis Snorkelstein Dupree*   Cat Stanford aka Stinky el Negro*   Cat Pinkola*/

代码解释:

  • keySet()方法所有键组成的Set,可以使用foreach去迭代遍历。

11.11 Queue

队列是一种先进先出的容器,即在容器的一端放入元素,另一端取出,放入和取出的顺序相同的。

LinkedList提供了方法以支持队列的行为,并且它实现了Queue接口,因此LinkedList可以用作Queue的一种实现。通过将LinkedList向上转型为Queue,下面的示例使用了在Queue接口中与Oueue相关的方法:

package 第11章_持有对象.第11节_Queue;import java.util.LinkedList;
import java.util.Queue;
import java.util.Random;public class QueueDemo {/*** 打印队列** @param queue 队列*/public static void printQ(Queue queue) {while (queue.peek() != null) {// peek()返回队头,但没有移除队列中的元素System.out.print(queue.remove() + " ");}System.out.println();}public static void main(String[] args) {// 整数类型队列Queue<Integer> queue = new LinkedList<>();Random rand = new Random(47);for (int i = 0; i < 10; i++) {queue.offer(rand.nextInt(i + 10));// offer()方法在队尾插入一个元素}printQ(queue);// 字符类型队列Queue<Character> qc = new LinkedList<>();for (char c : "Brontosaurus".toCharArray()) {qc.offer(c);}printQ(qc);}
}
/*** 打印结果:* 8 1 1 1 5 14 3 1 0 1* B r o n t o s a u r u s*/

代码解释:

  • offer()方法将一个元素插入到队尾或者返回false。
  • peek()方法在不移除队列元素的情况下返回队头元素,在队列为空时返回null。
  • element()方法在不移除队列元素的情况下返回队头元素,在队列为空时抛出NoSuchElementException异常。
  • poll()方法将移除并返回对头元素,在队列为空时返回null。
  • remove()方法将移除并返回对头元素,在队列为空时抛出NoSuchElementException异常。

11.11.1 PriorityQueue

PriorityQueue是优先级队列,优先级队列允许下一个弹出的元素是最需要的元素(即具有最高的优先级),而不是按照先进先出的规则了。

当你在PriorityQueue上调用offer()方法来插入一个对象时,这个对象会在队列中被排序。默认的排序将使用对象在队列中的自然顺序,但是你可以通过提供自己的Comparator来修改这个顺序。PriorityQueue可以确保当你调用peek()、 poll()和remove()方法时,获取的元素将是队列中优先级最高的元素。

下例中演示优先级队列PriorityQueue与Integer、String、Character一起使用的情况:

package 第11章_持有对象.第10节_Map.第1目_PriorityQueue;import 第11章_持有对象.第11节_Queue.QueueDemo;import java.util.*;public class PriorityQueueDemo {public static void main(String[] args) {// 演示优先级队列的基本使用PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();// 实例化优先级队列Random rand = new Random(47);for (int i = 0; i < 10; i++) {priorityQueue.offer(rand.nextInt(i + 10));// 使用offer()方法添加元素到优先级队列中}QueueDemo.printQ(priorityQueue);// 打印优先级队列// 将List<Integer>添加到优先级队列中List<Integer> ints = Arrays.asList(25, 22, 20, 18, 14, 9, 3, 1, 1, 2, 3, 9, 14, 18, 21, 23, 25);// 构建出一个List<Integer>集合priorityQueue = new PriorityQueue<>(ints);// 将List<Integer>作为参数传入PriorityQueue构造器中QueueDemo.printQ(priorityQueue);// 打印数字队列priorityQueue = new PriorityQueue<>(ints.size(), Collections.reverseOrder());// 使用Collections.reverseOrder()产生反序的ComparatorpriorityQueue.addAll(ints);QueueDemo.printQ(priorityQueue);// 打印反序数字队列String fact = "EDUCATION SHOULD ESCHEW OBFUSCATION";List<String> strings = Arrays.asList(fact.split(""));// 添加字符到List<String>列表中PriorityQueue<String> stringPQ = new PriorityQueue<>(strings);// 将List<String>列表传为参数到 PriorityQueue<String>队列中QueueDemo.printQ(stringPQ);// 打印字符队列stringPQ = new PriorityQueue<>(strings.size(), Collections.reverseOrder());// 使用Collections.reverseOrder()产生反序的ComparatorstringPQ.addAll(strings);QueueDemo.printQ(stringPQ);// 打印反序字符队列Set<Character> charSet = new HashSet<>();// 创建一个Set集合for (char c : fact.toCharArray()) {charSet.add(c);// 将字符添加到Set集合中,并利用Set的特性去除了重复字符}PriorityQueue<Character> characterPQ = new PriorityQueue<>(charSet);// 将无重复字符Set集合添加到队列中QueueDemo.printQ(characterPQ);// 打印无重复字符队列}
}
/*** 打印结果:* 0 1 1 1 1 1 3 5 8 14* 1 1 2 3 3 9 9 14 14 18 18 20 21 22 23 25 25* 25 25 23 22 21 20 18 18 14 14 9 9 3 3 2 1 1*       A A B C C C D D E E E F H H I I L N N O O O O S S S T T U U U W* W U U U T T S S S O O O O N N L I I H H F E E E D D C C C B A A*   A B C D E F H I L N O S T U W*/

Integer、String和Character可以与PriorityQueue一起工作,因为这些类已经内建了自然排序。如果你想在PriorityQueue中使用自己的类,就必须包括额外的功能以产生自然排序,或者必须提供自己的Comparator。

11.2 Collection和Iterator

Iterator和Collection:

import java.util.*;public class InterfaceVsIterator {/*** 通过迭代器Iterator来打印集合* @param it 集合的迭代器*/public static void display(Iterator<Pet> it){while (it.hasNext()){Pet p = it.next();System.out.print(p.id()+":"+p+" ");}System.out.println();}/*** 通过集合Collection来打印集合* @param pets 集合*/public static void display(Collection<Pet> pets){for (Pet p : pets) {System.out.print(p.id()+":"+p+" ");}System.out.println();}public static void main(String[] args) {// 创建测试数据ArrayList<Pet> petList = Pets.arrayList(8);Set<Pet> petSet=new HashSet<>(petList);Map<String,Pet> petMap=new LinkedHashMap<String, Pet>();String[] names=("Ralph, Eric, Robin, Lacey, "+"Britney, Sam, Spot, Fluffy").split(", ");for (int i = 0; i < names.length; i++) {petMap.put(names[i],petList.get(i));}// 进行测试display(petList);display(petSet);display(petList.iterator());display(petSet.iterator());System.out.println(petMap);System.out.println(petMap.keySet());display(petMap.values());display(petMap.values().iterator());}
}/*** 打印结果:* 0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx * 0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx * 0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx * 0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx * {Ralph=Rat, Eric=Manx, Robin=Cymric, Lacey=Mutt, Britney=Pug, Sam=Cymric, Spot=Pug, Fluffy=Manx}* [Ralph, Eric, Robin, Lacey, Britney, Sam, Spot, Fluffy]* 0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx * 0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx */

代码解释:

  • 两个版本的display0方法都可以使用Map或Collection的子类型来工作,而且Collection接口和Iterator都可以将displayO方法与底层容器的特定实现解耦。

  • 在本例中,这两种方式都可以凑效。事实上,Collection要更方便一点,因为它是Iterable类型,因此,在display(Collection)实现中,可以使用foreach结构,从而使代码更加清晰。

但当你要实现一个不是Collection的外部类时,由于让它去实现Collection接口可能非常困难或麻烦,因此使用Iterator就会变得非常吸引人。

例如,如果我们通过继承一个持有Pet对象的类来创建一个Collection的实现,那么我们必须实现所有的Collection方法,即使我们在displayO方法中不必使用它们,也必须如此。尽管这可以通过继承AbstractCollection而很容易地实现,但是你无论如何还是要被强制去实现iterator()和size),以便提供AbstractCollection没有实现,但是AbstractCollection中的其他方法会使用到的方法:

package 第11章_持有对象.第12节_Collection和Iterator;import java.util.AbstractCollection;
import java.util.Iterator;public class CollectionSequence extends AbstractCollection {private Pet[] pets = Pets.createArray(8);@Overridepublic Iterator iterator() {return new Iterator() {private int index = 0;@Overridepublic boolean hasNext() {return index < pets.length;// 如果索引小于集合中元素的个数,那么一定会有下一个元素,如果索引等于或大于,那么越界了一定不会有下一个元素了,所以返回false}@Overridepublic Pet next() {return pets[index++];}public void remove() {// 本方法是不需要implement的throw new UnsupportedOperationException();}};}@Overridepublic int size() {return pets.length;}public static void main(String[] args) {// 测试CollectionSequence c = new CollectionSequence();InterfaceVsIterator.display(c);InterfaceVsIterator.display(c.iterator());}
}
/*** 打印结果:* 0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx* 0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx*/

从本例中,你可以看到,如果你实现Collection,就必须实现iterator(),并且只拿实现iterator)与继承AbstractCollection相比,花费的代价只有略微减少。但是,如果你的类已经继承了其他的类,那么你就不能再继承AbstractCollection了(因为一个类只能继承一个类,不能继承几个类)。在这种情况下,要实现Collection,就必须实现该接口中的所有方法。此时,继承并提供创建迭代器的能力就会显得容易得多了:

package 第11章_持有对象.第12节_Collection和Iterator;import java.util.Collection;
import java.util.Iterator;public class NonCollectionSequence extends PetSequence {public Iterator<Pet> iterator() {return new Iterator<Pet>() {private int index = 0;@Overridepublic boolean hasNext() {return index < pets.length;}@Overridepublic Pet next() {return pets[index++];}public void remove() {// remove不是必要implement的throw new UnsupportedOperationException();}};}public static void main(String[] args) {NonCollectionSequence nc = new NonCollectionSequence();InterfaceVsIterator.display(nc.iterator());}
}class PetSequence {protected Pet[] pets = Pets.createArray(8);
}
/*** 打印结果:* 0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx*/

生成Iterator是将队列与消费队列的方法连接在一起耦合度最小的方式,并且与实现Collection相比,它在序列类上所施加的约束也少得多。

下面看下如果在已经继承一个类的情况下实现Collection接口的代码,相对复杂了起来:

package 第11章_持有对象.第12节_Collection和Iterator;import java.util.Collection;
import java.util.Iterator;// 已经继承PetSequence2了类,那么不能再继承AbstractCollection类,只能考虑实现Collection接口
public class CollectionSequence2 extends PetSequence2 implements Collection {@Overridepublic int size() {return 0;}@Overridepublic boolean isEmpty() {return false;}@Overridepublic boolean contains(Object o) {return false;}@Overridepublic Iterator iterator() {return null;}@Overridepublic Object[] toArray() {return new Object[0];}@Overridepublic boolean add(Object o) {return false;}@Overridepublic boolean remove(Object o) {return false;}@Overridepublic boolean addAll(Collection c) {return false;}@Overridepublic void clear() {}@Overridepublic boolean retainAll(Collection c) {return false;}@Overridepublic boolean removeAll(Collection c) {return false;}@Overridepublic boolean containsAll(Collection c) {return false;}@Overridepublic Object[] toArray(Object[] a) {return new Object[0];}
}class PetSequence2 {protected Pet[] pets = Pets.createArray(8);
}

11.13 foreach与迭代器

foreach语法既可以用于数组,又可以用于Collection对象。

package 第11章_持有对象.第13节_Foreach与迭代器;import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.LinkedList;public class ForEachCollections {public static void main(String[] args) {Collection<String> cs = new LinkedList<>();Collections.addAll(cs, "Take the long way home".split(" "));// 使用foreach遍历Collection对象for (String s : cs) {System.out.print("'" + s + "' ");}}
}
/*** 打印结果:* 'Take' 'the' 'long' 'way' 'home'*/

之所以能够使用foreach,是因为Java引入了Iterable接口,该接口包含一个能够产生Iterator的iterator()方法,并且Iterable接口用来在序列中移动。所以如果创建了任何实现了Iterable的类,都可以用于foreach语句,例如:

package 第11章_持有对象.第13节_Foreach与迭代器;import java.util.Iterator;public class IterableClass implements Iterable<String> {protected String[] words = ("And that is how " + "we know the Earth to be banana-shaped.").split(" ");@Overridepublic Iterator<String> iterator() {return new Iterator<String>() {private int index = 0;@Overridepublic boolean hasNext() {return index < words.length;}@Overridepublic String next() {return words[index++];}public void remove() {throw new UnsupportedOperationException();}};}public static void main(String[] args) {for (String s : new IterableClass()) {System.out.print(s + " ");}}
}
/*** 打印结果:* And that is how we know the Earth to be banana-shaped.*/

iterator()方法返回的是实现了Iterator<String>的匿名内部类的实例。

在Java SE中,大量的类都是Iterable类型,主要包括所有的Collection类(不包括Map)。

例如(该例目的在于认识到Java中有大量的类都实现了Iterable接口):

package 第11章_持有对象.第13节_Foreach与迭代器;import java.util.Map;public class EnvironmentVariables {public static void main(String[] args) {for (Map.Entry entry : System.getenv().entrySet()) {// 显式所有的操作系统环境变量System.out.println(entry.getKey() + ": " + entry.getValue());}}
}
/*** 打印结果:* 自行运行查看*/

System.getenv()返回一个Map.entrySet()产生一个由Map.Entry的元素构成的Set,并且|这个Set是一个Iterable,因此它可以用于foreach循环。

foreach语句可以用于数组或其他任何Iterable,但是这并不意味着数组肯定也是一个Iterable,而任何自动包装也不会自动发生:

例如(本例目的在于演示数组虽然能够使用foreach语法,但并没有实现Iterable接口):

package 第11章_持有对象.第13节_Foreach与迭代器;import java.util.Arrays;public class ArrayIsNotIterable {static <T> void test(Iterable<T> ib) {for (T t : ib) {System.out.print(t + " ");}}public static void main(String[] args) {test(Arrays.asList(1, 2, 3));String[] strings = {"A", "B", "C"};// 数组虽然能够使用foreach,但并不是实现了Iterable// test(strings);// 不能使用,必须使用Arrays.asList()方法转换为实现了Iterable接口的集合test(Arrays.asList(strings));}
}
/*** 打印结果:* 1 2 3 A B C*/

11.13.1 适配器方法惯用法

如果现有一个Iterable类,你想要添加一种或多种在foreach语句中使用这个类的方法,应该怎么做呢?例如,假设你希望可以选择以向前的方向或是向后的方向迭代一个单词列表。如果直接继承这个类,并覆盖iterator(方法,你只能替换现有的方法,而不能实现选择。

一种解决方案是所谓适配器方法的惯用法。“适配器”部分来自于设计模式,因为你必须提供特定接口以满足foreach语句。当你有一个接口并需要另一个接口时,编写适配器就可以解决问题。这里,我希望在默认的前向迭代器的基础上,添加产生反向迭代器的能力,因此我不能使用覆盖,而是添加了一个能够产生Iterable对象的方法,该对象可以用于foreach语句。正如你所见,这使得我们可以提供多种使用foreach的方式:

例如(本例目的在于演示通过适配器来产生Iterable对象):

package 第11章_持有对象.第13节_Foreach与迭代器.第1目_适配器方法惯用法;import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;public class AdapterMethodIdiom {public static void main(String[] args) {ReversibleArrayList<String> ral = new ReversibleArrayList<>(Arrays.asList("To be or not to be".split(" ")));for (String s : ral) {System.out.print(s + " ");}System.out.println();for (String s : ral.reversed()) {System.out.print(s + " ");}}
}class ReversibleArrayList<T> extends ArrayList<T> {public ReversibleArrayList(Collection<T> c) {super(c);}public Iterable<T> reversed() {return new Iterable<T>() {@Overridepublic Iterator<T> iterator() {return new Iterator<T>() {int current = size() - 1;@Overridepublic boolean hasNext() {return current > -1;}@Overridepublic T next() {return get(current--);}public void remove() {throw new UnsupportedOperationException();}};}};}
}
/*** 打印结果:* To be or not to be* be to not or be To*/

如果直接将ral对象置于foreach语句中,将得到(默认的)前向迭代器。但是如果在该对象上调用reversed0方法,就会产生不同的行为。

通过使用这种方式,我可以在IterableClass.java示例中添加两种适配器方法:

package 第11章_持有对象.第13节_Foreach与迭代器.第1目_适配器方法惯用法;import 第11章_持有对象.第13节_Foreach与迭代器.IterableClass;import java.util.*;public class MultiIterableClass extends IterableClass {public Iterable<String> reversed() {return new Iterable<String>() {@Overridepublic Iterator<String> iterator() {return new Iterator<String>() {int current = words.length - 1;@Overridepublic boolean hasNext() {return current > -1;}@Overridepublic String next() {return words[current--];}public void remove() {throw new UnsupportedOperationException();}};}};}public Iterable<String> randomized() {return new Iterable<String>() {@Overridepublic Iterator<String> iterator() {List<String> shuffled = new ArrayList<>(Arrays.asList(words));Collections.shuffle(shuffled, new Random(47));return shuffled.iterator();}};}public static void main(String[] args) {MultiIterableClass mic = new MultiIterableClass();for (String s : mic.reversed()) {System.out.print(s + " ");}System.out.println();for (String s : mic.randomized()) {System.out.print(s + " ");}System.out.println();for (String s : mic) {System.out.print(s + " ");}}
}
/*** 打印结果:* banana-shaped. be to Earth the know we how is that And* is banana-shaped. Earth that how the be And we know to* And that is how we know the Earth to be banana-shaped.*/

注意,第二个方法random()没有创建它自己的Iterator,而是直接返回被打乱的List中的Iterator。

从输出中可以看到,Collection.shuffe)方法没有影响到原来的数组,而只是打乱了shuffled中的引用。之所以这样,只是因为randomized0方法用一个ArrayList将Arrays.asList()方法的结果包装了起来。如果这个由Arrays.asList()方法产生的List被直接打乱,那么它就会修改底层的数组,就像下面这样:

package 第11章_持有对象.第13节_Foreach与迭代器.第1目_适配器方法惯用法;import java.util.*;public class ModifyingArraysAsList {public static void main(String[] args) {Random rand = new Random(47);Integer[] ia = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};List<Integer> list1 = new ArrayList<Integer>(Arrays.asList(ia));System.out.println("Before shuffling: " + list1);Collections.shuffle(list1, rand);System.out.println("After shuffling: " + list1);System.out.println("array: " + Arrays.toString(ia));List<Integer> list2 = Arrays.asList(ia);System.out.println("Before shuffling: " + list2);Collections.shuffle(list1, rand);System.out.println("After shuffling: " + list2);System.out.println("array: " + Arrays.toString(ia));}
}
/*** 打印结果:* Before shuffling: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]* After shuffling: [4, 6, 3, 1, 8, 7, 2, 5, 10, 9]* array: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]* Before shuffling: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]* After shuffling: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]* array: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]*/

在第一种情况中,Arrays.asList()的输出被传递给了ArrayList(O的构造器,这将创建一个引用ia的元素的ArrayList,因此打乱这些引用不会修改该数组。但是,如果直接使用Arrays.asList(ia)的结果,这种打乱就会修改ia的顺序。意识到Arrays.asList(产生的List对象会使用底层数组作为其物理实现是很重要的。只要你执行的操作会修改这个List,并且你不想原来的数组被修改,那么你就应该在另一个容器中创建一个副本。

11.14 总结

Java提供了大量持有对象的方式:

  1. 数组将数字与对象联系起来。它保存类型明确的对象,查询对象时,不需要对结果做类型转换。它可以是多维的,可以保存基本类型的数据。但是,数组一旦生成,其容量就不能改变。
  2. Collection保存单一的元素,而Map保存相关联的键值对。有了Java的泛型,你就可以指定容器中存放的对象类型,因此你就不会将错误类型的对象放置到容器中,并且在从容器中获取元素时,不必进行类型转换。各种Collection和各种Map都可以在你向其中添加更多的元素时,自动调整其尺寸。容器不能持有基本类型,但是自动包装机制会仔细地执行基本类型到容器中所持有的包装器类型之间的双向转换。
  3. 像数组一样,List也建立数字索引与对象的关联,因此,数组和List都是排好序的容器。List能够自动扩充容量。
  4. 如果要进行大量的随机访问,就使用ArrayList,如果要经常从表中间插入或删除元素,则应该使用LinkedList。
  5. 各种Queue以及栈的行为,由LinkedList提供支持。
  6. Map是一种将对象(而非数字)与对象相关联的设计。HashMap设计用来快速访问;而TreeMap保持“键”始终处于排序状态,所以没有HashMap快。LinkedHashMap保持元素插入的顺序,但是也通过散列提供了快速访问能力。
  7. Set不接受重复元素。HashSet提供最快的查询速度,而TreeSet保持元素处于排序状态。LinkedHashSet以插入顺序保存元素。
  8. 新程序中不应该使用过时的Vector、Hashtable和Stack。

Java容器的简图:

你可以看到,其实只有四种容器:Map、List、Set和Queue,它们各有两到三个实现版本(Queue的java.util.concurrent实现没有包括在上面这张图中)。常用的容器用黑色粗线框表示。
点线框表示接口,实线框表示普通的(具体的)类。带有空心箭头的点线表示一个特定的类实现了一个接口,实心箭头表示某个类可以生成箭头所指向类的对象。例如,任意的Collection可以生成Iterator,而List可以生成ListIterator(也能生成普通的Iterator,因为List继承自Collection)。

可以看到,除了TreeSet之外的所有Set都拥有与Collection完全一样的接口。List和Collection存在着明显的不同,尽管List所要求的方法都在Collection中。另一方面,在Queue接口中的方法都是独立的;在创建具有Queue功能的实现时,不需要使用Collection方法。最后,Map和Collection之间的唯一重叠就是Map可以使用entrySet()和values0方法来产生Collection。

《Java编程思想》阅读笔记之第11章-持有对象相关推荐

  1. java现有一个泛型类 提供数组排序功能,java编程思想读书笔记 第十六章 数组

    数组,你可以创建并组装它们,通过使用整型索引值访问它们的元素,并且它们的尺寸不能改变. 1.数组为什么特殊 数组与其他种类的容器之间的区别有三方面:效率.类型和保存基本类型的能力.在Java中数组是一 ...

  2. java编程思想学习笔记(第七章:复用类)

    复用代码是java众多引人注目的功能之一.但是要想成为极具革命性的语言,仅仅能够复制代码并对之加以改变是不够的,它还必须能够做更多的事情. 7.1组合语法 将对象引用置于新类中.每一个非基本类型的对象 ...

  3. Java编程思想读书笔记_第6章(访问权限)

    四种访问权限: public private 包访问权限 protected 如果没有明确指定package,则属于默认包 1 package access.dessert; 2 3 public c ...

  4. Java编程思想学习笔记-第11章

    <?xml version="1.0" encoding="utf-8"?> Java编程思想学习笔记-第11章 Java编程思想学习笔记-第11章 ...

  5. JAVA编程思想读书笔记(三)--RTTI

    接上篇JAVA编程思想读书笔记(二) 第十一章 运行期类型判定 No1: 对于作为程序一部分的每个类,它们都有一个Class对象.换言之,每次写一个新类时,同时也会创建一个Class对象(更恰当的说, ...

  6. Thinking in java 第11章 持有对象 笔记+习题

    Thinking in java 第11章 持有对象 学习目录 11.1 泛型和类型安全的容器 1. 当你制定了某个类型作为泛型参数时,你并不仅限于只能将该确切类型的对象放置到容器中.向上转型也可一样 ...

  7. Java编程思想学习笔记4 - 序列化技术

    今天来学习下Java序列化和反序列化技术,笔者对<Java编程思想>中的内容,结合网上各位前辈的帖子进行了整理和补充,包括: 序列化概述 Java原生序列化技术 Hessian序列化技术 ...

  8. Java编程思想读书笔记一:并发

    1. Thread.yield( )方法 当调用yield()时,即在建议具有相同优先级的其他线程可以运行了,但是注意的是,仅仅是建议,没有任何机制保证你这个建议会被采纳 .一般情况下,对于任何重要的 ...

  9. Java编程思想读书笔记(七)

    点击蓝字 关注我们 今天是端午节,在这里祝大家端午节安康.过节的同时也不要忘记知识储备,今天我 为大家带来了Java编程思想第七节多形性读书笔记.请大家一起品鉴,如果发现里面有啥写的不对的地方,请大家 ...

最新文章

  1. Google Chrome调试js入门
  2. Oracle和Mysql中的字符串的拼接
  3. OpenShift 4 - 用Debezium+Kafka实现MySQL数据库的CDC
  4. 问题 D: 求圆的面积和周长 山东科技大学oj c 语言
  5. 开课吧课堂-Java面试题:面向对象的特征有哪些方面?
  6. php版给UEditor的图片在线管理栏目增加图片删除功能
  7. 信创只是开始_一切只是开始!谁是下一个“第一创业”?
  8. 乌班图16.04网卡驱动安装
  9. oracle经纬度换算成xy坐标,xy坐标转换经纬度【处置步骤】
  10. chrome浏览器恢复书签方法
  11. 20140925百度校园招聘二面
  12. Windows Server 2012R2 虚拟专用网络技术
  13. Java 程序员必备的辅助开发神器(2022 年版),建议收仓
  14. 7、全国天气查询API接口,免费好用
  15. 5g网络优化先培训是真的吗?
  16. 尚硅谷视频总结——Java多线程
  17. WorldFirst手续费是多少?WorldFirst收取的费用有哪些?
  18. 维思营销策划网:中英文旅游网站策划方案
  19. Python 远程操作 Linux
  20. 54岁失业,97岁拿诺贝尔奖:对苦难保持耐心,才能笑到最后

热门文章

  1. SQL排序函数+排名函数使用
  2. APP开发流程实例讲解-儒释道网络电台八天开发全程-功能和界面初步设定
  3. h5 表单居中_div居中的几个方法 - 跛豪丶
  4. ECharts 实例
  5. 习题8-2 在数组中查找指定元素(15 分)
  6. 小米家投影仪2和极米z6x 哪个好
  7. matlab调用摄像头运用差帧法实现双物体跟踪
  8. 变压器绝缘电阻测试试验
  9. git stash 命令详解
  10. centos7镜像在VMware上安装centos7详细教程