我有一个Java对象的ArrayList。这些对象有四个字段,其中两个我用来将对象视为另一个。我正在寻找最有效的方法,给定这两个字段,看看数组是否包含该对象。
扳手是这些类是基于XSD对象生成的,所以我不能修改类本身来覆盖.equals
。
有没有更好的方法,只需循环并手动比较每个对象的两个字段,然后在找到时断开?这看起来很混乱,寻找更好的方式。
编辑: ArrayList来自一个解组到对象中的SOAP响应。
答案 0 :(得分:101)
这取决于你需要的东西的效率。简单地遍历列表寻找满足某个条件的元素是O(n),但是如果你可以实现Equals方法,那么ArrayList.Contains也是如此。如果你不是在循环或内循环中这样做,这种方法可能就好了。
如果你真的需要不惜一切代价提高效率,你需要做两件事:
当然,构建此HashSet仍然会产生O(n)成本。如果构建HashSet的成本与您需要执行的所有contains()检查的总成本相比可以忽略不计,那么您将获得任何收益。试图建立一个没有重复的列表就是这种情况。
答案 1 :(得分:37)
您可以使用带有Java内置方法的Comparator进行排序和二分查找。假设您有一个这样的类,其中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);
答案 2 :(得分:10)
考虑到你的限制,你会遇到暴力搜索(或者如果重复搜索就会创建索引)。你能详细说明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);
}
});
答案 3 :(得分:6)
如果列表为sorted,您可以使用binary search。如果没有,那就没有更好的方法了。
如果你这么做很多,那么第一次对列表进行排序几乎肯定是值得的。由于您无法修改类,因此您必须使用Comparator
进行排序和搜索。
答案 4 :(得分:4)
即使equals方法 比较这两个字段,那么从逻辑上讲,它与您手动执行的代码完全相同。好吧,它可能是“凌乱”,但它仍然是正确的答案
答案 5 :(得分:4)
如果您是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();
答案 6 :(得分:2)
有没有更好的方法,只需循环并手动比较每个对象的两个字段,然后在找到时断开?这看起来很混乱,寻找更好的方法。
如果您的关注点是可维护性的,那么您可以执行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建议会更好更容易实现(尽管这样的事情会更有效)
告诉我们你最后做了什么。
答案 7 :(得分:2)
答案 8 :(得分:1)
从性能角度来看,基于字段值作为键构建这些对象的HashMap是值得的,例如:填充地图一次并非常有效地查找对象
答案 9 :(得分:1)
如果您需要在同一个列表中搜索很多时间,那么建立索引可能会有所收获。
迭代一遍,构建一个HashMap,其中包含您要查找的等于的值以及作为值的相应节点。如果您需要all而不是给定equals值中的任何一个,那么让map具有值类型的list并在初始迭代中构建整个列表。
请注意,在执行此操作之前,您应该进行测量,因为构建索引的开销可能会超出遍历,直到找到预期的节点。
答案 10 :(得分:1)
有三个基本选项:
1)如果检索性能至关重要且实际可行,请使用一次构建的哈希表(如果列表更改,则更改为哈希表)。
2)如果方便地对List进行排序或者对它进行排序是可行的,并且O(log n)检索就足够了,可以进行排序和搜索。
3)如果O(n)检索足够快或者操作/维护数据结构或替换是不切实际的,则迭代List。
在编写比List上的简单迭代更复杂的代码之前,值得思考一些问题。
为什么需要不同的东西? (时间)表现?优雅?可维护性?重用?所有这些都是好的理由,除了或者在一起,但它们会影响解决方案。
您对相关数据结构有多少控制权?你能影响它的构建方式吗?稍后管理?
数据结构(和底层对象)的生命周期是什么?它是一次性构建的,永远不会改变,还是高度动态的?您的代码可以监控(甚至改变)其生命周期吗?
是否存在其他重要限制因素,例如内存占用?有关重复的信息是否重要?等
答案 11 :(得分:0)
我想说最简单的解决方案是包装对象并将contains调用委托给包装类的集合。这与比较器类似,但不会强制您对生成的集合进行排序,您只需使用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();
}
}