如何设计集合交集、差集、补集
集合的交集可能你会说JDK已经提供了交集的功能,但是差集、补集并没有提供实现。于是,首先让我们动手重复造轮子,先实现一个交集功能,你会怎么实现呢?
我设计一个函数,需要两个入参,然后求他们交集,并返回。我的实现如下。
public static <T> Set<T> intersection(Collection<T> from, Collection<T> to) {
Objects.requireNonNull(from);
Objects.requireNonNull(to);
Iterator<T> iterator = from.iterator();
Set<T> result = new HashSet<>();
while (iterator.hasNext()) {
T var = iterator.next();
if (to.contains(var)) {
result.add(var);
}
}
return result;
}
当然,这里肯定还有不足的地方,比如性能,内存等等。
JDK是如何设计的呢?
既然JDK已经我们提供这个功能,看看官方是怎么实现的,有什么值得借鉴的地方呢?
JDK在 AbstractCollection 提供了实现,主要是借助迭代器来实现的。
public boolean retainAll(Collection<?> c) {
Objects.requireNonNull(c);
boolean modified = false;
Iterator<E> it = iterator();
while (it.hasNext()) {
if (!c.contains(it.next())) {
it.remove();
modified = true;
}
}
return modified;
}
但是这里有一个比较尬尴的地方是,使用 Arrays.asList() 返回的list调用 retainAll 就会抛出异常。原因是Arrays.asList 只是提供一种视图的list,不支持修改操作。
那还要其他更好设计吗?
当然有啊,guava库提供Sets的工具类,它提供集合交集、差集、补集等等功能。在了解guava是如何设计之前,先了解怎么使用它。
Set<Integer> var1 = Sets.newHashSet(1,3,4,5,8);
Set<Integer> var2 = Sets.newHashSet(3,4,6,9,12);
Sets.SetView<Integer> var3 = Sets.intersection(var1, var2);
System.out.println(var3);
// [3,4] 交集
System.out.println(Sets.difference(var1, var2));
// [8,1,5] var1的差集
System.out.println(Sets.symmetricDifference(var1, var2));
// [8, 1, 5, 9, 12, 6] 补集
那么guava是如何设计的呢?
迭代器。首先guava的自定义的迭代器 AbstractIterator 。
public abstract class AbstractIterator<T> extends UnmodifiableIterator<T> {
private State state = State.NOT_READY;
private @Nullable T next;
protected abstract T computeNext(); // 钩子函数
/**
* 实现接口Iterator的hasNext方法,状态state来判断是否还有下个元素
*/
@Override
public final boolean hasNext() {
checkState(state != State.FAILED);
switch (state) {
case DONE:
return false;
case READY:
return true;
default:
}
return tryToComputeNext();
}
/**
* tryToComputeNext方法用来计算下一个元素的值,并把值绑定到域next上。
*
*/
private boolean tryToComputeNext() {
state = State.FAILED; // temporary pessimism
next = computeNext();
if (state != State.DONE) {
state = State.READY;
return true;
}
return false;
}
/**
* 实现了接口Iterator的next方法,并返回域next的值
*/
public final T next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
state = State.NOT_READY;
T result = next;
next = null;
return result;
}
}
Sets.intersection在内部使用匿名类重写了 AbstractIterator 类的 computeNext 方法。
return new AbstractIterator<E>() {
final Iterator<E> itr = set1.iterator();
@Override
protected E computeNext() {
while (itr.hasNext()) {
E e = itr.next();
if (set2.contains(e)) {
return e;
}
}
return endOfData();
}
};
方法调用时序图

再造轮子
现在我们需要动手借助guava设计,仿造这样的迭代器,对之前轮子进行改进。
参考guava的 AbstractIterator 迭代器,并简化一些状态的设计。
abstract static class MyIterator<T> implements Iterator<T> {
private boolean endOf = false;
private T next;
protected abstract T computeNext();
@Override
public boolean hasNext() {
next = computeNext();
return !endOf ;
}
@Override
public T next() {
return next;
}
protected T endOfData(){
endOf = true;
return null;
}
}
使用迭代改进第一个设计的版本。
public static <T> Set<T> intersection(Collection<T> from, Collection<T> to) {
Objects.requireNonNull(from);
Objects.requireNonNull(to);
return new AbstractSet<T>() {
@Override
public Iterator<T> iterator() {
return new MyIterator<T>() {
final Iterator<T> itr = from.iterator();
@Override
protected T computeNext() {
while (itr.hasNext()) {
T var = itr.next();
if (to.contains(var)) {
return var;
}
}
return endOfData();
}
};
}
@Override
public int size() {
int cnt = 0;
for (T t: from) {
if (to.contains(t)) {
cnt++;
}
}
return cnt;
}
};
}
guava的差集、补集都是类似的实现,如果你感兴趣的话,可以尝试把其他两个实现了。真的是长见识,迭代器还可以这样玩,优秀。
本文探讨了如何在Java中实现集合的交集、差集和补集操作。从自定义实现开始,逐步介绍了JDK和Guava库提供的解决方案。通过分析源码,展示了Guava如何巧妙利用迭代器提高效率。
2359

被折叠的 条评论
为什么被折叠?



