如何在 java 中编写自定义比较器?

How to write custom comparator in java?

我想在 TreeMap 中存储键值对,并按照以下逻辑根据键的值对条目进行排序:

  1. “类型”(不区分大小写)键应该在第一位。
  2. 以“元数据”开头的键(不区分大小写)最后应按升序排列
  3. 其余键(不区分大小写)应按升序排列在中间

我正在使用 Java 8 版本。

节目:

public class CustomeCamarator  {
    public static void main(String[] args) {
        CustomComparator comparator = new CustomComparator();
        Map<String,Object> empData=new TreeMap<>(comparator);
        empData.put("name","someName");
        empData.put("DOB","someDOB");
        empData.put("address","someAddress");
        empData.put("type","employee data");
        empData.put("ContactNo.","someContactNumber");
        empData.put("metadata.source","someMetaDataSource");
        empData.put("metadata.location","someMetaDataLocation");
        empData.put("metadata","someMetaData");
        System.out.println(empData);
        System.out.println(empData.containsKey("metadata"));//should be true but showing false
    }
}
class CustomComparator implements Comparator<String>{
    @Override
    public int compare(String o1, String o2) {
        String str1 = o1.toLowerCase();
        String str2 = o2.toLowerCase();
        if(str1.equalsIgnoreCase("type")) {
            return -1;
        }else if(!str1.contains("metadata") && !str2.contains("metadata")) {
            return str1.compareTo(str2);
        }else if(o1.contains("metadata") && !o2.contains("metadata")) {
            return 1;
        }else if(!o1.contains("metadata") && o2.contains("metadata")) {
            return -1;
        }else {
            return 1;
        }
    }
}





**Expected Output like this:**
type: someType
address: someAddress
ContactNo: someContactNumber
DOB: someDOB
name: someName
metadata: someMetaData
metadata.location: someMetaDataLocation
metadata.source: someMetaDataSource

TreeMap supports a custom Comparator in its constructor。只需实现 Comparator 接口并将其传递给构造函数即可。

您应该将实现该逻辑的比较器传递给 TreeMap 构造函数:

Map<String, Integer> order = Map.of(
    "type", Integer.MIN_VALUE, 
    "metadata", Integer.MAX_VALUE);

Map<String, Object> map = new TreeMap<>(
    Comparator.comparing(a -> order.getOrDefault(
        a.toLowerCase().startsWith("metadata") ?
            "metadata" :
            a.toLowerCase(), 
        0))
    .thenComparing(Comparator.naturalOrder()));

我认为应该这样做。这个想法是使用地图首先比较一些 Integer 值。 type对应的值是Integer的最小值,所以它总是排在第一位。 metadata 的值是 Integer 的最大值,因此它总是排在最后。在地图中搜索之前,我们首先检查字符串是否以 metadata 开头。确实如此,我们只需将其更改为 metadata,即可从地图中获取 Integer 值。如果地图中没有条目,我们 return 0。如果有平局,我们使用 String 的自然顺序。


编辑: 如果您使用 Java8 并且不能使用 Map.of,请考虑使用传统的 HashMap

Map<String, Integer> order = new HashMap<>();
order.put("type", Integer.MIN_VALUE);
order.put("metadata", Integer.MAX_VALUE);

我用了一个Decision Table如下

type  metadata.*  others
2     1           0

Key 1    Key 2    Result    Action
  0        0        0       String.compare
  0        1        1       return -1
  0        2        2       return 1
  1        0        3       return 1
  1        1        4       String.compare
  1        2        5       return 1
  2        0        6       return -1
  2        1        7       return -1
  2        2        8       return 0

Key 1Key 2是接口Comparator的方法compare()的参数。每个键可以具有三个值之一:

  1. 类型
  2. [开头为] 元数据
  3. 以上
  4. none

实现如下:

Comparator<String> comparator = (k1, k2) -> {
    Objects.requireNonNull(k1);
    Objects.requireNonNull(k2);
    int k1Val;
    int k2Val;
    if (k1.equalsIgnoreCase("type")) {
        k1Val = 2;
    }
    else if (k1.matches("(?i)^metadata.*$")) {
        k1Val = 1;
    }
    else {
        k1Val = 0;
    }
    if (k2.equalsIgnoreCase("type")) {
        k2Val = 2;
    }
    else if (k2.matches("(?i)^metadata.*$")) {
        k2Val = 1;
    }
    else {
        k2Val = 0;
    }
    int retVal;
    int index = k1Val * 3 + k2Val;
    switch (index) {
        case 0:
        case 4:
            retVal = k1.compareToIgnoreCase(k2);
            break;
        case 1:
        case 6:
        case 7:
            retVal = -1;
            break;
        case 2:
        case 3:
        case 5:
            retVal = 1;
            break;
        case 8:
            retVal = 0;
            break;
        default:
            throw new RuntimeException("Unhandled: " + index);
    }
    return retVal;
};
Map<String, Object> empData = new TreeMap<>(comparator);
empData.put("name","someName");
empData.put("address","someAddress");
empData.put("type","employee data");
empData.put("ContactNo.","someContactNumber");
empData.put("Metadata.source","someMetaDataSource");
empData.put("metadata.location","someMetaDataLocation");
empData.put("metadata","someMetaData");
System.out.println(empData);

上述代码 运行 的输出。

{type=employee data, address=someAddress, ContactNo.=someContactNumber, name=someName, metadata=someMetaData, metadata.location=someMetaDataLocation, Metadata.source=someMetaDataSource}

我认为以下涵盖了您的案例

public class CustomComparator implements Comparator<String> {
  @Override public int compare(String left, String right) {
    left = left.toLowerCase();
    right = right.toLowerCase();
    final int LEFT = -1;
    final int RIGHT = 1;
    // exact match!
    if (left.equals(right)) return 0;
    // not identical, so consider 'type' match
    if ("type".equals(left)) return LEFT;
    if ("type".equals(right)) return RIGHT;
    // at this point we know neither matches 'type' so lets check for 'metadata' prefix
    if (left.startsWith("metadata")) {
      // if both start with metadata use natural ordering
      if (right.startsWith("metadata")) return left.compareTo(right);
      // only left starts with 'metadata' so right comes first
      else return RIGHT;
    }
    // only right starts with 'metadata' so left comes first
    if (right.startsWith("metadata")) return LEFT;
    // at this point we know they are not equal but neither contains 'text' nor starts with 'metadata' so use natural ordering
    return left.compareTo(right);
  }
}