我在Java中有一个ArrayList对象。这些对象有四个字段,我将其中两个用于考虑该对象与另一个字段相等。给定这两个字段,我正在寻找最有效的方法,以查看数组是否包含该对象。
棘手的是,这些类是基于XSD对象生成的,因此我无法修改这些类本身以覆盖.equals
。
有没有比遍历并手动比较每个对象的两个字段然后在找到时中断的更好的方法了?寻找更好的方法,这看起来太混乱了。
编辑: ArrayList来自未编组到对象中的SOAP响应。
Answers:
这取决于您需要的效率如何。简单地遍历列表以查找满足特定条件的元素是O(n),但ArrayList也是如此。包含是否可以实现Equals方法。如果您不在循环或内部循环中执行此操作,则此方法可能很好。
如果您确实不惜一切代价需要非常高效的查找速度,则需要做两件事:
当然,构建此HashSet仍然需要O(n)成本。如果构建HashSet的成本与需要执行的所有contains()检查的总成本相比可以忽略不计,那么您将只会获得任何收益。尝试建立没有重复的列表就是这种情况。
您可以将Comparator与Java的内置方法结合使用以进行排序和二进制搜索。假设您有一个类似的类,其中a和b是您要用于排序的字段:
class Thing { String a, b, c, d; }
您将定义比较器:
Comparator<Thing> comparator = new Comparator<Thing>() {
public int compare(Thing o1, Thing o2) {
if (o1.a.equals(o2.a)) {
return o1.b.compareTo(o2.b);
}
return o1.a.compareTo(o2.a);
}
};
然后对您的列表进行排序:
Collections.sort(list, comparator);
最后进行二进制搜索:
int i = Collections.binarySearch(list, thingToFind, comparator);
考虑到您的限制,您将无法进行强力搜索(如果重复搜索,则将创建索引)。您能否详细说明如何ArrayList
生成-也许那里有一些摆动的空间。
如果您要查找的只是漂亮的代码,请考虑使用Apache Commons Collections类,尤其是CollectionUtils.find()来获取现成的语法糖:
ArrayList haystack = // ...
final Object needleField1 = // ...
final Object needleField2 = // ...
Object found = CollectionUtils.find(haystack, new Predicate() {
public boolean evaluate(Object input) {
return needleField1.equals(input.field1) &&
needleField2.equals(input.field2);
}
});
如果列表已排序,则可以使用二进制搜索。如果没有,那就没有更好的办法了。
如果您经常执行此操作,则几乎可以肯定,第一次进行列表排序是值得的。由于您无法修改类,因此必须使用aComparator
进行排序和搜索。
即使equals方法正在比较这两个字段,从逻辑上讲,它也将与您手动执行的代码相同。好的,可能是“混乱”,但这仍然是正确的答案
如果您是我的ForEach DSL的用户,则可以通过Detect
查询来完成。
Foo foo = ...
Detect<Foo> query = Detect.from(list);
for (Detect<Foo> each: query)
each.yield = each.element.a == foo.a && each.element.b == foo.b;
return query.result();
有没有比遍历并手动比较每个对象的两个字段然后在找到时中断的更好的方法了?寻找更好的方法,这看起来太混乱了。
如果您关心的是可维护性,则可以执行Fabian Steeg的建议(这就是我会做的),尽管它可能不是“最有效的”(因为您必须先对数组进行排序,然后执行二进制搜索),但肯定是最干净的更好的选择。
如果您确实关心效率,则可以创建一个自定义List实现,该实现将对象中的字段用作哈希,并使用HashMap作为存储。但这可能太多了。
然后,必须将填充数据的位置从ArrayList更改为YourCustomList。
喜欢:
List list = new ArrayList();
fillFromSoap( list );
至:
List list = new MyCustomSpecialList();
fillFromSoap( list );
该实现将类似于以下内容:
class MyCustomSpecialList extends AbstractList {
private Map<Integer, YourObject> internalMap;
public boolean add( YourObject o ) {
internalMap.put( o.getThatFieldYouKnow(), o );
}
public boolean contains( YourObject o ) {
return internalMap.containsKey( o.getThatFieldYouKnow() );
}
}
就像HashSet一样,这里的问题是HashSet依赖于hashCode方法的良好实现,而您可能没有。取而代之的是使用“您知道的字段”作为散列,它使一个对象等于另一个对象。
当然,从头开始实现List的难度比上面的代码段要难得多,这就是为什么我要说Fabian Steeg建议会更好,更容易实现(尽管这样会更有效)
告诉我们您最后做了什么。
如果您需要在同一列表中搜索很多时间,则可能需要建立一个索引。
迭代一次,并使用您要查找的equals值作为键并使用适当的节点作为值来构建HashMap。如果您需要全部而不是给定的equals值中的任何一个,那么让映射具有列表的值类型,并在初始迭代中构建整个列表。
请注意,在执行此操作之前,您应该先进行测量,因为建立索引的开销可能会遍历直到找到所需的节点为止。
有三个基本选项:
1)如果检索性能是最重要的并且实际可行,则使用一次构建的哈希表形式(并在// List更改时更改)。
2)如果列表方便地排序或对其进行排序并且O(log n)检索就足够了,则进行排序和搜索。
3)如果O(n)检索足够快,或者操作/维护数据结构或备用结构不切实际,请遍历List。
在编写比列表上的简单迭代复杂的代码之前,值得考虑一些问题。
为什么需要一些不同的东西?(时间)表现?优雅?可维护性?重复使用?所有这些都是好的理由,无论是分开还是一起,但它们都会影响解决方案。
您对所讨论的数据结构有多少控制权?您能影响它的建造方式吗?以后管理?
数据结构(和基础对象)的生命周期是什么?它是一次建立起来却永不改变,还是高度动态化?您的代码可以监视(甚至更改)其生命周期吗?
还有其他重要限制,例如内存占用量吗?有关重复的信息重要吗?等等。
我会说最简单的解决方案是包装对象并将包含调用委托给包装的类的集合。这类似于比较器,但不强制您对结果集合进行排序,您只需使用ArrayList.contains()。
public class Widget {
private String name;
private String desc;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
}
public abstract class EqualsHashcodeEnforcer<T> {
protected T wrapped;
public T getWrappedObject() {
return wrapped;
}
@Override
public boolean equals(Object obj) {
return equalsDelegate(obj);
}
@Override
public int hashCode() {
return hashCodeDelegate();
}
protected abstract boolean equalsDelegate(Object obj);
protected abstract int hashCodeDelegate();
}
public class WrappedWidget extends EqualsHashcodeEnforcer<Widget> {
@Override
protected boolean equalsDelegate(Object obj) {
if (obj == null) {
return false;
}
if (obj == getWrappedObject()) {
return true;
}
if (obj.getClass() != getWrappedObject().getClass()) {
return false;
}
Widget rhs = (Widget) obj;
return new EqualsBuilder().append(getWrappedObject().getName(),
rhs.getName()).append(getWrappedObject().getDesc(),
rhs.getDesc()).isEquals();
}
@Override
protected int hashCodeDelegate() {
return new HashCodeBuilder(121, 991).append(
getWrappedObject().getName()).append(
getWrappedObject().getDesc()).toHashCode();
}
}