Java 提供了一个操作 Set、List 和 Map 等集合的工具类:Collections,该工具类里提供了大量方法对集合元素进行排序、查询和修改等操作,还提供了将集合对象设置为不可变、对集合对象实现同步控制等方法。
1,排序操作
Collections 提供了如下几个方法用于对 List 集合元素进行排序。
static void reverse(List list):反转指定 List 集合中元素的顺序。
static void shuffle(List list):对 List 集合元素进行随机排序(shuffle 方法模拟了“洗牌”动作)。
static void sort(List list):根据元素的自然顺序对指定 List 集合的元素升序进行排序。
static void sort(List list, Comparator c):根据指定 Comparator 产生的顺序对 List 集合元素进行排序。
static void swap(List list, int i, int j):将指定 List 集合中的 i 出元素和 j 出元素进行交换。
static void rotate(List list, int distance):当 distance 为正数时,将 list 集合的后 distance 个元素“整体” 移到前面;当 distance 为负数时,将 List 集合的钱 distance 个元素“整体”移到后面。该方法不会改变集合的长度。
下面程序简单示范了利用 Collections 工具类来操作 List 集合。
package com.sym.demo3;
import java.util.ArrayList;
import java.util.Collections;
public class SortTest {
public static void main(String[] args) {
ArrayList nums = new ArrayList();
nums.add(2);
nums.add(-5);
nums.add(3);
nums.add(0);
//输出:[2, -5, 3, 0]
System.out.println(nums);
//输出最大元素,将输出 3
System.out.println(Collections.max(nums));
//输出最小元素,将输出 -5
System.out.println(Collections.min(nums));
//将 nums 中的 0 使用 1 来代替
Collections.replaceAll(nums, 0, 1);
//输出:[2, -5, 3, 1]
System.out.println(nums);
//判断 -5 在 List 集合中出现的次数,返回 1
System.out.println(Collections.frequency(nums, -5));
//对 nums 集合排序
Collections.sort(nums);
//输出:[-5, 1, 2, 3]
System.out.println(nums);
//只有排序后的 List 集合才可用二分法查询,输出 3
System.out.println(Collections.binarySearch(nums, 3));
}
}
上面代码示范了 Collections 类常用的排序操作。下面通过编写一个梭哈游戏来演示 List 集合、Collections 工具类的强大功能。
package com.sym.demo3;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
public class ShowHand {
//定义该游戏最多支持多少个玩家
private final int PLAY_NUM = 5;
//定义扑克牌的所有花色和数值
private String[] types = {"方块", "草花", "红心", "黑桃"};
private String[] values = {"2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"};
//cards 是一局游戏中剩下的的扑克牌
private List<String> cards = new LinkedList<String>();
//定义所有的玩家
private String[] players = new String[PLAY_NUM];
//所有玩家手上的扑克牌
private List<String>[] playersCards = new List[PLAY_NUM];
/**
* 初始化扑克牌,放入 52 张扑克牌
* 并且使用 shuffle 方法将它们按随机顺序排列
*/
public void initCards(){
for(int i = 0; i < types.length; i++ ){
for(int j = 0; j < values.length; j++ ){
cards.add(types[i] + values[j]);
}
}
//随机排列
Collections.shuffle(cards);
}
/**
* 初始化玩家,为每个玩家分派用户名
* @param names
*/
public void initPlayer(String... names){
if(names.length > PLAY_NUM || names.length < 2){
//校验玩家数量,此处使用异常机制更合理
System.out.println("玩家数量不对");
return;
} else {
//初始化玩家用户名
for (int i = 0; i < names.length; i++){
players[i] = names[i];
}
}
}
/**
* 初始化玩家手上的扑克牌,开始游戏时每个玩家手上的扑克牌为空
* 程序使用一个长度为 0 的 LinkedList 来表示
* @param names
*/
public void initPlayerCards(){
for (int i = 0; i < players.length; i++){
if(players[i] != null && !players[i].equals("")){
playersCards[i] = new LinkedList<String>();
}
}
}
/**
* 输出全部扑克牌,该方法没有实际作用,仅用作测试
*/
public void showAllCards(){
for (String card : cards){
System.out.println(card);
}
}
public void deliverCard(String first){
//调用 ArrayUtils 工具类的 search 方法
//查询出指定元素在数组中的索引
int firstPos = ArrayUtils.search(player, first);
//依次给位于该指定玩家之后的每个玩家派发扑克牌
for (int i = firstPos; i < PLAY_NUM; i++ ){
if (players[i] != null){
playersCards[i].add(cards.get(0));
cards.remove(0);
}
}
}
/**
* 输出玩家手上的扑克牌
* 实现该方法时,应该控制每个玩家看不到别人的第一张牌,但此处没有增加该功能
*/
public void showPlayerCards(){
for(int i = 0; i < PLAY_NUM; i++){
//当该玩家不为空时
if( players[i] != null){
//输出玩家
System.out.println(players[i] + ":");
//遍历输出玩家手上的扑克牌
for(String card : playersCards[i]){
System.out.println(card + "\t");
}
}
System.out.println("\n");
}
}
public static void main(String[] args) {
ShowHand sh = new ShowHand();
sh.initPlayer("电脑玩家", "孙悟空");
sh.initCards();
sh.initPlayerCards();
//下面测试所有扑克牌,没有实际作用
sh.showAllCards();
System.out.println("---------------");
//下面从“孙悟空”开始派牌
sh.deliverCard("孙悟空");
sh.showPlayerCards();
/*
这个地方需要增加出来:
1. 牌面最大的玩家下注
2. 其他玩家是否跟住
3. 游戏是否只剩下一个玩家?如果是,则此玩家胜利了
4. 如果已经是最后一张扑克牌,则需要比较剩下玩家的牌面大小
*/
//再次从“电脑玩家”开始派牌
sh.deliverCard("电脑玩家");
sh.showPlayerCards();
}
}
2,查找、替换操作
Collections 还提供了如下用于查找、替换集合元素的常用方法。
static int binarySearch(List list, Object key):使用二分搜索法搜索指定的 List 集合,以获得指定对象在 List 集合中的索引。如果要使该方法可以正常工作,则必须保证 List 中的元素已经处于有序状态。
static Object max(Collection coll):根据元素的自然顺序,返回给定集合中的最大元素。
static Object max(Collection coll, Comparator comp):根据 Comparator 指定的顺序,返回给定集合中的最大元素。
static Object min(Collection coll):根据元素的自然顺序,返回给定集合中的最小元素。
static Object min(Collection coll, Comparator comp):根据 Comparator 指定的顺序,返回给定集合中的最小元素。
static void fill(List list, Object obj):使用指定元素 obj 替换指定 List 集合中的所有元素。
static int frequency(Collection c, Object o):返回指定集合中指定元素的出现次数。
static int indexOfSubList(List source, List target):返回子 List 对象在父 List 对象中第一次出现的位置索引;如果父 List 中没有出现这样的子 List,则返回 -1.
static int lastIndexOfSubList(List source, List targer):返回子 List 对象在父 List 对象中最后一次出现的位置索引;如果父 List 中没有出现这样的子 List,则返回 -1.
static boolean replaceAll(List list, Object oldVal, Object newVal):使用一个新值 newVal 替换 List 对象的所有的旧值 oldVal。
package com.sym.demo3;
import java.util.ArrayList;
import java.util.Collections;
public class SearchTest {
public static void main(String[] args) {
ArrayList nums = new ArrayList();
nums.add(2);
nums.add(-5);
nums.add(3);
nums.add(0);
//输出:[2, -5, 3, 0]
System.out.println(nums);
//输出最大元素,将输出 3
System.out.println(Collections.max(nums));
//输出最小元素,将输出 -5
System.out.println(Collections.min(nums));
//将 nums 中的 0 使用 1 来代替
Collections.replaceAll(nums, 0, 1);
//输出:[2, -5, 3, 1]
System.out.println(nums);
//判断 -5 在 List 集合中出现的次数,返回 1
System.out.println(Collections.frequency(nums, -5));
//对 nums 集合排序
Collections.sort(nums);
//输出:[-5, 1, 2, 3]
System.out.println(nums);
//只有排序后的 List 集合才可用二分法查询,输出 3
System.out.println(Collections.binarySearch(nums, 3));
}
}
3,同步控制
Collections 类中提供了多个 synchronizedXxx() 方法,该方法可以将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题。
正如前面所介绍的,Java 中常用的集合框架中的实现类 HashSet、TreeSet、ArrayList、ArrayDeque、LinkedList、HashMap 和 TreeMap 都是线程不安全的。如果有多个线程访问它们,而且有超过一个的线程试图修改它们,则可能出现错误。Collections 提供了多个静态方法把它们包装成线程同步的集合。
下面的示例程序创建了 4 个同步的集合对象。
package com.sym.demo3;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class SynchronizedTest {
public static void main(String[] args) {
//下面程序创建了 4 个同步的集合对象
Collection c = Collections.synchronizedCollection(new ArrayList());
List list = Collections.synchronizedList(new ArrayList());
Set s = Collections.synchronizedSet(new HashSet());
Map m = Collections.synchronizedMap(new HashMap());
}
}
在上面示例程序中,直接将新创建的集合对象传给了 Collections 的 synchronizedXxx 方法,这样就可以直接获取 List、Set 和 Map 的线程安全实际版本。
4,设置不可变集合
Collections 提供了如下三类方法来返回一个不可变的集合。
emptyXxx():返回一个空的、不可变的集合对象,此处的集合既可以是 List,也可以是 Set,还可以是 Map。
singletonXxx():返回一个只包含指定对象(只有一个或一项元素)的、不可变的集合对象,此处的集合既可以是 List,也可以是 Set,还可以是 Map。
unmodifiableXxx:返回指定集合对象的不可变视图,此处的集合既可以是 List,也可以是 Set,还可以是 Map。
上面三类方法的参数是原有的集合对象,返回值是该集合的“只读”版本。通过 Collections 提供的三类方法,可以生产“只读”的 Collection 或 Map。
package com.sym.demo3;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class UnmodifiableTest {
public static void main(String[] args) {
//创建一个空的、不可改变的 List 对象
List unmodifiableList = Collections.emptyList();
//创建一个只有一个元素,且不可改变的 Set 对象
Set unmodifiableSet = Collections.singleton("疯狂 Java 讲义");
//创建一个普通的 Map 对象
Map scores = new HashMap();
scores.put("语文", 80);
scores.put("Java", 82);
//返回普通的 Map 对象对应的不可变版本
Map unmodifiableMap = Collections.unmodifiableMap(scores);
//下面任意一行代码都将引发 UnsupportedOperationException 异常
unmodifiableList.add("测试元素");//1
unmodifiableSet.add("测试元素");//2
unmodifiableMap.put("语文", 90);//3
}
}
上面程序里面分别顶一个了一个空的、不可变的 List 对象,一个只包含一个元素的、不可变的 Set 对象和一个不可变的 Map 对象。不可变的集合对象只能访问集合元素,不可修改集合元素。所以上面程序中1,2,3 处的代码都将引发 UnsupportedOperationException 异常。
烦琐的接口:Enumeration
Enumeration 接口是 Iterator 跌打起的“古老版本”,从 JDK 1.0 开始,Enumeration 接口就已经存在了(Iterator 从 JDK 1.2 才出现)。Enumeration 接口只有两个名字很长的方法。
boolean hasMoreElements():如果此迭代器还有剩下的元素,则返回 true。
Object nextElement():返回该迭代器的下一个元素,如果还有的话(否则抛出异常)。
通过这两个方法不难发现,Enumeration 接口中的方法名称冗长,难以记忆,而且没有提供 Iterator 的 remove() 方法。如果现在编写 Java 程序,应该尽量采用 Iterator 迭代器,而不是 Enumeration 迭代器。
Java 之所以保留 Enumeration 接口,主要是为了照顾以前那些“古老”的程序,那些程序里大量使用了 Enumeration 接口,如果新版本的 Java 里直接删除 Enumeration 接口,将会导致那些程序全部出错。在计算机行业有一条规则:加入任何规则都必须慎之又慎,因为以后无法删除规格。
实际上,前面介绍的 Vector(包括其子类 Stack)、Hashtable 两个集合类,以及另一个极少使用的 BitSet,都是从 JDK 1.0 遗留下来的集合类,而 Enumeration 接口可用于遍历这些“古老”的集合类。对于 ArrayList、HashMap 等集合类,不再支持使用 Enumeration 迭代器。
package com.sym.demo3;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
public class EnumerationTest {
public static void main(String[] args) {
Vector v = new Vector();
v.add("疯狂 Java 讲义");
v.add("轻量级 Java EE 企业应用实战");
Hashtable scores = new Hashtable();
scores.put("语文", 78);
scores.put("数学", 88);
Enumeration em = v.elements();
while (em.hasMoreElements()){
System.out.println(em.nextElement());
}
Enumeration keyEm = scores.keys();
while (keyEm.hasMoreElements()){
Object key = keyEm.nextElement();
System.out.println(key + "--->" + scores.get(key));
}
}
}
上面程序使用 Enumeration 迭代器来遍历 Vector 和Hashtable 集合里的元素,其工作方式与 Iterator 迭代器的工作方式基本相似。但使用 Enumeration 迭代器方法名更加冗长,而且 Enumeration 迭代器只能遍历 Vector、Hashtable 这种古老的集合,因此通常不要使用它。除非在某些极端情况下,不得不使用 Enumeration,否则都应该选择 Iterator 迭代器。