容器深入研究(3):Collection的功能方法、可选操作

一、Collection的功能方法

    下面的表格列出了可以通过Collection执行的所有操作(不包括从Object继承而来的方法)。因此,它们也是可通过Set或List执行的所有操作(List还有额外的功能)。Map不是继承自Collection的,所以会另行介绍。

boolean add(T)确保容器持有具有泛型类型T的参数。如果没有将此参数添加进容器,则返回false(这是“可选方法”,稍后会解释)
boolean addAll(Collection<? 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

    请注意,其中不包括随机访问所选择元素的get()方法。因为Collection包括Set、而Set是自己维护内部顺序的(这使得随机访问变得没有意义)。因此,如果想检查Collection中的元素,那就必须得使用迭代器。

    下面的例子展示了所有这些方法。虽然任何实现了Collection的类都可以使用这些方法,但示例中使用ArrayList,以说明各种Collection子类的“最基本的共同特性”:

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import com.buba.util.Countries;

public class CollectionMethods {
	public static void main(String[] args) {
		Collection<String> c = new ArrayList<String>();
		c.addAll(Countries.names(6));
		c.add("ten");
		c.add("eleven");
		System.out.println(c);
		Object[] array = c.toArray();
		String[] str = c.toArray(new String[0]);
		System.out.println("Collection.max(c) = " + Collections.max(c));
		System.out.println("Collection.min(c) = " + Collections.min(c));
		Collection<String> c2 = new ArrayList<>();
		c2.addAll(Countries.names(6));
		c.addAll(c2);
		System.out.println(c);
		c.remove(Countries.DATA[0][0]);
		System.out.println(c);
		c.remove(Countries.DATA[1][0]);
		System.out.println(c);
		c.removeAll(c2);
		System.out.println(c);
		c.addAll(c2);
		System.out.println(c);
		String var = Countries.DATA[3][0];
		System.out.println("c.contains(" + var + ") = " + c.contains(var));
		System.out.println("c.containsAll(c2) = " + c.containsAll(c2));
		Collection<String> c3 = ((List<String>) c).subList(3, 5);
		c2.retainAll(c3);
		System.out.println(c2);
		c2.removeAll(c3);
		System.out.println("c2.isEmpty() = " + c2.isEmpty());
		c = new ArrayList<String>();
		c.addAll(Countries.names(6));
		System.out.println(c);
		c.clear();
		System.out.println("after c.clear():" + c);
	}
}

    创建ArrayList来保存不同的数据集,然后向上转型为Collection,所以很明显,代码只用到了Collection接口。main()用简单的练习展示了Collection中的所有方法。

    本章后面的小节将介绍List、Set和Map的各种实现,每种情况都会(以星号)标出默认的选择。对遗留类Vector、Stack和Hashtable的描述放到了本章的末尾,尽管你不应该使用这些类,但是在老的代码中仍旧会看到它们。

二、可选操作

    执行各种不同的添加和移除的方法在Collection接口中都是可选操作。这意味着实现类并不需要为这些方法提供功能定义。

    这是一种很不寻常的接口定义方式。正如你所看到的那样,接口是面向对象设计中的契约,它声明“无论你选择如何实现该接口,我保证你可以向该接口发送这些消息。”但是可选操作违反了这个非常基本的原则,它声明调用某些方法将不会执行有意义的行为,相反,它们会抛出异常。这看起来好像是编译期类型安全被抛弃了!

    事情并不那么糟。如果一个操作是可选的,编译器仍旧会严格要求你只能调用该接口中的方法。这与动态语言不同,动态语言可以在任何对象上调用任何方法,并且可以在运行时发现某个特定调用是否可以工作。另外,将Collection当作参数接受的大部分方法只会从该Collection中读取,而Collection的读取方法都不是可选的。

    为什么你会将方法定义为可选的呢?那是因为这样做可以防止在设计中出现接口爆炸的情况。容器类库中的其他设计看起来总是为了描述每个主题的各种变体,而最终患上了令人困惑的接口过剩症。甚至这么做仍不能捕捉接口的各种特例,因为总是有人会发明新的接口。“未获支持的操作”这种方式可以实现java容器类库的一个重要目标:容器应该易学易用。未获支持的操作是一种特例,可以延迟到需要时再实现。但是,为了让这种方式能够工作:

  1. UnsupportedOperationException必须是一种罕见事件。即,对于大多数类来说,所有操作都应该可以工作,只有在特例中才会有未获支持的操作。在java容器类库中确实如此,因为你在99%的时间里面使用的容器类,如ArrayList、LinkedList、HashSet和HashMap,以及其他的具体实现,都支持所有的操作。这种设计留下了一个“后门”,如果你想创建新的Collection,但是没有为Collection接口中的所有方法都提供有意义的定义,那么它仍旧适合现有的类库。
  2. 如果一个操作是未获支持的,那么在实现接口的时候可能就会导致UnsupportedOperationException异常,而不是将产品程序交给客户之后才出现此异常,这种情况是有道理的。毕竟,它表示编程上有错误:使用了不正确的接口实现。

    值得注意的是,未获支持的操作只有在运行时才能探测到,因此它们表示动态类型检查。如果你以前使用的是像C++这样的静态类型语言,那么可能会觉得java也只是另一种静态类型语言,但是它还具有大量的动态类型机制,因此很难说它到底是哪一种类型的语言。一旦开始注意到这一点了,你就会看到java中动态类型检查的其他例子。

三、未获支持的操作

    最常见的未获支持的操作,都来源于背后固定尺寸的数据结构支持的容器。当你用Arrays.asList()将数组转换为List时,就会得到这样的容器。你还可以通过使用Collections类中“不可修改”的方法,选择创建任何会抛出UnsupportedOperationException的容器(包括Map)。下面的示例包括这两种情况:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

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);
		Collection<String> c2 = new ArrayList<>(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);
		}
		try {
			list.set(0, "X");
		} catch (Exception e) {
			System.out.println("list.sets() : " + 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("Array.asList()", list);
		test("unmodifiableList()", Collections.unmodifiableList(new ArrayList<String>(list)));
	}
}

    因为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“尺寸固定”这一特性。但是很明显,unmodifiableList()的结果在任何情况下都应该不是可修改的。如果使用的是接口,那么还需要两个附加的接口,一个具有可以工作的set()方法,另外一个没有,因为附加的接口对于Collection的各种不可修改的子类型来说是必需的。

    对于将容器作为参数接受的方法,其文档应该指定哪些可选方法必须实现。

如果本文对您有很大的帮助,还请点赞关注一下。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

游王子og

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值