如何最好地比较Java中的两个集合并对其采取行动?
我有同一个对象的两个集合,即Collection <Foo> oldSet和Collection <Foo> newSet。所需的逻辑如下:
- 如果
foo
是in(*)OldSet
,而不是newSet
,则调用doRemove(foo)
- 否则,如果foo不是在oldSet中,而是在newSet中,则调用doAdd(foo)
- 否则,如果两个集合中都包含foo但已对其进行了修改,请调用doUpdate(oldFoo,newFoo)。
- 否则,如果
!foo.activated && foo.startDate> = now
,则调用doStart(foo)
- 否则,如果
foo.activated && foo.endDate <= now
,则调用doEnd(foo)
(*)"中"表示唯一标识符匹配,不一定与内容匹配。
当前(旧版)代码做了很多比较,以找出" removeSet"," addSet"," updateSet"," startSet"和" endSet",然后循环以对每个项目进行操作。
代码非常混乱(部分原因是我已经省略了一些意大利面条逻辑),并且我试图对其进行重构。一些更多的背景信息:
- 据我所知,
oldSet
和newSet
实际上由ArrayList
支持。 - 每套包含少于100件物品,最有可能最多20件
- 尽管设置很少不同,但经常调用此代码(以百万/天为单位)
我的问题:
- 如果我将ID设置为键,将oldSet和newSet转换为HashMap <Foo>(在这里顺序无关),是否会使代码更易于阅读和比较?转换损失了多少时间和内存性能?
- 迭代这两组并执行适当的操作会更高效,更简洁吗?
解决方案
回答
对于这么小的集合,通常不值得将其从Array转换为HashMap / set。实际上,最好将它们保留在一个数组中,然后按键对它们进行排序,并同时遍历两个列表以进行比较。
回答
我已经创建了一个大概的视图,我认为我们只是在Java中使用Collections Framework。坦白地说,正如@Mike Deck指出的那样,我认为这可能是过大了。对于这么少的项目进行比较和处理,我认为从过程的角度来看,数组是一个更好的选择,但这是我的伪编码(因为我很懒)解决方案。我假设Foo类基于其唯一ID而不是其内容中的所有数据都是可比较的:
Collection<Foo> oldSet = ...; Collection<Foo> newSet = ...; private Collection difference(Collection a, Collection b) { Collection result = a.clone(); result.removeAll(b) return result; } private Collection intersection(Collection a, Collection b) { Collection result = a.clone(); result.retainAll(b) return result; } public doWork() { // if foo is in(*) oldSet but not newSet, call doRemove(foo) Collection removed = difference(oldSet, newSet); if (!removed.isEmpty()) { loop removed { Foo foo = removedIter.next(); doRemove(foo); } } //else if foo is not in oldSet but in newSet, call doAdd(foo) Collection added = difference(newSet, oldSet); if (!added.isEmpty()) { loop added { Foo foo = addedIter.next(); doAdd(foo); } } // else if foo is in both collections but modified, call doUpdate(oldFoo, newFoo) Collection matched = intersection(oldSet, newSet); Comparator comp = new Comparator() { int compare(Object o1, Object o2) { Foo f1, f2; if (o1 instanceof Foo) f1 = (Foo)o1; if (o2 instanceof Foo) f2 = (Foo)o2; return f1.activated == f2.activated ? f1.startdate.compareTo(f2.startdate) == 0 ? ... : f1.startdate.compareTo(f2.startdate) : f1.activated ? 1 : 0; } boolean equals(Object o) { // equal to this Comparator..not used } } loop matched { Foo foo = matchedIter.next(); Foo oldFoo = oldSet.get(foo); Foo newFoo = newSet.get(foo); if (comp.compareTo(oldFoo, newFoo ) != 0) { doUpdate(oldFoo, newFoo); } else { //else if !foo.activated && foo.startDate >= now, call doStart(foo) if (!foo.activated && foo.startDate >= now) doStart(foo); // else if foo.activated && foo.endDate <= now, call doEnd(foo) if (foo.activated && foo.endDate <= now) doEnd(foo); } } }
关于问题:
如果我将oldSet和newSet转换为HashMap(此处不关心顺序),并以ID作为键,是否会使代码更易于阅读和比较?转换损失了多少时间和内存性能?
我认为我们可能会通过使用Map BUT使代码更易读...在转换过程中可能会使用更多的内存和时间。
迭代这两组并执行适当的操作会更高效,更简洁吗?
是的,这将是两全其美的做法,特别是如果我们遵循@Mike Sharek的建议,即使用特殊方法滚动自己的列表,或者遵循类似Visitor Design模式的操作来遍历收藏夹并处理每个项目。
回答
我将移至列表并通过以下方式解决它:
- 如果列表中的对象不可比较,则使用自定义比较器按ID升序对两个列表进行排序
- 像在合并排序算法中的合并阶段那样,对两个列表中的元素进行迭代,但是要检查逻辑,而不是合并列表。
该代码或者多或者少是这样的:
/* Main method */ private void execute(Collection<Foo> oldSet, Collection<Foo> newSet) { List<Foo> oldList = asSortedList(oldSet); List<Foo> newList = asSortedList(newSet); int oldIndex = 0; int newIndex = 0; // Iterate over both collections but not always in the same pace while( oldIndex < oldList.size() && newIndex < newIndex.size()) { Foo oldObject = oldList.get(oldIndex); Foo newObject = newList.get(newIndex); // Your logic here if(oldObject.getId() < newObject.getId()) { doRemove(oldObject); oldIndex++; } else if( oldObject.getId() > newObject.getId() ) { doAdd(newObject); newIndex++; } else if( oldObject.getId() == newObject.getId() && isModified(oldObject, newObject) ) { doUpdate(oldObject, newObject); oldIndex++; newIndex++; } else { ... } }// while // Check if there are any objects left in *oldList* or *newList* for(; oldIndex < oldList.size(); oldIndex++ ) { doRemove( oldList.get(oldIndex) ); }// for( oldIndex ) for(; newIndex < newList.size(); newIndex++ ) { doAdd( newList.get(newIndex) ); }// for( newIndex ) }// execute( oldSet, newSet ) /** Create sorted list from collection If you actually perform any actions on input collections than you should always return new instance of list to keep algorithm simple. */ private List<Foo> asSortedList(Collection<Foo> data) { List<Foo> resultList; if(data instanceof List) { resultList = (List<Foo>)data; } else { resultList = new ArrayList<Foo>(data); } Collections.sort(resultList) return resultList; }
回答
Apache的commons.collections库具有CollectionUtils类,该类提供了易于使用的Collection操作/检查方法,例如交集,差和联合。
org.apache.commons.collections.CollectionUtils API文档在这里。
回答
我认为最简单的方法是使用apache collections api CollectionUtils.subtract(list1,list2),只要列表的类型相同即可。
回答
为了兼容列表或者集合,我们可以使用Arrays.equals(object [],object [])。它将仅检查值。要获取Object [],我们可以使用Collection.toArray()方法。