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

时间:2015-05-13 15:54:46

标签: java comparator treemap

我正在尝试使用排序键创建Map,按字母顺序排序,最后按数字排序。为此,我使用TreeMap和自定义Comparator

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

3 个答案:

答案 0 :(得分:6)

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

答案 1 :(得分:1)

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

  

当且仅当c.compare(e1, e2)==0具有与e1.equals(e2)相同的布尔值时,比较器c对一组元素S施加的排序被称为与等于一致对于e1中的每个e2S

您的比较器不是这种情况:comparator.compare("0","0")会在您的情况下返回1

进一步说:

  

当使用能够强加与equals不一致的排序的比较器来排序有序集(或有序映射)时,应该小心。假设具有显式比较器c的有序集(或有序映射)与从集合S中绘制的元素(或键)一起使用。如果由S对S施加的排序与equals,排序集(或已排序)不一致()#34; 特别是有序集(或有序映射)将违反集合(或映射)的一般契约,它以等于的方式定义。

(强调我 - 你可以替换&#34;奇怪地&#34;和#34;怪异&#34;,对于你的情况; - )

关于如何实施这种比较的细节,有一定程度的自由。例如。像"123isNotNumeric"这样的密钥会发生什么?应该&#34;数字&#34;总是个位数?它们应该总是整数吗?

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

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]

答案 2 :(得分:0)

压缩机代码不正确。在treeMap.get(“0”)的情况下,不满足等式。

压缩器中的以下代码不正确并导致问题。当您从MAP获取某些元素(以查找匹配的键)时,也会调用压缩器。在“0”的情况下,你的字母数字代码返回true,如果条件返回1则跟随,所以它永远不会为“0”找到“0”相等为真,这就是返回NULL的原因。

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