《Thanking in Java》11. 持有对象

2017-09-14  本文已影响37人  Lemon_Home

java中提供了一套相当完整的集合类,其中基本的类型是List,Set,Queue和Map。

11.1 泛型和类型安全的容器

ArrayList<Apple>,不仅仅只是ArrayList,其中尖括号括起来的是类型参数(可以有多个),它指定了这个容器实例可以保存的类型。通过使用泛型,就可以在编译器防止将错误类型的对象放置到容器中。向上转型也可以像作用于其他类型一样作用于泛型。

11.3 添加一组元素

Collection.addAll()
Collections.addAll()
两者的区别:前者只能接受另一个Collection对象作为参数,后者是可变参数,且第一个参数是被添加的Collection对象。

Arrays.asList,其底层表示的是数组,因此不能调整尺寸。

//: holding/AsListInference.java
// Arrays.asList() makes its best guess about type.
import java.util.*;

class Snow {}
class Powder extends Snow {}
class Light extends Powder {}
class Heavy extends Powder {}
class Crusty extends Snow {}
class Slush extends Snow {}

public class AsListInference {
  public static void main(String[] args) {
    List<Snow> snow1 = Arrays.asList(
      new Crusty(), new Slush(), new Powder());

    // Won't compile:
    // List<Snow> snow2 = Arrays.asList(
    //   new Light(), new Heavy());
    // Compiler says:
    // found   : java.util.List<Powder>
    // required: java.util.List<Snow>

    // Collections.addAll() doesn't get confused:
    List<Snow> snow3 = new ArrayList<Snow>();
    Collections.addAll(snow3, new Light(), new Heavy());

    // Give a hint using an
    // explicit type argument specification:
    List<Snow> snow4 = Arrays.<Snow>asList(
       new Light(), new Heavy());
  }
} ///:~

Arrays.asList()只有Powder类型,因此它会创建List<Powder>而不是List<Snow>。
Collections.addAll工作的很好,因为它从第一个参数中了解到了目标类型是什么。
Arrays.<Snow>asList告诉了编译器实际的目标类型应该是什么。这称为显示类型参数说明。

TreeSet, TreeMap,按照升序保存。
LinkedHashSet, LinkedHashMap,按照插入顺序保存,同时保留了查询速度。

11.5 List

有两种list:
基本的ArrayList,优点是随机访问元素,但是在List的中间插入和删除元素时较慢;
LinkedList,通过代价较低的在List中间进行的插入和删除操作,提供了优化的顺序访问,LinkedList在随机访问方面相对比较慢。

Collections类的shuffle()方法的作用是将List中的内容随机打乱顺序。

11.6 迭代器

迭代器不必知道序列底层的机构,通常被称为轻量级对象,创建它的代价小。迭代器统一了对容器的访问方式。

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

//: holding/ListIteration.java
import typeinfo.pets.*;
import java.util.*;

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();
    // Backwards:
    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();
      it.set(Pets.randomPet());
    }
    System.out.println(pets);
  }
} /* Output:
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]
*///:~

ListIterator可以双向移动,获取当前位置的前后元素索引,set()方法替换它访问过的最后一个元素,通过调用ListIterator(n)方法创建一个一开始就指向列表索引为n的元素处得ListIterator。

11.7 LinkedList

//: holding/LinkedListFeatures.java
import typeinfo.pets.*;
import java.util.*;
import static net.mindview.util.Print.*;

public class LinkedListFeatures {
  public static void main(String[] args) {
    LinkedList<Pet> pets =
      new LinkedList<Pet>(Pets.arrayList(5));
    print(pets);
    // Identical:
    print("pets.getFirst(): " + pets.getFirst());
    print("pets.element(): " + pets.element());
    // Only differs in empty-list behavior:
    print("pets.peek(): " + pets.peek());
    // Identical; remove and return the first element:
    print("pets.remove(): " + pets.remove());
    print("pets.removeFirst(): " + pets.removeFirst());
    // Only differs in empty-list behavior:
    print("pets.poll(): " + pets.poll());
    print(pets);
    pets.addFirst(new Rat());
    print("After addFirst(): " + pets);
    pets.offer(Pets.randomPet());
    print("After offer(): " + pets);
    pets.add(Pets.randomPet());
    print("After add(): " + pets);
    pets.addLast(new Hamster());
    print("After addLast(): " + pets);
    print("pets.removeLast(): " + pets.removeLast());
  }
} /* Output:
[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.9 Set

查找成为了Set中最重要的操作,通常会选择一个HashSet的实现,它专门对快速查找进行了优化。

在TreeSet的构造器里可以传入比较器。

11.10 Map

使用containsKeys()和containsValue()来测试一个Map,以便查看它是否包含某个键某个值。

Map的值可以是其他容器

entrySet()返回一个实现Map.Entry接口的对象集合。Entry是Map中用来保存一个键值对的,而Map实际上就是多个Entry的集合。

11.11 Queue

LinkedList提供了方法以支持队列的行为,并且它实现了Queue接口,因此LinkedList可以用作Queue的一种实现。通过将LinkedList向上转型为Queue。

offer方法将一个元素插入到队尾,或者返回false。peek和element都是不移除的情况下返回队头,peek在队空时返回null。poll和remove方法将移除并返回队头,poll在空的时候返回null。

自动装箱机制可以将int转换成queue所需的Interger对象。

优先级队列声明下一个弹出元素时最需要的元素(具有最高的优先级)。

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

//: holding/PriorityQueueDemo.java
import java.util.*;

public class PriorityQueueDemo {
  public static void main(String[] args) {
    PriorityQueue<Integer> priorityQueue =
      new PriorityQueue<Integer>();
    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<Integer>(ints);
    QueueDemo.printQ(priorityQueue);
    priorityQueue = new PriorityQueue<Integer>(
        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<String>(strings);
    QueueDemo.printQ(stringPQ);
    stringPQ = new PriorityQueue<String>(
      strings.size(), Collections.reverseOrder());
    stringPQ.addAll(strings);
    QueueDemo.printQ(stringPQ);

    Set<Character> charSet = new HashSet<Character>();
    for(char c : fact.toCharArray())
      charSet.add(c); // Autoboxing
    PriorityQueue<Character> characterPQ =
      new PriorityQueue<Character>(charSet);
    QueueDemo.printQ(characterPQ);
  }
} /* Output:
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
*///:~

最小值拥有最高的优先级,空格比子目的优先级更高。可以使用Comparator对象改变排序

11.12 Collection和Iterator

Collection是描述所有序列容器的共性的根接口,通过针对接口而非具体实现来编写代码,我们的代码可以应用于更多的对象类型,与底层容器的特定实现解耦。

一共有三种方式实现可迭代:

  1. 继承AbstractCollection;
  2. 实现Collection;
  3. 继承并提供创建迭代器,生成Iterator。将队列与消费队列的方法连接在一起耦合度最小,并且与实现Collection相比,它在序列类上所施加的约束也少得多。

11.13 Foreach与迭代器

如果创建了任何实现Iterable的类,都可以将它用于foreach语句中。主要包括所有的Collection类,但是不包括各种Map。

foreach语句可以用于数组,这并不意味着数组肯定也是一个Iterable,不存在任何从数组到Iterable的自动转换,必须手动执行这种转换,Arrays.asList()。

Arrays.asList()和构造方法区别:
Arrays.asList()的输出传递给了ArrayList的构造器,这将创建一个引用对象,因此打乱这些引用不会修改该数组;但是如果直接使用Arrays.asList(),产生的List对象会使用底层数组作为其物理实现,只要修改这个List,底层的数组也会随之修改。

容器不能持有基本类型,但是自动包装机制会仔细地执行基本类型到容器中所持有的包装器类型之间的双向转换。

Queue和Stack的行为,有LinkedList提供支持。

上一篇下一篇

猜你喜欢

热点阅读