持有对象2(thinking in Java)

List

List承诺可以将元素维护在特定的序列中。List接口在Collection的基础上添加了大量的方法,使得可以在List中间插入和移出元素。
有两种类型的List:

  • 基本的ArrayList,它善于随机访问元素,但是在List的中间插入和移出元素时比较慢。
  • LinkedList,它通过较低的代价在List中间进行插入和删除操作,提供了优化的顺序访问。LinkedList在随机访问方面比较慢。

迭代器

任何容器类,都必须有某种方式可以插入元素并将它们再次取回。比较,持有事物时容器最基本的工作。对于List,add()时插入元素的方法之一,而get()时取出元素的方法之一。
如果从更高层的角度出发,会发现这里有个缺点:要使用容器,必须对容器的确切类型编程。如果原本是对着List编码的,但是后来发现如果把相同的代码应用于Set,将会显得非常方便,此时应该怎么做?或者打算从头开始编写通用的代码,它们只是使用容器,不知道或者说不关心容器的类型,那么如何才能不重写代码就可以应用于不同类型的容器?
迭代器的概念可以用于达成此目的。迭代器是一个对象,它的工作是遍历并选择序列中的对象,而客户端程序员不必知道或关心该序列底层的结构。此外,迭代器通常被称为轻量级对象:创建它的代价小。因此,经常可以看到对迭代器有奇怪的限制。

LinkedList

LinkedList也像ArrayList一样实现了基本的List接口,但是它执行某些操作(在List的中间插入和移除)时比ArrayList更高效,随机访问操作访问却要逊色一些。
LinkedList还添加了可以使其用作栈、队列或双端队列的方法。

11.8 Stack

“栈”通常是指“后进先出”的容器。有时栈也被称为叠加栈,因为最后“压入”栈的元素,第一个“弹出”栈。通常用来类比栈的事物是装有弹簧的储放器中的自助餐托盘,最后装入的托盘总是最先拿出来使用的。
LinkedList具有能够直接实现栈的所有功能的方法,因此可以直接将LinkedList作为栈使用,不过,有时候一个真正的“栈”更能把事情讲清楚:

import java.util.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();
    }
    public String toString(){
        return storage.toString();
    }
}

这里通过使用泛型,引入了在栈的类定义中最简单可行示例。类名之后的告诉编译器这将是一个参数化类型,而其中的类型参数,即在类被使用时将会被实际类型替换的参数,就是T。大体上,这个类在声明**“我们在定义一个可以持有T类型对象的Stack”** Stack是用LinkedList实现的,而LinkedList也被告知它将持有T类型对象。注意,push()接受的是T类型对象,而peek() and pop()将T类型的对象。peek()方法将提供栈顶元素,但是并不将其从栈顶移出。而pop()方法将移出并返回栈顶元素。
下面演示这个新的 Stack类

public class StackTest {
    public static void main(String[] args) {
        Stack<String> stack = new Stack<String>();
        for(String s : "My dog has nothing".split(" ")){
            stack.push(s);
        }
        while (!stack.empty()){
            System.out.println(stack.pop() + " ");
        }
    }
}

nothing
has
dog
My

先进后出。

如果你想在自己的代码中使用这个Stack类,当你在创建实例时,就需要完整指定包名,或者更改这个类的名称。

Set

set不保存重复的元素。如果你试图将相同对象的多个实例添加到Set中,那么他就会阻止这种重复现象。Set中最常被使用的是测试归属性,你可以很容易地询问某个对象是否在某个Set中。正因如此,查找就成了Set中最重要的操作,因此你通常都会选择一个HashSet的实现,它专门对快速查找进行了优化。

Set具有与Collection完全一样的接口,因此没有任何额外的功能,不像前面有两个不同的List。实际上Set就是Collection,只是行为不同。Set是基于对象的值来确定归属性的。
下面是使用存放Integer对象的HashSet的实例:

import java.util.HashSet;
import java.util.Random;
import java.util.Set;

public class SetOfInteger {
    public static void main(String[] args) {
        Random random = new Random(47);
        Set<Integer> integers = new HashSet<Integer>();
        for (int i = 0; i < 10000; i++) {
            integers.add(random.nextInt(30));
        }
        System.out.println(integers);
    }
}

[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]

0~29之间的10000个随机数被添加到了Set中,每一个数都重复了许多次,但是,每一个数都只有一个实例出现在结果

你将会执行的最常见的操作之一,就是用contains()测试Set的归属性,

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

public class SetOperation {
    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(set1);
        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 remove from set1 : " + set1 );

        Collections.addAll(set1,"X Y Z".split(" "));
        System.out.println("'X Y Z'added to set1 : " + set1 );
    }
}

[A, B, C, D, E, F, G, H, I, J, K, L, M]
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 remove 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]

Map

将对象映射到其他对象的能力是一种解决编程问题的杀手锏。例如,考虑一个程序,它将用来检查Java的Random类的随机性。理想状态下,Random可以将产生理想的数字分布,但要想测试它,则需要生成大量的随机数,并对落入各种不同范围的数字进行计数。Map可以很容易地解决该问题。在本例中,键是由Random产生的数字,而值是该数字出现的次数

import java.util.HashMap;
import java.util.Map;
import java.util.Random;

public class TestMap {
    public static void main(String[] args) {
        Random random = new Random(47);
        Map<Integer,Integer> m = new HashMap<Integer, Integer>();
        for (int i = 0; i < 10000; i++) {
            int r = random.nextInt(20);
            Integer freq = m.get(r);
            m.put(r,freq == null? 1 : freq+1);
        }
        System.out.println(m);
    }
}

在main()中,自动包装机制将随机生成int转换为HashMap可以使用的Integer引用。如果键不再容器中,get()方法将返回null(这表示该数字第一次被找到了),否则,get()方法将产生与该键值相关联的Integer值,然后这个值被递增(自动包装机制再次简化了表达式,但是确实发生了对Integer的包装和拆包)

Queue

队列是一个典型的先进先出容器。从容器的一端放入事物,从另一端取出,并且事物放入容器的顺序与取出的顺序是相同。队列常被当作一种可靠的将对象从程序的某个区域传输到另一个区域的途径。队列在并发编程中特别重要。
LinkedList提供了方法以支持队列的行为,并且它实现了Queue接口,因此LinkedList可以用作Queue的一种实现。通过将LinkedList向上转型为Queue,下面的示例使用了在Queue接口中的相关方法:

import java.util.LinkedList;
import java.util.Queue;
import java.util.Random;

public class QueueDemon {
    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 random = new Random(47);
        for (int i = 0; i < 10; i++) {
            queue.offer(random.nextInt(i + 10));
        }
        printQ(queue);

        Queue<Character> qc = new LinkedList<Character>();
        for(char c : "sing dance rap basketball".toCharArray()){
            qc.offer(c);
        }
        System.out.println(qc);
    }
}

offer()方法是与Queue相关的方法之一,它在允许的情况下,将一个元素插入到队尾,或者返回false。peek()和element()都将在不移除的情况下返回队头,但是peek()方法在队列为空时返回null,而element()会抛异常。poll() and remove()方法将移除并返回队头,但是poll()在队列为空时返回null,而remove()会抛异常。

自动包装机制会自动将nextInt()方法的int结果转换为queue所需的Integer对象,将char c 转换为qc所需的Character 对象。
Queue接口窄化了对LinkedList的方法的访问权限,以使得只有恰当的方法才可以使用。

到目前为止,foreach语法主要用于数组,但是它也可以应用于任何Collection对象。你实际上已经看到过很多使用ArrayList时用到它的示例,下面是一个更通用的证明


import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;

public class ForeachCollection  {
    public static void main(String[] args) {
        Collection<String> cs = new LinkedList<String>();
        Collections.addAll(cs,"sing dance rap basketball ".split(" "));
        for(String s : cs){
            System.out.println("'" + s + "'");
        }
    }
}

‘sing’
‘dance’
‘rap’
‘basketball’

由于cs是一个Collection,所以这段代码展示了能够与foreach一起工作是所有Collection对象的特性。

之所以能够工作,因为JavaSE5引入了新的被称为Iterable的接口,该接口能够产生iterator()方法。并且Iterable接口被foreach用来在序列中移动,如果你创建了任何实现Iterable的类,都可以用于foreach语句中

import java.util.Iterator;

public class IterableClass implements Iterable<String> {
    protected String[] words = ("cai xu kun" + "sing dance basketball rap.").split(" ");

    @Override
    public Iterator<String> iterator() {
        return new Iterator<String>(){
            private int index = 0;
            public boolean hasNext(){
                return index < words.length;
            }
            public String next(){
                return words[index++];
            }
            public void remove(){
                throw new UnsupportedOperationException();
            }
        };
    }

    public static void main(String[] args) {
        for(String s : new IterableClass()){
            System.out.println(s);
        }
    }
}

结果

cai
xu
kunsing
dance
basketball
rap.

总结
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保持元素处于排序状态。

看这么一张图

在这里插入图片描述

你可以看到,其实只有四种容器:Map、List、Set和Queue,它们各有两到三个实现版本。常用的容器用黑色粗线表示

点线框表示接口,实线框表示普通的类,带有空心箭头的点线表示一个特定的类实现了一个接口,实心箭头表示某个类可以生成箭头所指向类的对象。例如,任意的Collection可以生成Iterator,而List可以生成ListIterator

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值