来自主要是 C++ 背景的我现在正在愤怒地编写一些 Java。我发现使用 STL 在 C++ 中基本的东西在 Java 中似乎比我认为的更麻烦。我的结论是,可能有一个更好的 Java 习惯用法我还没有掌握。这是一个使用伪代码的示例。
我有一组基于某些恰好是字符串的成员变量具有自然排序关系的事物。
class Thing
{
String key1;
String key2;
}
在 C++ 中,我可能会定义一个排序运算符<(Thing,Thing) 并将它们放在 std::set 中。例如
///
/// @brief
/// provide a total order for 'Things' using key1 and key2
///
bool operator<(const Thing& a, const Thing& b)
{
if (a.key1 < b.key1) return true;
else if (a.key1 > b.key1) return false;
else return a.key2 < b.key2;
}
然后,我可以使用 set::find 在 O(log N) 时间内找到元素,以应对拥有事物的情况。使用 operator<() 的额外重载。我可以使用 std::lower_bound 或 std::equal_range 搜索只有 key1 或同时拥有 key1 和 key2。例如:
struct Ordering
{
/// A strict weak ordering not a total ordering
bool operator()(const Thing& A,const std::string& key1) const;
}
const_iterator iter = std::lower_bound(someThings.begin(),
someThings.end(),
key1,
Ordering());
为了不那么抽象,想象 key1 是名称,key2 是版本。我可以问我们是否有任何名为 Foobar 的软件,或者更具体地说,我们是否有 Foobar v1.0。
从表面上看,Java 中 std::set 最直接的等价物似乎是 TreeSet ,可以通过继承 Comparator 接口来实现排序。然而,对于我所说的,看起来需要多个地图才能在 Java 中执行此操作。在 C++ 中,如果我想更改值,只会费心使用像 std::map 这样的关联容器。在 C++ std::set 中,就像在 Java TreeSet 中一样,值是它自己的键。但是,在 C++ 中,我可以编写比较器来使用 key1 或 key2 将“Thing”与“std::string”进行比较,并在它们的 std::set 中找到特定的东西。在我看来,您必须使用 Map 在 Java 中执行此操作。否则(因为 Comparator 只有一个类型参数)你最终会遇到这样的混乱:
public static class Order implements Comparator<Object>
{
@Override
@Constant
public int compare(Object a, Object b)
{
String aString;
String bString;
if (a instanceof String)
{
aString = (String)a;
}
else if (a instanceof Thing)
{
aString = ((Field)a).getKey1();
}
else
{
throw new ClassCastException("String or Field object expected.");
}
if (b instanceof String)
{
bString = (String)b;
}
else if (b instanceof Thing)
{
bString = ((Field)b).getKey1();
}
else
{
throw new ClassCastException("String or Field object expected.");
}
return aString.compareTo(bString);
}
};
但是,如果您这样做,您可以(在 Thing 类中)编写:
Set<Thing> things = new TreeSet<Thing>(new Order());
boolean hasFieldWithKey1(final String key1)
{
return this.fields.contains(key1);
}
使用 Java Set,您只能测试是否存在,但不能检索您正在搜索的对象。例如你不能做
Field getFieldWithKey1(final String key1)
{
return this.fields.floor(key1);
}
因为像 floor() 这样的方法只接受值类型的对象(即 Thing)
显而易见的解决方案是为每个键使用一个 Map。
Map<String,Thing> thingsByKey1 = new TreeMap<Thing>(new Order());
来自 C++ 背景,这似乎不必要地臃肿。当东西已经包含它时,为什么我要再次存储它?如果我有两把钥匙,那就更糟了。我需要两张地图。
Map<String,Thing> thingsByKey1 = new TreeMap<Thing>(new OrderByKey1());
Map<String,Thing> thingsByKey2 = new TreeMap<Thing>(new OrderByKey2());
我现在不仅要复制密钥,还要创建额外的不必要的树数据结构(或具有更好运行时性能的 HashMap)。对于上面的排序实现,这也可能是“完全错误的”,因为每个键本身仅形成部分顺序,而不是一组事物的总顺序。
我已经看到这里使用线性搜索回答了有关搜索的问题,这几乎总是最糟糕的选择。例如
我注意到有一个 BinarySearch 版本接受 Comparator 对象作为参数,但返回元素的索引而不是元素本身。这意味着在使用它之后对 get() 进行不必要的调用(假设集合支持它)。
那么在时间和空间上有效地做到这一点的 Java 方法是什么?