
本文介绍如何对形如 "1D"、"23M" 的字符串列表进行自定义排序:先按末尾字母升序排列,再在相同字母组内按前面数字升序排列。使用 Java 8+ 的 Comparator.comparing 链式方法可简洁、安全、高效地实现该逻辑。
本文介绍如何对形如 "1d"、"23m" 的字符串列表进行自定义排序:先按末尾字母升序排列,再在相同字母组内按前面数字升序排列。使用 java 8+ 的 `comparator.comparing` 链式方法可简洁、安全、高效地实现该逻辑。
要正确排序类似 (2D, 1D, 1Y, 23M, 4M, 2Y, 12D) 的字符串列表,并得到期望结果 (1D, 2D, 12D, 4M, 23M, 1Y, 2Y),关键在于理解排序规则的两层优先级:
-
第一优先级:按字符串末尾的字母(如
'D','M','Y')字典序升序; -
第二优先级:在字母相同的情况下,将前面的数字部分(如
"1","12","23")解析为整数后升序排列。
原始代码失败的主要原因有三:
-
s.split("")将字符串拆分为单字符数组,[1]取第二个字符——但"1D"长度为 2,索引1是'D',而"23M"长度为 3,索引1是'3',逻辑错乱; - 未校验字符串格式,
Integer.parseInt在非数字前缀时会抛出NumberFormatException; - 未处理空字符串或长度不足的异常边界情况。
✅ 推荐写法(Java 8+,简洁且健壮):
myList.sort(
Comparator.comparing((String s) -> s.charAt(s.length() - 1))
.thenComparingInt(s -> Integer.parseInt(s.substring(0, s.length() - 1)))
);该写法利用函数式接口链式组合:
-
comparing(...)提取并比较末尾字符(自动按char的 Unicode 值排序,等价于字母序); -
thenComparingInt(...)在首级相同时,提取前n−1个字符并转为int进行数值比较(避免字符串字典序误判"12D""4M" 的问题)。
⚠️ 注意事项:
- 确保所有字符串非空、长度 ≥ 2,且末尾为字母、前面部分可转为有效整数;否则需添加预处理或异常处理。生产环境建议封装为安全版本:
myList.sort((s1, s2) -> {
if (s1 == null || s2 == null || s1.length() < 2 || s2.length() < 2) {
return Objects.compare(s1, s2, String::compareTo);
}
char c1 = s1.charAt(s1.length() - 1);
char c2 = s2.charAt(s2.length() - 1);
if (c1 != c2) return Character.compare(c1, c2);
try {
int n1 = Integer.parseInt(s1.substring(0, s1.length() - 1));
int n2 = Integer.parseInt(s2.substring(0, s2.length() - 1));
return Integer.compare(n1, n2);
} catch (NumberFormatException e) {
return s1.compareTo(s2); // 回退到字典序
}
});总结:合理利用 Comparator 的组合能力,结合明确的提取逻辑(charAt(length-1) + substring(0, length-1)),即可优雅实现“字母后缀优先、数字前缀次之”的双级排序,既避免手动类型转换错误,又提升代码可读性与可维护性。

















