Java 学习之路 之 操作集合的工具类:Collections(三十八)

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 迭代器。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值