第17章 容器深入研究
17.2 填充容器

package com.cy.container;

import java.util.*;

class StringAddress {
private String s; public StringAddress(String s) {
this.s = s;
}
public String toString() {
return super.toString() + " " + s;
}
} public class FillingLists {
public static void main(String[] args) {
List<StringAddress> list= new ArrayList<StringAddress>(Collections.nCopies(4, new StringAddress("Hello")));
System.out.println(list); Collections.fill(list, new StringAddress("World!"));
System.out.println(list);
}
} /* Output: (Sample)
[StringAddress@82ba41 Hello, StringAddress@82ba41 Hello, StringAddress@82ba41 Hello, StringAddress@82ba41 Hello]
[StringAddress@923e30 World!, StringAddress@923e30 World!, StringAddress@923e30 World!, StringAddress@923e30 World!]
*///:~
这个示例展示了两种用对单个对象的引用来填充Collection的方式,第一种是使用
Collections.nCopies()创建传递给构造器的List,这里填充的是ArrayList.
StringAddress的toString()方法调用Object.toString()并产生该类的名字,后面紧跟该对象的
散列码的无符号十六进制表示.(通过hashCode()生成的)。从输出中你可以看到所有引用都被设
置为指向相同的对象,在第二种方法的Collection.fill()被调用之后也是如此。fill()方法的用处更
有限,因为它只能替换已经在List中存在的元素,而不能添加新的元素。
 
17.4.1 未获支持的操作
最常见的未获支持的操作,都来源于背后由固定尺寸的数据结构支持的容器。当你用
Arrays.asList()将数组转换为List时,就会得到这样的容器。你还可以通过使用Collections类中
"不可修改"的方法,选择创建任何会抛出UnsupportedOperationException的容器(包括Map ) 。
下面的示例包括这两种情况:
package com.cy.container;

import java.util.*;

public class Unsupported {
static void test(String msg, List<String> list) {
System.out.println("--- " + msg + " ---");
Collection<String> c = list;
Collection<String> subList = list.subList(1,8);
// Copy of the sublist:
Collection<String> c2 = new ArrayList<String>(subList);
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);
}
// The List.set() method modifies the value but
// doesn't change the size of the data structure:
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 G H I J K L".split(" "));
test("Modifiable Copy", new ArrayList<String>(list));
test("Arrays.asList()", list);
test("unmodifiableList()", Collections.unmodifiableList(new ArrayList<String>(list)));
}
} /* Output:
--- Modifiable Copy ---
--- 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,它基于一个固定大小的数组, 仅支持那些不会改变数组
大小的操作,对它而言是有道理的。任何会引起对底层数据结构的尺寸进行修改的方法都会产生
一个UnsupportedOperationException异常,以表示对未获支持操作的调用(一个编程错误).
注意,应该把Arrays.asList()的结果作为构造器的参数传递给任何Collection (或者使用
addAll()方法,或Collections.addAll()静态方法) ,这样可以生成允许使用所有的方法的普通容
器一一这在main()中的第一个对test()的调用中得到了展示, 这样的调用会产生新的尺寸可调的
底层数据结构。Collections类中的"不可修改"的方法将容器包装到了一个代理中,只要你执
行任何试图修改容器的操作,这个代理都会产生UnsupportedOperationException异常。使用这
些方怯的目标就是产生"常量"容器对象。"不可修改"的Collections方法的完整列表将在稍后
介绍。
test()中的最后一个try语句块将检查作为List的一部分的set()方法.这很有趣,因为你可以
看到"未获支持的操作"这一技术的粒度来的是多么方便-一所产生的"接口"可以在
Arrays.asList()返回的对象和Collections.unmodifiableList()返回的对象之间,在一个方法的粒
度上产生变化。Arrays.asList()返回固定尺寸的List,而Collections.unmodifiableList()产生不可
修改的列表.正如从输出中所看到的.修改Arrays.asList()返回的List中的元素是可以的,因为
这没有违反该List“尺寸固定"这一特性.但是很明显, unmodlfiableList()的结果在任何情况
下都应该不是可修改的。如果使用的是接口,那么还需要两个附加的接口,一个具有可以工作
的set()方法,另外一个没有, 因为附加的接口对于Collection的各种不可修改的子类型来说是必
需的。
 
 

17.6 Set和存储顺序
在HashSet上打星号表示,如果没有其他的限制,这就应该是你默认的选择,因为它对速度
进行了优化。
定义hashCode()的机制将在本章稍后进行介绍。你必须为散列存储和树型存储都创建一个
equals()方法,但是hashCode()只有在这个类将会被置于HashSet (这是有可能的,因为它通常
是你的Set实现的首选)或者LinkedHashSet中时才是必需的。但是,对于良好的编程风格而言,
你应该在覆盖equals()方法时,总是同时覆盖hashCode()方法。
package com.cy.container;

import java.util.*;

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 this.i;
}
} class TreeType extends SetType implements Comparable<TreeType> {
public TreeType(int n) { super(n); } @Override
public int compareTo(TreeType arg) {
return (arg.i < i ? -1 : (arg.i == i ? 0 : 1));
}
} public class TypesForSets {
static <T> Set<T> fill(Set<T> set, Class<T> type) {
try {
for(int i = 0; i < 10; i++){
T t = type.getConstructor(int.class).newInstance(i);
set.add(t);
}
} catch(Exception e) {
throw new RuntimeException(e);
}
return set;
}
static <T> void test(Set<T> set, Class<T> type) {
fill(set, type);
fill(set, type); // Try to add duplicates
fill(set, type);
System.out.println(set);
}
public static void main(String[] args) {
test(new HashSet<HashType>(), HashType.class);
test(new LinkedHashSet<HashType>(), HashType.class);
test(new TreeSet<TreeType>(), TreeType.class); // Things that don't work:
test(new HashSet<SetType>(), SetType.class);
test(new HashSet<TreeType>(), TreeType.class);
test(new LinkedHashSet<SetType>(), SetType.class);
test(new LinkedHashSet<TreeType>(), TreeType.class); try {
test(new TreeSet<SetType>(), SetType.class);
} catch(Exception e) {
System.out.println(e.getMessage());
}
try {
test(new TreeSet<HashType>(), HashType.class);
} catch(Exception e) {
System.out.println(e.getMessage());
}
}
} /* Output: (Sample)
[2, 4, 9, 8, 6, 1, 3, 7, 5, 0]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
[9, 9, 7, 5, 1, 2, 6, 3, 0, 7, 2, 4, 4, 7, 9, 1, 3, 6, 2, 4, 3, 0, 5, 0, 8, 8, 8, 6, 5, 1]
[0, 5, 5, 6, 5, 0, 3, 1, 9, 8, 4, 2, 3, 9, 7, 3, 4, 4, 0, 7, 1, 9, 6, 2, 1, 8, 2, 8, 6, 7]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
java.lang.ClassCastException: SetType cannot be cast to java.lang.Comparable
java.lang.ClassCastException: HashType cannot be cast to java.lang.Comparable
*///:~
HashType继承自SetType , 并且添加丁hashCode()方法,该方法对于放置到Set的散列实现
中的对象来说是必需的。
TreeType实现了Comparable接口,如果一个对象被用于任何种类的排序容器中,例如
SortedSet (TreeSet是其唯一实现) ,那么它必须实现这个接口。
从输出中可以看到, HashSet以某种神秘的顺序保存所有的元素(这将在本章稍后进行解释) ,
LinkedHashSet按照元素插入的顺序保存元素,而TreeSet按照排序顺序维护元素(按照
compareTo()的实现方式,这里维护的是降序)。
如果我们尝试着将没有恰当地支持必需的操作的类型用于需要这些方法的Set ,那么就会有
大麻烦了。对于没有重新定义hashCode()方法的SetType或TreeType,如果将它们放置到任何散
列实现中都会产生重复值,这样就违反了Set的基本契约。这相当烦人,因为这甚至不会有运行
时错误。但是,默认的hashCode()是合法的,因此这是合法的行为,即便它不正确。确保这种程
序的正确性的唯一可靠方法就是将单元测试合并到你的构建系统中.
如果我们尝试着在TreeSet中使用没有实现Comparable的类型,那么你将会得到更确定的结
果: 在TreeSet试图将该对象当作Comparable使用时,将抛出一个异常。
 
 
17.6.1 SortedSet
SortedSet中的元素可以保证处于排序状态,这使得它可以通过在SortedSet接口中的下列方
法提供附加的功能:
Comparator comparator()返回当前Set使用的Comparator , 或者返回null ,表示以自然方式排序。
Object flrst() 返回容器中的第一个元素。
Object last() 返回容器中的最末一个元素。
SortedSet subSet(fromElement, toElement) 生成此Set的子集, 范围从fromElement (包含)到toEIement (不包含)。
SortedSet headSet(toElement) 生成此Set的子集,由小于toElement的元素组成。
SortedSet tailSet(fromElement) 生成此Set的子集,由大于或等于fromElement的元素组成。
package com.cy.container;

import java.util.*;
import static com.java.util.Print.*; public class SortedSetDemo {
public static void main(String[] args) {
SortedSet<String> sortedSet = new TreeSet<String>();
Collections.addAll(sortedSet,"one two three four five six seven eight".split(" "));
print(sortedSet); String low = sortedSet.first();
String high = sortedSet.last();
print(low);
print(high); Iterator<String> it = sortedSet.iterator();
for(int i = 0; i <= 6; i++) {
if(i == 3) low = it.next();
if(i == 6) high = it.next();
else it.next();
}
print(low);
print(high);
print(sortedSet.subSet(low, high));
print(sortedSet.headSet(high));
print(sortedSet.tailSet(low));
}
}
/* Output:
[eight, five, four, one, seven, six, three, two]
eight
two
one
two
[one, seven, six, three]
[eight, five, four, one, seven, six, three]
[one, seven, six, three, two]
*///:~
注意, SortedSet的意思是"按对象的比较函数对元素排序",而不是指"元素插入的次序"。
插入顺序可以用LinkedHashSet来保存。
17.7 队列
除了并发应用, Queue在Java SE5中仅有的两个实现是LinkedList和PriorityQueue ,它们的
差异在于排序行为而不是性能。下面是涉及Queue实现的大部分操作的基本示例(并非所有的
操作在本例中都能工作) ,包括基于并发的Queue。你可以将元素从队列的一端插入,并于另
端将它们抽取出来:
package com.cy.container;

import java.util.concurrent.*;
import java.util.*;
import com.java.util.*; public 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 ConcurrentLinkedQueue<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
*///:~
package com.java.util;

public interface Generator<T> {
T next();
}
你可以看到,除了优先级队列,Queue将精确地按照元素被置于Queue中的顺序产生它们.
 
1 7.7.1 优先级队列
在第11章曾经绘出过优先级队列的一个简单介绍.其中更有趣的问题是to-do列表,该列表
中每个对象都包含一个字符串和一个主要的以及次要的优先级值。该列表的排序顺序也是通过
实现Comparable而进行控制的:
package com.cy.container;

import java.util.*;

class ToDoList extends PriorityQueue<ToDoList.ToDoItem> {
static class ToDoItem implements Comparable<ToDoItem> {
private char primary;
private int secondary;
private String item; public ToDoItem(String td, char pri, int sec) {
primary = pri;
secondary = sec;
item = td;
} public int compareTo(ToDoItem arg) {
if(primary > arg.primary)
return +1;
if(primary == arg.primary)
if(secondary > arg.secondary)
return +1;
else if(secondary == arg.secondary)
return 0;
return -1;
} public String toString() {
return Character.toString(primary) + secondary + ": " + item;
}
} public void add(String td, char pri, int sec) {
super.add(new ToDoItem(td, pri, sec));
} public static void main(String[] args) {
ToDoList toDoList = new ToDoList();
toDoList.add("Empty trash", 'C', 4);
toDoList.add("Feed dog", 'A', 2);
toDoList.add("Feed bird", 'B', 7);
toDoList.add("Mow lawn", 'C', 3);
toDoList.add("Water lawn", 'A', 1);
toDoList.add("Feed cat", 'B', 1); while(!toDoList.isEmpty())
System.out.println(toDoList.remove());
}
}
/* Output:
A1: Water lawn
A2: Feed dog
B1: Feed cat
B7: Feed bird
C3: Mow lawn
C4: Empty trash
*///:~
你可以看到各个项的排序是如何因为使用了优先级队列而得以自动发生的.
 
 
 

-----------------

ThinkJava-容器深入研究的更多相关文章

  1. 容器技术研究-Kubernetes基本概念

    最近在研究容器技术,作为入门,基本概念必须搞明白,今天整理一下Kubernetes的基本概念. 一.什么是Kubernetes Kubernetes(k8s)是自动化容器操作的开源平台,这些操作包括部 ...

  2. 《Think in Java》(十七)容器深入研究

    阿西吧,这一章好长啊,感觉看了快一个月了吧!JDK 自带的容器框架真是很好很强大啊,这一章看得有点蒙蒙的,接下来还得去看看官方文档啊!

  3. 《Java编程思想》笔记 第十七章 容器深入研究

    1 容器分类 容器分为Collection集合类,和Map键值对类2种 使用最多的就是第三层的容器类,其实在第三层之上还有一层Abstract 抽象类,如果要实现自己的集合类,可以继承Abstract ...

  4. Thinking in Java:容器深入研究

    1.虚线框表示Abstract类,图中大量的类的名字都是以Abstract开头的,它们仅仅是部分实现了特定接口的工具,因此创建时能够选择从Abstract继承. Collections中的实用方法:挑 ...

  5. Java编程思想之十七 容器深入研究

    17.1 完整的容器分类方法 17.2 填充容器 import java.util.*; class StringAddress { private String s; public StringAd ...

  6. spring源码分析系列3:BeanFactory核心容器的研究

    目录 @(spring源码分析系列3:核心容器的研究) 在讲容器之前,再明确一下知识点. BeanDefinition是Bean在容器的描述.BeanDefinition与Bean不是一个东西. Be ...

  7. Java编程思想——第17章 容器深入研究 读书笔记(四)

    九.散列与散列码 HashMap使用equals()判断当前的键是否与表中存在的键相同. 正确的equals()方法需满足一下条件: 1)自反性.x.equals(x) 是true; 2)对称性.x. ...

  8. Java编程思想——第17章 容器深入研究 读书笔记(三)

    七.队列 排队,先进先出. 除并发应用外Queue只有两个实现:LinkedList,PriorityQueue.他们的差异在于排序而非性能. 一些常用方法: 继承自Collection的方法: ad ...

  9. Java编程思想——第17章 容器深入研究 读书笔记(一)

    这一章将学习散列机制是如何工作的,以及在使用散列容器时怎么样编写hashCode()和equals()方法. 一.容器分类 先上两张图 来概况完整的容器分类 再细说都为什么会有那些特性. 二.填充容器 ...

  10. Java编程思想——第17章 容器深入研究(two)

    六.队列 排队,先进先出.除并发应用外Queue只有两个实现:LinkedList,PriorityQueue.他们的差异在于排序而非性能. 一些常用方法: 继承自Collection的方法: add ...

随机推荐

  1. ant 打 jar 包添加 manifest.mf 文件

    经查询 ant 有 <manifest> 任务可以创建 manifest文件(https://ant.apache.org/manual/Tasks/manifest.html) 但尝试在 ...

  2. grafana dashboard的导入导出

    grafana的官方提供了很多社区或者官方设置的漂亮的dashboard,地址如下: 点击打开链接 导入图表大大节省了我们配置监控的时间,非常方便. 以linux host overview为例,首先 ...

  3. 如何编写Makefile,一份由浅入深的Makefile全攻略

    本文转载整理自陈浩大大的文章(跟我一起写 Makefile),由于原文内容庞大,故梳理出目录结构以便于学习及查阅参考. 一.概述 —— 什么是makefile?或许很多Winodws的程序员都不知道这 ...

  4. SWIFT显示底部的工具条

    有以下页面显示我的讯息,用户可以点击右上角的编辑按钮进入删除状态.点击编辑按钮后,按钮文字改为“取消”,左上角的按钮变为“全选”,同时显示底部工具条带有“删除”按钮 实现起来挺简单的,在正常状态下点击 ...

  5. rem & em初探

    Rem为单位 CSS3的出现,他同时引进了一些新的单位,包括我们今天所说的rem.在W3C官网上是这样描述rem的——“font size of the root element” .下面我们就一起来 ...

  6. iOS 证书 设置指南

    点击这里跳转到改链接:http://docs.jpush.cn/pages/viewpage.action?pageId=1343727

  7. shell 脚本实战笔记(10)--spark集群脚本片段念念碎

    前言: 通过对spark集群脚本的研读, 对一些重要的shell脚本技巧, 做下笔记. *). 取当前脚本的目录 sbin=`dirname "$0"` sbin=`cd &quo ...

  8. iPhone/iPad被停用怎么办 3招轻松解锁

    家中小孩玩电脑游戏,自己拿了iPad,随便输入密码,结果造成平板电脑无法使用,相信这是许多家长都将面对或是早已发生的事情.本文整理当iPhone或是iPad被停用时的处理方法. iPhone被停用 为 ...

  9. (dfs痕迹清理兄弟篇)bfs作用效果的后效性

    dfs通过递归将每种情景分割在不同的时空,但需要对每种情况对后续时空造成的痕迹进行清理(这是对全局变量而言的,对形式变量不需要清理(因为已经被分割在不同时空)) bfs由于不是利用递归则不能分割不同的 ...

  10. device public set

    backgroud:  our dvertiser provide on device list of idfa to show ad to  target audience,however none ...