Java TreeMap 自定义比较器奇怪的行为

Java TreeMap custom comparator weird behaviour

我正在尝试创建一个 Map,其中包含已排序的键,先按字母顺序排序,最后按数字排序。为此,我使用带有自定义 ComparatorTreeMap

public static Comparator<String> ALPHA_THEN_NUMERIC_COMPARATOR =
    new Comparator<String> () {

        @Override
        public int compare(String first, String second) {
            if (firstLetterIsDigit(first)) {
                return 1;
            } else if (firstLetterIsDigit(second)) {
                return -1;
            }
            return first.compareTo(second);
        }
    };

private static boolean firstLetterIsDigit(String string) {
    return (string == null) ? false : Character.isDigit(string.charAt(0));
}

我编写了以下单元测试来说明问题所在:

@Test
public void testNumbericallyKeyedEntriesCanBeStored() {
    Map<String, String> map = new HashMap<>();
    map.put("a", "some");
    map.put("0", "thing");
    TreeMap<String, String> treeMap = new TreeMap<>(ALPHA_THEN_NUMERIC_COMPARATOR);
    treeMap.putAll(map);

    assertEquals("some", treeMap.get("a"));
    assertEquals("thing", treeMap.get("0"));
}

结果:

java.lang.AssertionError: 
Expected :thing
Actual   :null

检查您的比较器代码。比较“0”和“0”return 0 是否应该如此?不,它不是,因为如果您的字符串以数字开头,您不会检查是否相等。如果两个字符串都以数字开头,您也不会 return 正确排序。

Comparator 的有效实施有一些要求。引用文档:

The ordering imposed by a comparator c on a set of elements S is said to be consistent with equals if and only if c.compare(e1, e2)==0 has the same boolean value as e1.equals(e2) for every e1 and e2 in S.

您的比较器不是这种情况:comparator.compare("0","0") 将 return 1 用于您的情况。

还有:

Caution should be exercised when using a comparator capable of imposing an ordering inconsistent with equals to order a sorted set (or sorted map). Suppose a sorted set (or sorted map) with an explicit comparator c is used with elements (or keys) drawn from a set S. If the ordering imposed by c on S is inconsistent with equals, the sorted set (or sorted map) will behave "strangely." In particular the sorted set (or sorted map) will violate the general contract for set (or map), which is defined in terms of equals.

(我强调 - 您可以将 "strangely" 替换为 "weird",适合您的情况 ;-))

关于如何实现这种比较器的细节有一定的自由度。例如。 "123isNotNumeric" 这样的键应该怎么办? "numbers" 应该总是个位数吗?他们应该总是整数吗?

但是,一种可能的实现方式可能如下所示:

public class SpacialTreeSetComparator
{
    public static void main(String[] args)
    {
        TreeMap<String, String> map = new TreeMap<String, String>(
            ALPHA_THEN_NUMERIC_COMPARATOR);
        map.put("b", "x");
        map.put("a", "x");
        map.put("1", "x");
        map.put("0", "x");
        System.out.println(map.keySet());
    }
    public static Comparator<String> ALPHA_THEN_NUMERIC_COMPARATOR =
        new Comparator<String> () {

            @Override
            public int compare(String first, String second) {

                Double firstNumber = asNumber(first);
                Double secondNumber = asNumber(second);
                if (firstNumber != null && secondNumber != null)
                {
                    return firstNumber.compareTo(secondNumber);
                }
                if (firstNumber != null)
                {
                    return 1;
                }
                if (secondNumber != null)
                {
                    return -1;
                }
                return first.compareTo(second);
            }
            private Double asNumber(String string)
            {
                try
                {
                    return Double.parseDouble(string);
                }
                catch (NumberFormatException e)
                {
                    return null;
                }
            }
        };
}

打印地图的 keySet() 会按所需顺序打印键:

[a, b, 0, 1]

Compactor 代码不正确。如果 treeMap.get("0") 不满足等式。

压缩器中的以下代码不正确,给您带来了问题。当您从 MAP 中获取某些元素(以查找匹配的键)时,也会调用压缩器。在“0”的情况下,您的字母数字代码 return true 和以下 if condition return 1 ,因此它从未发现“0”等于“0”的 true 这就是为什么 return NULL。

if (firstLetterIsDigit(first)) {
                return 1;
            } else if (firstLetterIsDigit(second)) {
                return -1;
            }