引言

大家在项目中难免会遇到字符串小数的情况,这是之前在项目中遇到一个字符串小数大小比较的问题,需要获取集合中最大的字符串小数,在用 Stream 流处理时候 ide 提示使用 String 的 compareTo 方法,当时没有多想就直接这样写了,然后简单写了一个单元测试,却发现并没有得到想要的结果,所以将此问题记录了下来。

场景重现

@Test
public void testStringNumberCompare() {
    List<String> numList = Stream.of("11.29", "11.3", "2.4", "9.12", "8.5", "7.88")
            .collect(Collectors.toList());

    String maxStrNum = numList.stream()
            .max(String::compareTo)
            .orElse(null);

    System.out.println(maxStrNum);
}
9.12

问题分析

通过以上例子我们很容易的知道正确的答案是 11.3,但是实际得到的结果却是 9.12,那么到底是怎么回事呢?于是我去看了一下 String 类的 compareTo 方法的源码,通过分析源码得出 compareTo 方法比较大小的以下步骤:

  1. 获取两个字符串的长度,并得到最短字符串的长度
  2. 分别将两个字符串存放在两个字符数组中
  3. 以最短字符串长度作为循环条件依次循环比较两个字符串的每一个字符 ASCII 码值大小,如果在某次循环中两个字符的 ASCII 码值不一样则直接返回,所有字符都一样则执行下面的操作
  4. 直接比较两个字符串的长度返回

源码

public int compareTo(String anotherString) {
    int len1 = value.length;
    int len2 = anotherString.value.length;
    int lim = Math.min(len1, len2);
    char v1[] = value;
    char v2[] = anotherString.value;

    int k = 0;
    while (k < lim) {
        char c1 = v1[k];
        char c2 = v2[k];
        if (c1 != c2) {
            return c1 - c2;
        }
        k++;
    }
    return len1 - len2;
}

ASCII 码值

image-20241230173243698

从 ASCII 码值表中我们可以看到,字符 1 的码值为 49,而字符 9 的码值为 57,又因为第一次比较字符串中的字符码值大小时,字符串数字 11.3 的字符 1 码值小于字符串数字 9.83 的字符 9 的码值,故第一次比较就直接返回了,得出字符串数字 9.83 大于字符串数字 11.3。所以无论字符串数字是 111.11 还是 1111.11,与 字符串数字 9.83 通过 String 类的 compareTo 方法比较都是小于后者的。

解决方案

通过上述分析我们知道其实 String 类 compareTo 方法的底层是将字符串数字转换成字符来处理的。那么我们能不能把字符串先转换成数字,然后再来比较大小呢?

经过一番了解,发现String 类 compareTo 方法所在的接口是 Comparable,而 Comparable 接口只支持 String 类型,但是有另外一个接口 Comparator,该接口中的comparing 方法封装了对各种数据类型的大小比较。

下面是解决该问题的两种方法:

字符串数字转换为 Double

@Test
public void testStringNumberConvertDoubleCompare() {
    List<String> numList = Stream.of("11.29", "11.3", "2.4", "9.12", "8.5", "7.88")
            .collect(Collectors.toList());

    String maxDoubleNum = numList.stream()
            .max(Comparator.comparing(Double::new))
            .orElse(null);
    System.out.println(maxDoubleNum);
}
11.3

字符串数字转换为 BigDecimal

@Test
public void testStringNumberConvertBigDecimalCompare() {
    List<String> numList = Stream.of("11.29", "11.3", "2.4", "9.12", "8.5", "7.88")
            .collect(Collectors.toList());

    String maxBigDecimalNum = numList.stream()
            .max(Comparator.comparing(BigDecimal::new))
            .orElse(null);
    System.out.println(maxBigDecimalNum);
}
11.3

总结

大家在写代码的过程中一定不要贪图一时方便,不然很有可能造成生产问题,还有写完一定要进行单元测试,这样也能避免出现问题。