说到Java数据结构,Collection下衍生出的各种list, set, 以及Map下的子类可以说是种类繁多,数不胜数。如何挑选如何使用就好比妹子们逛街买配饰,要跟鞋搭,跟衣服搭,跟妆容搭,各种搭。在详细归纳,比较这些结构之前,大黄对另一个类更感兴趣,就是Collections。和Collection好像啊是吧,又是亲兄弟吗?No No No, 名字像,可实际上却是完全不同的东西,就像谁一生没遇见过几个同名同姓的朋友呀,但即使名字一模一样,也是不同的个体。
Collection 与 Collections的区别
光看名字没有用,我们直接打开jdk源码来看看这两个类的java doc里的关键描述。
Collection
The root interface in the collection hierarchy. A collection represents a group of objects, known as its elements.
Collections
This class consists exclusively of static methods that operate on or return collections. It contains polymorphic algorithms that operate on collections, "wrappers", which return a new collection backed by a specified collection, and a few other odds and ends.
很清楚有木有, Collection是一个基础的接口,包含一组对象,也即它的元素。而Collections是包含静态方法,操作或返回集合类。也就是说这个类实际上是一个"工具类"。类似于Collection和Collections这样关系的组合常见的还有Array和Arrays类,是不是很眼熟啊,那个大明湖畔的Arrays.asList方法。这里就不再扩大篇幅分析这一对了。明白了区别以后就来看看Collections到底能干什么吧,看看它到底是不是一把swiss knife。
百宝箱Collections——"网红"方法介绍举例
Collections里有一些被频繁使用,几乎天天露脸的方法,大大方便了对于集合类一些特殊要求的操作。
我要排序
对于顺序的要求是Collection的基础需求,有时需要排序,有时需要打乱,有时还需要指定交换某两个元素的位置等等,大黄用例子介绍下列最常用的几个方法。
- sort(List list):对List中的所有元素进行自然升序排序
- sort(List list, Comparator c):自定义比较器进行排序
- shuffle(List list):对List中的元素进行随机打乱,类似于洗牌
- swap(List list, int i, int j):将List中i处元素和j处元素进行交换
- rotate(List list, int distance):将所有元素向右移位指定长度
- reverse(List list):对List集合中元素进行一次倒序排序
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
public class collectionTest {
public static void main(String[] args) {
String[] testData={ "London", "Shanghai", "Paris", "Tokyo", "Cannes", "Berlin" };
ArrayList<String> dataList = new ArrayList<String>(Arrays.asList(testData));
System.out.println("Initial Order:" + dataList);
//排序
Collections.sort(dataList);
System.out.println("Order after sort:" + dataList);
//倒序
Collections.reverse(dataList);
System.out.println("Order after reverse:" + dataList);
//打乱
Collections.shuffle(dataList);
System.out.println("Order after shuffle:" + dataList);
//交换
Collections.swap(dataList, 1,2);
System.out.println("Order after swap:" + dataList);
//移位
Collections.rotate(dataList, 1);
System.out.println("Order after rotate:" + dataList);
}
}
看一下每一个方法后产生的变化:
Initial Order:[London, Shanghai, Paris, Tokyo, Cannes, Berlin]
Order after sort:[Berlin, Cannes, London, Paris, Shanghai, Tokyo]
Order after reverse:[Tokyo, Shanghai, Paris, London, Cannes, Berlin]
Order after shuffle:[Paris, Tokyo, Berlin, London, Cannes, Shanghai]
Order after swap:[Paris, Berlin, Tokyo, London, Cannes, Shanghai]
Order after rotate:[Shanghai, Paris, Berlin, Tokyo, London, Cannes]
我要查找替换
查找和替换同样也是集合类中的重要概念,如果不想生硬地使用for循环这样的结构,熟练地运用下面这些常见方法会大大提升效率。我们向原始list多添加一个Shanghai,然后开始验证下列方法:
binarySearch(List list, Object key):使用二分搜索法,以获得指定对象在List中的索引,前提是集合已经排序
- max(Collection coll):获取最大元素
- min(Collection coll):获取最小元素
- frequency(Collection Object o):获取集合中指定元素出现的次数
- replaceAll(List list, Object old, Object new):替换指定的元素
- fill(List list, Object obj):使用指定元素填充集合
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
public class collectionTest {
public static void main(String[] args) {
String[] testData={ "London", "Shanghai", "Paris", "Tokyo", "Cannes", "Berlin","Shanghai" };
ArrayList<String> dataList = new ArrayList<String>(Arrays.asList(testData));
System.out.println("Initial Order:" + dataList);
//取极值
System.out.println("max:" + Collections.max(dataList));
System.out.println("min:" + Collections.min(dataList));
//指定元素的出现次数
System.out.println("Frequency of Shanghai:" + Collections.frequency(dataList, "Shanghai"));
//替换指定元素
Collections.replaceAll(dataList, "Shanghai", "Dubai");
System.out.println("After replaceAll:" + dataList);
// binarySearch
System.out.println("binarySearch before sort:" + Collections.binarySearch(dataList, "London"));
Collections.sort(dataList);
System.out.println("Order after sort:" + dataList);
System.out.println("binarySearch after sort:" + Collections.binarySearch(dataList, "London"));
Collections.fill(dataList, "Beijing");
System.out.println("After fill:" + dataList);
}
}
结果是:
Initial Order:[London, Shanghai, Paris, Tokyo, Cannes, Berlin, Shanghai]
max:Tokyo
min:Berlin
Frequency of Shanghai:2
After replaceAll:[London, Dubai, Paris, Tokyo, Cannes, Berlin, Dubai]
binarySearch before sort:-3
Order after sort:[Berlin, Cannes, Dubai, Dubai, London, Paris, Tokyo]
binarySearch after sort:4
After fill:[Beijing, Beijing, Beijing, Beijing, Beijing, Beijing, Beijing]
这里要提一下binarySearch, 二分法排序查找,上面在排序前输出的答案很离谱,但排序后是正确的,为什么呢?看一下Jdk源码:
public static <T>
int binarySearch(List<? extends Comparable<? super T>> list, T key) {
if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
return Collections.indexedBinarySearch(list, key);
else
return Collections.iteratorBinarySearch(list, key);
}
private static <T>
int indexedBinarySearch(List<? extends Comparable<? super T>> list, T key) {
int low = 0;
int high = list.size()-1;
while (low <= high) {
int mid = (low + high) >>> 1;
Comparable<? super T> midVal = list.get(mid);
int cmp = midVal.compareTo(key);
if (cmp < 0)
low = mid + 1;
else if (cmp > 0)
high = mid - 1;
else
return mid; // key found
}
return -(low + 1); // key not found
}
private static <T>
int iteratorBinarySearch(List<? extends Comparable<? super T>> list, T key)
{
int low = 0;
int high = list.size()-1;
ListIterator<? extends Comparable<? super T>> i = list.listIterator();
while (low <= high) {
int mid = (low + high) >>> 1;
Comparable<? super T> midVal = get(i, mid);
int cmp = midVal.compareTo(key);
if (cmp < 0)
low = mid + 1;
else if (cmp > 0)
high = mid - 1;
else
return mid; // key found
}
return -(low + 1); // key not found
}
用排序前大黄的例子来看它是怎么运转的:
- low=0; high=6; mid=3, value=Tokyo
- low=0; high=2; mid=1, value=Dubai
- low=2; high=2; mid=2, value=Paris
- low=2; high=1; return -3
这就是为啥出现-3的原因,由于没有排序,二分的思路从一开始就走向了歧途。所以这个方法的使用还是要看具体情况,你的数据是否是按序排列的,你是否可以接受先排序一次再进行查找,这些都是需要考虑的。
Wrapper的魔力
如果你仔细看了大黄贴出来的java doc,你肯定注意到了Collections里还有一些"wrappers"。我们一般译为封装器。主要作用就是将我们原始的一些Collection类封装转换成具有特定属性的Collection. 其中比较重要的一种是synch wrappers:synchronizedXxx(),如synchronizedList,synchronizedSet。这些方法会返回指定集合对象对应的同步对象,从而解决多线程并发访问集合时线程的安全问题。这也提供了除使用concurrent包以外另一种多线程的数据结构构造方式。
另一种常见的wrapper种类我们统一称之为不可变集合,即转换成这类对象后集合将被禁止相关的操作。这类wrapper包含
- emptyXxx():获取一个空的不可变集合对象
- singletonXxx():获取一个只包含指定对象的,不可变集合对象。
- unmodifiableXxx():获取指定集合对象的不可变视图
当你有一些特殊需求时,比如你的集合对外必须是只读的,那么就可以运用这些封装器。简单的一个例子
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class collectionTest {
public static void main(String[] args) {
String[] testData={ "London", "Shanghai", "Paris", "Tokyo", "Cannes", "Berlin","Shanghai" };
ArrayList<String> dataList = new ArrayList<String>(Arrays.asList(testData));
List<String> unModifyList = (List) Collections.unmodifiableList(dataList);
unModifyList.add("Madrid"); //此处应有exception
System.out.println("unModifyList:" + unModifyList);
}
}
可以看到,在add时,抛出了java.lang.UnsupportedOperationException,印证了我们的想法:集合对象不可变。
Exception in thread "main" java.lang.UnsupportedOperationException
at java.util.Collections$UnmodifiableCollection.add(Collections.java:1055)
at jianshu.collectionTest.main(collectionTest.java:15)
后话
Collections里还有许多其他的方法,wrappers,这里就不一一介绍了,有兴趣的可以翻看jdk源码。工欲善其事,必先利其器,熟悉并挑选正确的工具类方法可以大大提升我们对集合类对象的操作效率,对我们深入了解Java数据结构的核心部分打下基础,这也是这个类优秀之处。
Et voilà!