Java 11中如何让en-US-POSIX在Locale LanguageRange优先级中排于en-US之后?
解决Java 11中Locale.filter排序不符合预期的问题
这个问题的根源在于Locale.filter()的默认逻辑:它会优先根据Locale的具体程度排序(更具体的Locale在前),而不是严格遵循你定义的LanguageRange的权重或顺序。在你的例子里,en-US-POSIX虽然第二个range的权重是0.9,但它同时匹配了第一个权重为1.0的en-US范围,所以两者的最高匹配权重相同——这时候默认逻辑会把更具体的en-US-POSIX排在前面,导致和你的预期相反。
要实现en-US在前、en-US-POSIX在后的排序,你可以通过自定义排序逻辑来覆盖默认行为,这里提供两种实用的方案:
方案一:按匹配的LanguageRange权重+精确匹配优先排序
这种方案优先保证高权重的Locale在前,权重相同时让精确匹配的Locale(和range完全一致)排在前缀匹配的Locale前面:
import java.util.*; public class LocaleSortExample { public static void main(String[] args) { List<LanguageRange> ranges = LanguageRange.parse("en-US;q=1.0,en-US-POSIX;q=0.9"); List<Locale> availableLocales = Arrays.asList(Locale.getAvailableLocales()); // 先过滤出符合条件的Locale List<Locale> filteredLocales = Locale.filter(ranges, availableLocales); // 自定义排序逻辑 filteredLocales.sort((locale1, locale2) -> { // 获取每个Locale匹配到的最高权重 double maxWeight1 = ranges.stream() .filter(range -> LanguageRange.matches(range, locale1)) .mapToDouble(LanguageRange::getWeight) .max() .orElse(0.0); double maxWeight2 = ranges.stream() .filter(range -> LanguageRange.matches(range, locale2)) .mapToDouble(LanguageRange::getWeight) .max() .orElse(0.0); // 先按权重降序排序 int weightCompare = Double.compare(maxWeight2, maxWeight1); if (weightCompare != 0) { return weightCompare; } // 权重相同时,精确匹配的Locale在前 boolean isExactMatch1 = ranges.stream() .anyMatch(range -> range.getRange().equals(locale1.toLanguageTag())); boolean isExactMatch2 = ranges.stream() .anyMatch(range -> range.getRange().equals(locale2.toLanguageTag())); return Boolean.compare(isExactMatch2, isExactMatch1); }); // 输出结果:en-US, en-US-POSIX filteredLocales.forEach(System.out::println); } }
方案二:按LanguageRange的定义顺序排序
如果你希望严格遵循你编写的LanguageRange顺序(先出现的range对应的Locale优先),可以先匹配每个Locale对应的第一个range,再按range的索引排序:
import java.util.*; public class LocaleSortExample2 { public static void main(String[] args) { List<LanguageRange> ranges = LanguageRange.parse("en-US;q=1.0,en-US-POSIX;q=0.9"); List<Locale> availableLocales = Arrays.asList(Locale.getAvailableLocales()); List<Locale> filteredLocales = Locale.filter(ranges, availableLocales); filteredLocales.sort((locale1, locale2) -> { // 找到每个Locale匹配的第一个LanguageRange的索引 int rangeIndex1 = ranges.stream() .filter(range -> LanguageRange.matches(range, locale1)) .findFirst() .map(ranges::indexOf) .orElse(Integer.MAX_VALUE); int rangeIndex2 = ranges.stream() .filter(range -> LanguageRange.matches(range, locale2)) .findFirst() .map(ranges::indexOf) .orElse(Integer.MAX_VALUE); // 先按range的出现顺序排序 int indexCompare = Integer.compare(rangeIndex1, rangeIndex2); if (indexCompare != 0) { return indexCompare; } // 同一range下,精确匹配的Locale在前 boolean exactMatch1 = locale1.toLanguageTag().equals(ranges.get(rangeIndex1).getRange()); boolean exactMatch2 = locale2.toLanguageTag().equals(ranges.get(rangeIndex1).getRange()); return Boolean.compare(exactMatch2, exactMatch1); }); // 输出结果:en-US, en-US-POSIX filteredLocales.forEach(System.out::println); } }
关键说明
- 默认的
Locale.filter()排序逻辑:当多个Locale匹配到相同权重的range时,**更具体的Locale(子标签更多)**会被优先返回,这就是你看到en-US-POSIX在前的原因。 - 两种方案的核心都是在过滤后,通过自定义
Comparator重新调整顺序,让结果符合你的业务预期。
内容的提问来源于stack exchange,提问作者eastwater
相关产品推荐
相关产品推荐

