Java编程思想笔记——持有对象
Java有多种方式保存对象(对象的引用):数组,Java实用类库还提供了完整的容器类:list、set、map等集合类,但由于Java的类库中使用了Collection这个名字来指代该类库的一个特殊子集,所以使用范围更广的术语容器称呼他们。
泛型和类型安全的容器
Java SE5之前的容器允许向容器插入不正确的类型。现在,可以把ArrayList当作可以自动扩充自身尺寸的数组来看待。
public class Apple {
private static long counter;
private final long id = counter++;
public long id(){
return id;
}
}
public class Orange {
}
public class ApplesAndOrangesWithoutGenerics {
@SuppressWarnings("unchecked")
public static void main(String[] args) {
ArrayList apples = new ArrayList();
for (int i = 0; i < 3; i++){
apples.add(new Apple());
}
apples.add(new Orange());
for (int i = 0; i < apples.size(); i++) {
((Apple)apples.get(i)).id();
}
}
}
实例中没有使用泛型,正常情况下Java编译器会报告警告信息,使用@SuppressWarnings(“unchecked”)表示只有有关不受检查的异常的警告信息应该被抑制。
上例未使用泛型,在遍历Orange是会出现异常。要想定义用来保存Apple对象的ArrayList,可以声明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());
}
for (int i = 0; i < apples.size(); i++) {
System.out.println(apples.get(i).id());
}
}
}/*
0
1
2
*/
通过泛型,不仅知道编译器将会检查放置在容器中的对象类型,而且在使用容器中的对象时,可以使用更加清晰的语法。如果不需要使用每个元素的索引,可以使用foreach语法来选择List中的每个元素。
向上转型也可以作用于泛型:
public class GrannySmith extends Apple{
}
public class Gala extends Apple {
}
public class Fuji extends Apple {
}
public class Braeburn extends Apple {
}
public class GenericsAndUpcasting {
public static void main(String[] args) {
ArrayList<Apple> apples = new ArrayList<Apple>();
apples.add(new GrannySmith());
apples.add(new Gala());
apples.add(new Fuji());
apples.add(new Braeburn());
for (Apple c : apples){
System.out.println(c);
}
}
}/*
aaa@qq.com
aaa@qq.com
aaa@qq.com
aaa@qq.com
*/
程序的输出是从Object默认的toString方法产生的,该方法将打印类名,后面紧跟该对象的散列码的无符号十六进制表示(这个散列码是通过hashCode方法产生)。
基本概念
Java容器类库的用途是保存对象,并将其划分为两个不同的概念:
1.Collection,一个独立元素的序列,这些元素都服从一条或多条规则。List必须按照插入的顺序保存元素,而Set不能有重复元素。Queue按照排队规则来确定对象产生的顺序(通常与插入顺序相同)。
2。Map,一组成对的键值对对象,允许你使用键来查找值。映射表允许使用另一个对象来查找对象,被称为关联数组,或者字典。
List apples = new ArrayList();
使用接口的目的在于如果你决定修改你的实现,你所需的只是在创建处修改它,就像这样:
List apples = new LinkedList();
这种方式并非总能奏效,因为某些类具有额外的功能。LinkedList具有在List接口中额外的方法,如果你想要使用这些方法,就不能向上转型为更为通用的接口。
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.printf(i + ". ");
}
}
}/*
0. 1. 2. 3. 4. 5. 6. 7. 8. 9.
*/
add方法在文档中非常详细的叙述到,(目的是)要确保这个Collection包含指定的元素,这考虑到Set的含义,因为在Set中只有元素不存在的情况下才会被添加。任何List时只是把元素放进去,不关心是否重复。
添加一组元素
Arrays.asList()方法接受一个数组或一个用逗号分隔的元素列表,并将其转换为List对象。
Collections.addAll()方法接受一个Collection对象,以及一个数组或用逗号分割的列表,将元素添加到Collection中。
public class AddingGroups {
public static void main(String[] args) {
Collection<Integer> collection =
new ArrayList<Integer>(Arrays.asList(1,2,3,4,5));
Integer[] moreInts = {6,7,8,9,10};
collection.addAll(Arrays.asList(moreInts));
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);
for (Integer i : collection){
System.out.print(i + " ");
}
System.out.println();
for (Integer c : list){
System.out.print(c + " ");
}
}
}/*
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 6 7 8 9 10
16 99 18 19 20
*/
Collection的构造器可以接受另一个collection,用它来将自身初始化,因此可以使用Arrays.List()来为这个构造器产生输入。但是Collection.addAll方法运行起来要快得多,而且构建一个不包含元素的Collection,然后调用Collections.addAll方法很方便。
Collections.addAll只能接收另一个Collection对象作为对参数,因此它不如Arrays.asList或Collections.addAll灵活。
可以直接将Arrays.asList的输出将其当作List,但是其底层表示的是数组,因此不能调整尺寸。如果视图用add()或delete()方法就可能会引起数组尺寸的尝试。
Arrays.asList()方法限制是他对所产生的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 Powder());
List<Snow> snow3 = new ArrayList<Snow>();
// 显式类型参数说明
List<Snow> snow4 = Arrays.<Snow>asList(
new Light(), new Heavy());
}
}
当时试图创建snow2时,Arrays.asList()中只有Powder类型,因此她会创建List
容器的打印
使用Arrays.toString()来产生数组的可打印表示:
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) {
System.out.println(fill(new ArrayList<String>()));
System.out.println(fill(new LinkedList<String>()));
System.out.println(fill(new HashSet<String>()));
System.out.println(fill(new TreeSet<String>()));
System.out.println(fill(new LinkedHashSet<String>()));
System.out.println(fill(new HashMap<String,String>()));
System.out.println(fill(new TreeMap<String,String>()));
System.out.println(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的槽只能保存一个元素,Mao的槽可以保存两个对象。
Collection打印的内容用方括号括住,每个元素由逗号分隔,Map则用大括号括住,键值由等号联系,键左值右。
ArrayList与LinkedList输出上看都按照被插入的顺序保存元素。
HashSet,TreeSet,LinkedHashSet相同项只存一次。如果存储顺序很重要,那么可以使用TreeSet,它按照比较结果的升序保存对象;LinkedHashSet按照被添加顺序保存对象。HashSet比较复杂。
Map,put,get,Map不必指定尺寸,它自己会自动地调整尺寸。Map的保存顺序并不是插入顺序,HashMao会使用一种非常快的算法来控制顺序。TreeMap按照比较结果的升序保存键,而LinkedHashMap则按照插入顺序保存键,同时还保留了HashMap的查询速度。
List
List在Collection的基础上添加了大量的方法,使得List的中间插入和移除元素。
基本的ArrayList:长于随机访问元素,但是在list中间插入和移除元素时较慢
LinkedList,它通过代价较低的在List中间进行的插入和删除操作,提供了优先的顺序访问。LinkedList在随机访问方面相对比较慢,但是它的特性集较ArrayList更大。
typeinfo.pets.jar
1.Pet类,以及Pet的各种子类型
2.静态的Pets.arrayList()方法返回一个填充了随机选取的Pet对象的ArrayList:
public class ListFeatures {
public static void main(String[] args) {
Random rand = new Random(47);
List<Pet> pets = Pets.arrayList(7);
System.out.println("1: " + pets);
Hamster h = new Hamster();
pets.add(h);
System.out.println("2: " + pets);
System.out.println("3: " + pets.contains(h));
pets.remove(h);
Pet p = pets.get(2);
System.out.println("4: " + p + " " + pets.indexOf(p));
Pet cymric = new Cymric();
System.out.println("5: " + pets.indexOf(cymric));
System.out.println("6: " + pets.remove(cymric));
System.out.println("7: " + pets.remove(p));
System.out.println("8: " + pets);
pets.add(3, new Mouse());
System.out.println("9: " + pets);
List<Pet> sub = pets.subList(1, 4);
System.out.println("subList: " + sub);
System.out.println("10: " + pets.containsAll(sub));
Collections.sort(sub);
System.out.println("sorted subList: " + sub);
System.out.println("11: " + pets.containsAll(sub));
Collections.shuffle(sub,rand);
System.out.println("shuffled subList: " + sub);
System.out.println("12: " + pets.containsAll(sub));
List<Pet> copy = new ArrayList<Pet>(pets);
sub = Arrays.asList(pets.get(1), pets.get(4));
System.out.println("sub: " + sub);
copy.retainAll(sub);
System.out.println("13: " + copy);
copy = new ArrayList<Pet>(pets);
copy.remove(2);
System.out.println("14: " + copy);
copy.removeAll(sub);
System.out.println("15: " + copy);
copy.set(1, new Mouse());
System.out.println("16: " + copy);
copy.addAll(2,sub);
System.out.println("17: " + copy);
System.out.println("18: " + pets.isEmpty());
pets.clear();
System.out.println("19: " + pets);
System.out.println("20: " + pets.isEmpty());
pets.addAll(Pets.arrayList(4));
System.out.println("21: " + pets);
Object[] o = pets.toArray();
System.out.println("22: " + o[3]);
Pet[] pa = pets.toArray(new Pet[0]);
System.out.println("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
*/
List允许在被创建之后添加、移除元素或者自我调整尺寸,一个可修改的序列。输出行2中添加一个Hamster结果,即对象被追加到了表尾。
可以用contains()方法确定某个对象是否在列表中。
移除一个对象,可以将这个对象的引用传递给remove()方法。
有一个对象的引用,则可以使用indexOf()来发现该对象在List中所处位置的索引编号。
当确定一个元素是否属于某个List,发现某个元素的索引,以及从某个List中移除一个元素时,都会用到equals方法。每个Pet都被定义为唯一的对象,因此即使在列表中已经有两个Cymric,如果在创建一个新的,并把它传递给indexOf()方法,其结果仍是-1,而尝试调用remove()方法来删除这个对象,也会返回false。对于其他的类,equals()的定义有可能不同。例如,两个String只有在内容上完全一样的情况下会是等价的。因此为了防止意外,就必须意识到List的行为根据equals的行为而有所变化。
在List中间插入元素是可行的。对于LinkedList在列表中间插入和删除的廉价操作,但是对于ArrayList是高昂的操作。那就永远不能使用ArrayList插入元素了吗?不,这仅仅意味着应该意识到这个问题,如果在某个ArrayList中间执行很多插入操作,并且程序开始变慢,那就应该看看List实现有可能是罪魁祸首。优化是一个棘手的问题,最好的策略就是置之不顾,直到大仙需要担心它了。
subList()允许容易地从较大的列表中创建出一个片段,而将其结果传递给较大列表的containsAll()方法时,自然是true,顺序并不重要。subList()所产生的列表的幕后就是初始列表,因此,对所返回的列表的修改都会反映到初始列表中。
retainAll()方法是一种有效的交集操作,它保留了所有同时在copy与sub中的元素。再次注意,所产生的行为依赖于equals()方法。输出14使用索引值来移除元素的结果,与通过对象引用来移除相比,它显得更加直观,因此在使用索引值时,不必担心equals的行为。
removeAll()方法的行为也是基于equals()方法的,就像其名称所表示,将从List中移除在参数List中的所有元素。
set()方法,在指引的索引处,用于第二个参数替换整个位置的元素。
17表明对于List,有一个重载的addAll()方法使得可以在初始List的中间插入新的列表,而不仅仅只能用Collection中的addAll()方法将其追加到表尾。
toArray()方法,将任意的Collection转换为一个数组。这是一个重载方法,其无参数版本返回的是一个Object数组,但是如果想这个重载版本传递目标类型的数据,那么它将产生指定类型的数据。如果参数数组太小,存放不下List中的所有元素,toArray()方法将创建一个具有合适尺寸的数组。
迭代器
迭代器是一个对象,他的工作是遍历并选择序列中的对象,而客户端程序员不必知道或关心该序列底层的结构。迭代器通常被称为轻量级对象:创建它的代价小。
Java的Iterator只能单向移动,这个Iterator只能用来:
1.使用iterator()要求容器返回一个Iterator。Iterator将准备好返回序列的第一个元素。
2.使用next()获得序列的下一个元素
3.使用hasNext()检查序列中是否还有下一个元素
4.使用remove()将迭代器新进返回的元素删除:
public class SimpleIteration {
public static void main(String[] args) {
List<Pet> pets = Pets.arrayList(12);
Iterator<Pet> it = pets.iterator();
while (it.hasNext()){
Pet p = it.next();
System.out.print(p.id() + ":" + p + " ");
}
System.out.println();
for(Pet p : pets){
System.out.print(p.id() + ":" + p + " ");
}
System.out.println();
it = pets.iterator();
for(int i = 0; i < 6; i++){
it.next();
it.remove();
}
System.out.println(pets);
}
}/*
0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx 8:Cymric 9:Rat 10:EgyptianMau 11:Hamster
0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx 8:Cymric 9:Rat 10:EgyptianMau 11:Hamster
[Pug, Manx, Cymric, Rat, EgyptianMau, Hamster]
*/
Iterator不必为容器中元素的数量操心。
只是向前遍历List,并不打算修改List对象本身,可以看到foreach语法会显得更加简洁。
Iterator还可以移除由next()产生的最后一个元素,这意味着在调用remove()之前必须先调用next()。
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
*/
能够将遍历序列的操作与序列底层的结构分离。迭代器统一了对容器的访问方式。
ListIterator
ListIterator是一个更加强大的Iterator的子类型,它只能用于各种List类的访问。ListIterator可以双向移动。它还可以产生相对于迭代器在列表中指向的当前位置的前一个和后一个的索引,并且可以使用set()方法替换它访问过的最后一个元素。可以通过调用listIterator()方法产生一个指向List开始处的ListIterator,并且还可以通过调用listIterator(n)方法创建一个一开始指向列表索引为n的元素处的ListIterator。
public class ListIteration {
public static void main(String[] args) {
List<Pet> pets = Pets.arrayList(8);
ListIterator<Pet> it = pets.listIterator();
while (it.hasNext()){
System.out.print(it.next() + ", " + it.nextIndex() +
", " + it.previousIndex() + "; ");
}
System.out.println();
while (it.hasPrevious()){
System.out.print(it.previous().id() + " ");
}
System.out.println();
System.out.println(pets);
it = pets.listIterator(3);
while (it.hasNext()){
it.next();
// 替换在列表中所有的Pet对象
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]
*/
LinkedList
LinkedList实现了基本的List接口,但是它执行插入和移除时比ArrayList更高效,但在随机访问方面要逊色一些。
LinkedList还添加了可以使其用作栈、队列或双端队列的方法。这些方法中有些彼此之间只是名字有些差异,或者只存在些许差异,以使得这些名字在特定用法的上下文环境中更加适用。例如,getFirst()和element()完全一样,他们都返回列表的头,而并不移除它,如果List为空,则抛出NoSuchElementException。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));
System.out.println(pets);
System.out.println("pets.getFirst(): " + pets.getFirst());
System.out.println("pets.element(): " + pets.element());
System.out.println("pets.peek(): " + pets.peek());
System.out.println("pets.remove(): " + pets.remove());
System.out.println("pets.removeFirst(): " + pets.removeFirst());
System.out.println("pets.poll(): " + pets.poll());
System.out.println(pets);
pets.addFirst(new Rat());
System.out.println("After addFirst(): " + pets);
pets.offer(Pets.randomPet());
System.out.println("After offer(): " + pets);
pets.add(Pets.randomPet());
System.out.println("After add(): " + pets);
pets.addLast(new Hamster());
System.out.println("After addLast(): " + pets);
System.out.println("pets.removeLast(): " + pets.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
*/
Queue在LinkedList的基础上添加了element、offer、peek、poll、remove方法,以使其可以成为一个Queue的实现。
Stack
栈通常是指后进先出(LIFO)的容器。有时候栈也被成为叠加栈,因为最后压入栈的元素,第一个弹出栈。
LinkedList具有能够直接实现栈的所有功能的方法,因此可以直接将LinkedList作为栈使用:
public class Stack<T> {
private LinkedList<T> storage = new LinkedList<T>();
public void push(T v){
storage.addFirst(v);
}
public T peek(){
return storage.getFirst();
}
public T pop(){
return storage.removeFirst();
}
public boolean empty(){
return storage.isEmpty();
}
@Override
public String toString(){
return storage.toString();
}
}
泛型,参数化类型
public class StackTest {
public static void main(String[] args) {
Stack<String> stack = new Stack<>();
for (String s : "My dog has fleas".split(" ")){
stack.push(s);
}
while (!stack.empty()){
System.out.print(stack.pop() + " ");
}
}
}/*
fleas has dog My
*/
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<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
*/
Set
Set不保存重复元素,Set具有与Collection完全一样的接口,因此没有额外的功能。实际上Set就是Collection,只是行为不同(这就是继承与多态思想的典型因果那个:表现不同的行为)。
public class SetOfInteger {
public static void main(String[] args) {
Random rand = new Random(47);
Set<Integer> intset = new HashSet<Integer>();
for (int i = 0; i < 10000; i++){
intset.add(rand.nextInt(30));
}
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]
*/
输出的顺序没有任何规律可循,HashSet使用了散列。TreeSet将元素存储在红-黑数数据结构中,而HashSet使用的是散列函数。LinkedHashList因为查询速度的原因也使用了散列,但是看起来它使用了链表来维护元素的插入顺序。
对结果进行排序:
public class SetOfInteger {
public static void main(String[] args) {
Random rand = new Random(47);
Set<Integer> intset = new TreeSet<Integer>();
for (int i = 0; i < 10000; i++){
intset.add(rand.nextInt(30));
}
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]
*/
使用contains()测试Set的归属性:
public class SetOperations {
public static void main(String[] args) {
Set<String> set1 = new HashSet<String>();
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"));
System.out.println("N: " + set1.contains("N"));
Set<String> set2 = new HashSet<String>();
Collections.addAll(set2,"H I J K L".split(" "));
System.out.println("set2 in set1: " + set1.containsAll(set2));
set1.remove("H");
System.out.println("set1: " + set1);
System.out.println("set2 in set1: " + set1.containsAll(set2));
set1.removeAll(set2);
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
set2 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]
*/
能够产生每个元素都唯一的列表是相当有用的功能。
public class UniqueWords {
public static void main(String[] args) {
Set<String> words = new TreeSet<String>(new TextFile("src/thinking5/SetOperations.java", "\\W+"));
System.out.println(words);
}
}/*
[A, B, C, Collections, Created, D, E, F, G, H, HashSet, I, J, K, L, M, N, Set, SetOperations, Spring, String, System, X, Y, Z, add, addAll, added, args, by, class, contains, containsAll, false, from, import, in, java, main, new, on, out, package, println, public, remove, removeAll, removed, set1, set2, split, static, thinking5, to, true, util, void]
*/
“\W+”表示“一个或多个字母”。排序是按字典序进行的,因此大写和小写字母被划分到了不同的组中。如果想按照字母序排序,那么可以想TreeSet的构造器传入String.CASE_INSENTIVE_ORDER比较器:
public class UniqueWords {
public static void main(String[] args) {
Set<String> words = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
words.addAll(new TextFile("src/thinking5/SetOperations.java", "\\W+"));
System.out.println(words);
}
}/*
[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, SetOperations, split, static, String, System, thinking5, to, true, util, void, X, Y, Z]
*/
Map
将对象映射到其他对象的能力。
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++) {
int r = rand.nextInt(20);
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}
*/
在main()方法中,自动包装机制将随机生成的int转换为HashMap可以使用的Integer引用(不能使用基本类型的容器)。
使用containsKey()和containsValue()来测试一个Map:
public class PetMap {
public static void main(String[] args) {
Map<String,Pet> petMap = new HashMap<>();
petMap.put("My Cat", new Cat("Molly"));
petMap.put("My Dog", new Dog("Ginger"));
petMap.put("My Hamster", new Hamster("Bosco"));
System.out.println(petMap);
Pet dog = petMap.get("My Dog");
System.out.println(dog);
System.out.println(petMap.containsKey("My Dog"));
System.out.println(petMap.containsValue(dog));
}
}/*
{My Dog=Dog Ginger, My Cat=Cat Molly, My Hamster=Hamster Bosco}
Dog Ginger
true
true
*/
Map和数组和其他的Collection一样,很容易扩展到多维:
public class MapOfList {
public static Map<Person, List<? extends Pet>> petPeople = new HashMap<>();
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 aka 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) {
System.out.println("People: " + petPeople.keySet());
System.out.println("Pets: " + petPeople.values());
for (Person person : petPeople.keySet()){
System.out.println(person + "has: ");
for (Pet pet : petPeople.get(person)){
System.out.println(" " + pet);
}
}
}
}/*
People: [Person Marilyn, Person Dawn, Person Luke, Person Isaac, Person Kate]
Pets: [[Pug Louie aka Louis Snorkelstein Dupree, Cat Stanford aka Stinky el Negro, Cat Pinkola], [Cymric Molly, Mutt Spot], [Rat Fuzzy, Rat Fizzy], [Rat Freckly], [Cat Shackleton, Cat Elsie May, Dog Margrett]]
Person Marilynhas:
Pug Louie aka Louis Snorkelstein Dupree
Cat Stanford aka Stinky el Negro
Cat Pinkola
Person Dawnhas:
Cymric Molly
Mutt Spot
Person Lukehas:
Rat Fuzzy
Rat Fizzy
Person Isaachas:
Rat Freckly
Person Katehas:
Cat Shackleton
Cat Elsie May
Dog Margrett
*/
Map可以返回它的键的Set,它的值的Collection,或者它的键值对的Set。keySet()方法产生了由在petPeople中的所有键组成的Set,它在foreach语句中被用来迭代遍历该Map。
Queue
队列是一个典型的先进先出的容器。LinkedList提供了方法以支持队列的行为,并且它实现了Queue接口,因此LinkedList可以用作Queue的一种实现。通过将LinkedList向上转型为Queue:
public class QueueDemo {
public static void printQ(Queue queue){
while (queue.peek() != null){
System.out.print(queue.remove() + " ");
}
System.out.println();
}
public static void main(String[] args) {
Queue<Integer> queue = new LinkedList<Integer>();
Random rand = new Random(47);
for (int i = 0; i < 10; i++){
queue.offer(rand.nextInt(i + 10));
}
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()方法是与Queue相关的方法之一,将一个元素插入到队尾,或者返回false。peek(),element()都将在不移除的情况下返回队头,但是peek在队列为空时返回nul,而element会抛出NoSuchElementException异常。poll()和remove()方法将移除并返回队头,但是poll在队列为空时返回null,而remove会抛出NoSuchElementException异常。
自动封装机制会自动地将nextInt方法的int结果转换为queue所需的Integer对象,将char c转换为qc所需的Character对象。Queue接口窄化了对LinkedList的方法的访问权限,以使得只有恰当的方法才可以使用。
PriorityQueue
先进先出描述了最典型的队列规则。队列规则是指在给定一组队列中的元素的情况下,确定下一个弹出队列的元素的规则。先进先出声明的是下一个元素应该是等待时间最长的元素。
优先级队列声明下一个弹出元素是最需要的元素(具有最高的优先级)。当你在PrioriryQueue上调用offer方法来插入一个对象时,这个对象会在队列中被排序。默认排序将使用对象在队列中的自然顺序,但你可以通过自己的Comparator来修改这个顺序。
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));
}
QueueDemo.printQ(priorityQueue);
List<Integer> ints = Arrays.asList(25,22,20,18,14,9,3,1,1,2,3,9,14,18,21,23,25);
priorityQueue = new PriorityQueue<>(ints);
QueueDemo.printQ(priorityQueue);
priorityQueue = new PriorityQueue<>(ints.size(), Collections.reverseOrder());
priorityQueue.addAll(ints);
QueueDemo.printQ(priorityQueue);
String fact = "EDUCATION SHOULD ESCHEW OBFUSCATION";
List<String> strings = Arrays.asList(fact.split(""));
PriorityQueue<String> stringPQ = new PriorityQueue<>(strings);
QueueDemo.printQ(stringPQ);
stringPQ = new PriorityQueue<>(strings.size(), Collections.reverseOrder());
stringPQ.addAll(strings);
QueueDemo.printQ(stringPQ);
Set<Character> charSet = new HashSet<>();
for (char c : fact.toCharArray()) {
charSet.add(c);
}
PriorityQueue<Character> characterPQ = new PriorityQueue<>(charSet);
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
*/
重复是允许的,最小值拥有最高优先级(空格也算值,并且比字母的优先级高)。调用Collection.reverseOrder()产生的反序的Comparator。
Collection和Iterator
Collection是描述所有序列容器的共性的根接口,可能被认为是一个“附属接口”,因为要表示其他若干接口的共性而出现的接口。
使用接口描述的一个理由是它可以使我们更够创建更通用的代码。通过针对接口而非具体实现来编写代码。容器之间的所有共性都是通过迭代器达成的。但是,这两种方法绑定到了一起,因为实现Collection就意味着需要提供iterator()方法:
public class InterfaceVsIterator {
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 display(Collection<Pet> pets) {
for(Pet p : pets){
System.out.print(p.id() + ":" + p + " ");
}
System.out.println();
}
public static void main(String[] args) {
List<Pet> petList = Pets.arrayList(8);
Set<Pet> petSet = new HashSet<>(petList);
Map<String,Pet> petMap = new LinkedHashMap<>();
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
*/
两个版本的display()方法都可以使用Map或Collection的子类型来工作,而且Collection接口和Iterator都可以将display()方法与底层容器的也定实现解耦。
当要实现一个不是Collection的外部类时,由于让它去实现Collection接口可能非常困难或麻烦,,因此使用Iterator就会变得非常吸引人。例如,如果通过继承一个持有Pet对象的类来创建一个Collection的实现,那必须实现所有的Collection方法,即使在display中不必使用它们,也必须如此。尽管可以通过继承AbstractCollection很容易地实现,但你无论如何还是要强制实现iterator和size,以便提供AbstractCollection没有实现,但是AbstractCollection中的其他方法会使用到的方法:
public class CollectionSequence extends AbstractCollection<Pet> {
private Pet[] pets = Pets.createArray(8);
@Override
public int size() {
return pets.length;
}
@Override
public Iterator<Pet> iterator() {
return new Iterator<Pet>() {
private int index = 0;
@Override
public boolean hasNext() {
return index < pets.length;
}
@Override
public Pet next() {
return pets[index++];
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
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
*/
如果你的类已经继承了其他类,那么你就不能再继承AbstractCollection了。在这种情况下,要实现Collection,就必须实现该接口中的所有方法。此时,继承并提供创建迭代器的能力就会显得容易很多:
public class NonCollectionSequence extends PetSequence {
public Iterator<Pet> iterator(){
return new Iterator<Pet>() {
private int index = 0;
@Override
public boolean hasNext() {
return index < pets.length;
}
@Override
public Pet next() {
return pets[index++];
}
};
}
public static void main(String[] args) {
NonCollectionSequence nc = new NonCollectionSequence();
InterfaceVsIterator.display(nc.iterator());
}
}/*
0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx
*/
生成Iterator是将队列与消费队列的方法链接在一起耦合度最小的方式,并且与实现Collection相比,它在序列类上所施加的约束也少得多。
Foreach与迭代器
public class ForEachCollections {
public static void main(String[] args) {
Collection<String> cs = new LinkedList<>();
Collections.addAll(cs,"Take the long way home".split(" "));
for (String s : cs) {
System.out.print("'" + s + "' ");
}
}
}/*
'Take' 'the' 'long' 'way' 'home'
*/
能够与foreach一起工作是所有Collection对象的特性。之所以能够工作,是因为Java SE5引入了新的被称为Iterable的接口,该接口包括一个能够产生Iterator的iterator()的方法,并且Iterable接口被foreach用来在序列中移动。因此如果你创建了任何实现Iterable的类,都可以将它用于foreach语句:
public class IterableClass implements Iterable<String> {
protected String[] words = ("And that is how we know the Earth to be banana-shaped.").split(" ");
@Override
public Iterator<String> iterator(){
return new Iterator<String>() {
private int index = 0;
@Override
public boolean hasNext() {
return index < words.length;
}
@Override
public String next() {
return words[index++];
}
};
}
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.
*/
在Java SE5中,大量的类都是Iterable类型,主要包括所有的Collection类(但是不包括各种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,而任何自动包装也不会自动发生:
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"};
//! test(strings);
test(Arrays.asList(strings));
}
}
尝试把数组当作一个Iterable参数传递会导致失败。这说明不存在任何数组到Iterable的自动转换,必须手工执行这种转换。
适配器方法惯用法
如果现有一个Iterable类,想要添加一种或多种在foreach语句中使用这个类的方法。假设希望可以选择以向前的方向或向后的方向迭代一个单词列表。如果直接继承这个类,并覆盖iterator()方法,你只能替换现有方法,而不能实现选择。一种解决方案是所谓适配器方法的惯用法。在默认的向前迭代器的基础上,添加产生反向迭代器的能力,因此不能使用覆盖,而是添加一个能够产生Iterable对象的方法,该对象可以用于foreach语句:
public class ReversibleArrayList<T> extends ArrayList<T> {
public ReversibleArrayList(Collection<T> c) {
super(c);
}
public Iterable<T> reversed() {
return new Iterable<T>() {
@Override
public Iterator<T> iterator() {
return new Iterator<T>() {
int current = size() - 1;
@Override
public boolean hasNext() {
return current > -1;
}
@Override
public T next() {
return get(current--);
}
};
}
};
}
}
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();
// 通过reversed()方法,产生不同的效果
for (String s : ral.reversed()){
System.out.print(s + " ");
}
}
}/*
To be or not to be
be to not or be To
*/
在IterableClass实例中添加两种适配器方法:
public class MultiIterableClass extends IterableClass {
public Iterable<String> reversed(){
return new Iterable<String>() {
@Override
public Iterator<String> iterator() {
return new Iterator<String>() {
int current = words.length - 1;
@Override
public boolean hasNext() {
return current > -1;
}
@Override
public String next() {
return words[current--];
}
};
}
};
}
public Iterable<String> randmized(){
return new Iterable<String>() {
@Override
public 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.randmized()){
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.
*/
从输出上看到,Collection.shuffle()方法没有影响到原来的数组,而只是打乱了shuffled中的引用。之所以这样,只是因为randomized()方法用一个ArrayList将Array.asList()方法的结果包装了起来。如果这个由Arrays.asList()方法产生的List被直接打乱,那么他就会修改底层的数组:
public class ModifyingArrayAsList {
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<>(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(list2, 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: [9, 1, 6, 3, 7, 2, 5, 10, 4, 8]
Array: [9, 1, 6, 3, 7, 2, 5, 10, 4, 8]
*/
在第一种情况中,Arrays.asList()的输出被传递给了ArrayList()的构造器,这将创建一个引用ia的元素的ArrayList。因此打乱这些引用不会修改数组。但是,如果直接使用Arrays.asList(ia)的结果,这种打乱就会修改ia的顺序。
意识到Arrays.asList()产生的List对象会使用底层数组做为其物理实现是很重要的。只要你执行的操作会修改这个list,并且不想原来的数组被修改,就应该在另一个容器中创建一个副本。