简体   繁体   English

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

[英]Java TreeMap custom comparator weird behaviour

I am trying to create a Map with sorted keys, sorted according to alphabetically first, and numerical last. 我正在尝试使用排序键创建一个Map ,按照字母顺序排序,最后按数字排序。 For this I am using a TreeMap with a custom Comparator : 为此,我使用了一个带有自定义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));
}

I've wrote the following unit test to illustrate what goes wrong: 我写了下面的单元测试来说明出了什么问题:

@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"));
}

With result: 结果如下:

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

Check your comparator code. 检查比较器代码。 Does comparing "0" and "0" return 0, as it should? 比较“0”和“0”是否会返回0,应该如何? No it doesn't, since you don't check for equality if your string starts with a digit. 不,它没有,因为如果您的字符串以数字开头,则不检查是否相等。 You also don't return proper ordering if two strings both start with digits. 如果两个字符串都以数字开头,您也不会返回正确的顺序。

There are some requirements for a valid implementation of a Comparator . 有效实现Comparator有一些要求。 Quoting from the documentation: 引用文档:

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 . 当且仅当c.compare(e1, e2)==0具有与每个e1 e1.equals(e2)相同的布尔值时,比较器c对一组元素S施加的排序被称为与等于一致。 S e2e2

This is not the case for your comparator: comparator.compare("0","0") will return 1 in your case. 您的比较器不是这种情况:在您的情况下, comparator.compare("0","0")将返回1

And further: 并进一步:

Caution should be exercised when using a comparator capable of imposing an ordering inconsistent with equals to order a sorted set (or sorted map). 当使用能够强加与equals不一致的排序的比较器来排序有序集(或有序映射)时,应该谨慎行事。 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." 假设带有显式比较器c的有序集(或有序映射)与从集合S中绘制的元素(或键)一起使用。 如果由S对S施加的排序与equals不一致,则排序集(或有序映射)将表现得“奇怪”。 In particular the sorted set (or sorted map) will violate the general contract for set (or map), which is defined in terms of equals. 特别是有序集(或有序映射)将违反集合(或映射)的一般契约,其以等于的方式定义。

(emphasis by me - you may replace "strangely" with "weird", for your case ;-)) (我强调 - 对于你的情况,你可以用“怪异”代替“奇怪”;-))

There are some degrees of freedom regarding the details of how such a comparator could be implemented. 关于如何实施这种比较的细节有一定程度的自由。 Eg what should happen for keys like "123isNotNumeric" ? 例如,对于像"123isNotNumeric"这样的键会发生什么? Should the "numbers" always be single digits? “数字”应该是单个数字吗? Should they always be integers? 它们应该总是整数吗?

However, one possible implementation may look like this: 但是,一种可能的实现可能如下所示:

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;
                }
            }
        };
}

Printing the keySet() of the map prints the keys in the desired order: 打印地图的keySet()按所需顺序打印键:

[a, b, 0, 1]

Compactor code is not correct. 压缩机代码不正确。 In case of treeMap.get("0") equality is not satisfied. 在treeMap.get(“0”)的情况下,不满足等式。

The following code in compactor is not correct and causing issue for you. 压缩器中的以下代码不正确并导致问题。 The compactor is also called when you fetch some element from MAP(to find matching key ). 当您从MAP获取某些元素(以查找匹配的键)时,也会调用压缩器。 In case of "0" your alphanumeric code return true and following if condition return 1 , So it never found "0" equality to true for "0" that is why return NULL. 在“0”的情况下,你的字母数字代码返回true,如果条件返回1则跟随,所以它永远不会为“0”找到“0”相等为真,这就是返回NULL的原因。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM