《Thinking in Java》学习——17章容器深入研究(一)

.#完整的容器分类法

1.
Java集合类库完备图

填充容器

1.Collections类也有一些实用的用于填充的方法,其中包括fill()。与Arrays的一样,也只是复制同一个对象引用来填充整个容器,并且只对List有用。

一.使用Abstract类

1.对于产生用于容器的测试数据问题,有一种方式是创建定制的CollectionMap实现。每个java.util容器都有自己的Abstract类,它们提供了该容器的部分实现,因此你必须做的只是去实现那些产生想要容器所必需的方法。

Collection的功能方法

1.下面是可以通过Collection执行的所有操作:

Method Description
boolean add(T) 确保容器持有具有泛型类型T的参数。如果没有将此参数添加进容器,则返回false
boolean addAll(Co�llection<? extends T>) 添加参数中的所有元素。只要添加了任意元素就返回true
void clear() 移除容器中的所有元素
boolean contains(T) 如果容器已持有具有泛型类型T的词参数,则返回true
boolean containsAll(Collection<?>) 如果容器持有此参数中的所有元素,则返回true
boolean isEmpty() 容器中没有元素时返回true
Iterator<T> iterator() 返回一个Iterator<T>,可以用来遍历容器中的元素
boolean remove(Object) 如果参数在容器中,则移除此元素的一个实例。如果做了移除动作,则返回true
boolean removeAll(Collection<?>) 移除参数中的所有元素。只要有移除动作发生就返回true
boolean retainAll(Collection<?>) 只保存参数中的元素(即“交集”)。只要Collection发生改变就返回true
int size() 返回容器中的元素的数目
Object[] toArray() 返回一个数组,该数组包含容器中的所有元素
<T> T[] toArray(T[] a) 返回一个数组,该数组包含容器中的所有元素。返回结果的运行时类型与参数数组a的类型相同,而不是单纯的Object

2.由于Collection包括Set,因此,如果想检查Collection中的元素,那就必须使用迭代器。

可选操作

1.执行各种不同的添加和移除的方法在Collection接口中都是可选操作。这意味着实现类并不需要为这些方法提供功能定义。
2.将方法定义为可选是因为这样做可以防止在设计中出现接口爆炸的情况。
3.“未获支持的操作”这种方式可以实现Java容器类库的一个重要目标:容器应该易学易用。未获支持的操作是一种特例,可以延迟到需要时再实现。但是为了让这种方式工作:
(1)UnsupportedOperationException必须是一种罕见事件。即,对大多数类来说,所有操作都应该可以工作,只有在特例中才会有未获支持操作。这种设计意味着,在Java容器类库中,如果想要创建新的Collection,但是没有为Collection接口中的所有方法都提供有意义的定义,那么它仍旧适合现有的类库。
(2)如果一个操作是未支持的,那么在实现接口的时候就会导致UnsupportedOperationException异常。

一.未获支持操作

1.最常见的未获支持操作,都来源于背后由固定尺寸的数据结构支持的容器。

public class Unsupported {
    public static void test(String msg, List<String> list) {
        Collection<String> c = list;
        Collection<String> subList = list.subList(1, 8);
        Collection<String> c2 = new ArrayList<String>(subList);
        System.out.println("---" + msg + "---");
        try {
            c.retainAll(c2);
        } catch (Exception e) {
            System.out.println("retainAll():" + e);
        }
        try {
            c.removeAll(c2);
        } catch (Exception e) {
            System.out.println("removeAll():" + e);
        }
        try {
            c.clear();
        } catch (Exception e) {
            System.out.println("clear():" + e);
        }
        try {
            c.add("X");
        } catch (Exception e) {
            System.out.println("add():" + e);
        }
        try {
            c.addAll(c2);
        } catch (Exception e) {
            System.out.println("addAll():" + e);
        }
        try {
            c.remove("C");
        } catch (Exception e) {
            System.out.println("remove():" + e);
        }
        try {
            list.set(0, "X");
        } catch (Exception e) {
            System.out.println("List.set():" + e);
        }
    }
    
    public static void main(String...args) {
        List<String> list = Arrays.asList(("A B C D E F").split(" "));
        test("Arrays.asList()", list);
        test("unmodifiableList()", Collection.unmodifiableList(new ArrayList(list)));
    }
}
/* Output:
---Arrays.asList()---
retainAll():java.lang.UnsupportedOperationException
removeAll():java.lang.UnsupportedOperationException
clear():java.lang.UnsupportedOperationException
add():java.lang.UnsupportedOperationException
addAll():java.lang.UnsupportedOperationException
remove():java.lang.UnsupportedOperationException
---unmodifiableList()---
retainAll():java.lang.UnsupportedOperationException
removeAll():java.lang.UnsupportedOperationException
clear():java.lang.UnsupportedOperationException
add():java.lang.UnsupportedOperationException
addAll():java.lang.UnsupportedOperationException
remove():java.lang.UnsupportedOperationException
List.set():java.lang.UnsupportedOperationException
*/

上面的代码中,Arrays.asList()会生成一个List,它基于一个固定大小的数组,仅支持那些不会改变数组大小的操作,因此除了set()方法,其它方法都会抛出异常。Collections.unmodifiableList()产生的是一个不可修改的列表,因此任意方法都不能对它产生修改。

List的功能方法

1.基本的List很容易使用:大多数的时候只是调用add()添加对象,使用get()一次取出一个元素,以及调用iterator()获取用于该序列的Iterator。

Set和存储顺序

1.在Java中有诸如Integer和String这样的预定义类型,这些类型被定义为可依在容器内部使用。当创建自己的类型时,要意识到Set需要一种方式来维护存储顺序,而存储顺序如何维护,则是在Set的不同实现之间会有所变化。
2.不同Set实现不仅具有不同当行为,而且它们对于放置在特定的Set中的元素类型也有不同的要求:

Set类型 要求
Set 存入Set的每个元素都必须是唯一的,因为Set不重复保存元素。加入Set的元素必须定义equals()方法以确保对象的唯一性。Set与Collection有完全一样的接口。Set接口不保证维护元素的次序
HashSet 为快速查找而设计的Set。存入HashSet的元素必须定义HashCode()
TreeSet 保持次序的Set,底层为树结构。使用它可以从Set中提取有序的序列。元素必须事项Comparable接口
LinkedHashSet 具有HashSet的查询速度,且内部使用链表维护元素的顺序(插入的顺序)。于是在使用迭代器遍历Set时,结果会按元素插入的次序显示,元素也必须定义hashCode()方法

3.如果没有其他限制,HashSet就应该是你默认的选择,因为它对速度进行了优化。
4.通常你必须为散列存储和树型存储都创建一个equals()方法,但是hashCode()只有在这个类将会被置于HashSet或者LinkedHashSet中市=时才是必需的。你应该在覆盖equals()方法的同时覆盖hashCode()方法,定义hashCode()的机制将会在后面介绍:

class SetType {
    int i;
    public SetType(int n) {
        i = n;
    }
    public boolean equals(Object o) {
        return o instanceof SetType && (i == (SetType) o).i;
    }
    public String toString() {
        return Integer.toString(i);
    }
}

class HashType extends SetType {
    public HashType(int n) {
        super(n);
    }
    public int hashCode() {
        return i;
    }
}

class TreeType extends SetType implements Comparable<TreeType> {
    public TreeType(int n) {
        super(n);
    }
    public compareTo(TreeType arg) {
        return (arg.i < i ? -1 : (arg.i == i ? 0 : 1));
    }
}

上面的代码展示了编写存储类型的最基本的方式,HashType放置于HashSet中,TreeType放置于TreeSet中。
5.如果我们尝试着将没有恰当支持必需操作的类型用于需要这些方法的Set,并不会出现任何问题,甚至不会出现运行时异常,那是因为hashCode()equals()的默认实现是合法的,即便它不正确。但是如果这样就将它们放置到任何散列实现中都会实现重复,这样就违反了Set的基本契约。

一.SortedSet

1.SortedSet中的元素可以保证处于排序状态,也就赋予了它接口外的其他功能:

Method 功能描述
Comparator comparator() 返回当前Set使用的Comparator;或者返回null,表示以自然的方式排序
Object first() 返回容器中第一个元素
Object last() 返回容器中最后一个元素
SortedSet subSet(fromElement, toElement) 生成此Set的子集,范围从fromElement(包含)到toElement(不包含)
SortedSet headSet(toElement) 生成此Set的子集,由小于toElement的元素组成
SortedSet tailSet(fromElement) 生成此Set的子集,由大于或等于fromElement的元素组成

队列

1.除了并发应用,Queue在Java SE5中仅有的两个实现是LinkedListPriorityQueue,它们的差异在于排序行为而不是性能:

class QueueBehavior {
    private static int count = 10;
    static <T> void test(Queue<T> queue, Generator<T> gen) {
        for (int i = 0; i < count; i++) 
            queue.offer(gen.next());
        while (queue.peek() != null) 
            System.out.print(queue.remove() + " ");
        System.out.println();
    }
    static class Gen implements Generator<String> {
        String[] s = ("one two three four five six seven eight nine ten").split(" ");
        int i;
        public String next() {
            return s[i++];
        }
    }
    public static void main(String... args) {
        test(new LinkedList<String>(), new Gen());
        test(new PriorityQueue<String>(), new Gen());
        test(new ArrayBlockingQueue<String>(count), new Gen());
        test(new ConcurrentLinkedList<String>(), new Gen());
        test(new LinkedBlockingQueue<String>(), new Gen());
        test(new PriorityBlockingQueue<String>(), new Gen());
    } 
}
/*
Output:
one two three four five six seven eight nine ten
eight five four nine one seven six ten three two
one two three four five six seven eight nine ten
one two three four five six seven eight nine ten
one two three four five six seven eight nine ten
eight five four nine one seven six ten three two
*/
一.优先级队列

1.优先级队列声明下一个弹出的元素是最需要的元素(具有最高级的优先级)。
2.当你在PriorityQueue上调用offer()方法来插入一个对象时,这个对象会在队列中被排序。默认的排序将使用对象在队列中的自然顺序,但是可以容果提供自己的Comparator来修改这个顺序。
3.在这里,重复时允许的,最小的值拥有组最高的优先级。

二.双向队列

1.双向队列就像是一个队列,但是你可以在任何一段添加或移除元素。
2.Java标准类库中没有人和显示的用于双向队列的接口。但是,可以使用组合来创建一个Deque类,并直接从LinkedList中暴露相关方法:

public class Deque<T> {
    private LinkedList<T> deque = new LinkedList<T>;
    public void addFirst(T e) { deque.addFirst(e); }
    public void addLast(T e) { deque.addLast(e); }
    public T getFirst() { return deque.getFirst(); }
    public T getLast() { return deque.getLast(); }
    public T removeFirst() { return deque.removeFirst(); }
    public T removeLast() { return deque.removeLast(); }
    public int size() { rerturn deque.size(); }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容