java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap 无法转换为 java.util.LinkedHashMap

java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to java.util.LinkedHashMap

我很抱歉就这个一般性问题提出另一个问题,但是 none 我在 SO 上发现的问题似乎与我的问题密切相关。

我有一个现有的工作数据流管道,它接受 KV<Long, Iterable<TableRow>> 的对象并输出 TableRow 的对象。此代码在我们的生产环境中,运行 没有问题。我现在正在尝试使用直接运行器实施单元测试来测试此管道,但是,单元测试在到达行

时失败
LinkedHashMap<String, Object> evt = (LinkedHashMap<String, Object>) row.get(Schema.EVT);

在管道中,抛出错误信息:

java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to java.util.LinkedHashMap

现有数据流代码的简化版本如下所示:

public static class Process extends DoFn<KV<Long, Iterable<TableRow>>, TableRow> {

    /* private variables */

    /* constructor */

    /* private functions */

    @ProcessElement
    public void processElement(ProcessContext c) throws InterruptedException, ParseException {       

      EventProcessor eventProc = new EventProcessor();
      Processor.WorkItem workItem = new Processor.WorkItem();
      Iterator<TableRow> it = c.element().getValue().iterator();

      // process all TableRows having the same id
      while (it.hasNext()) {
        TableRow item = it.next();

        if (item.containsKey(Schema.EVT))
          eventProc.process(item, workItem);
        else
          /* process by different Proc class */
      }

      /* do additional logic */

      /* c.output() is somewhere far below */

    }
}

public class EventProcessor extends Processor {

    // Extract data from an event into the WorkItem
    @SuppressWarnings("unchecked")
    @Override
    public void process(TableRow row, WorkItem item) {    

        try {
          LinkedHashMap<String, Object> evt = (LinkedHashMap<String, Object>) row.get(Schema.EVT);
          LinkedHashMap<String, Object> profile = (LinkedHashMap<String, Object>) row.get(Schema.PROFILE);

          /* if no exception, process further business logic */

          /* business logic */

        } catch (ParseException e) {
            System.err.println("Bad row");
        }
    }
}      

单元测试的相关部分,准备 Process() DoFn 的主要输入,如下所示:

Map<Long, List<TableRow>> groups = new HashMap<Long, List<TableRow>>();
List<KV<Long, Iterable<TableRow>>> collections = new ArrayList<KV<Long,Iterable<TableRow>>>();    
Gson gson = new Gson();    

// populate the map with events grouped by id
for(int i = 0; i < EVENTS.length; i++) {
  TableRow row = gson.fromJson(EVENTS[i], TableRow.class);
  Long id = EVENT_IDS[i];

  if(groups.containsKey(id))
    groups.get(id).add(row);
  else
    groups.put(id, new ArrayList<TableRow>(Arrays.asList(row)));        
}

// prepare main input for pipeline
for(Long key : groups.keySet())
  collections.add(KV.of(key, groups.get(key)));

导致问题的行是 gson.fromJson(EVENTS[i], TableRow.class);,它似乎将 TableRow 的内部表示编码为错误类型的 LinkedTreeMap。

TableRow 的编码类型似乎是 com.google.gson.internal.LinkedTreeMap 而不是预期的 java.util.LinkedHashMap。有没有一种方法可以将我在单元测试中创建的 TableRow 转换为 java.util.LinkedHashMap 的正确类型,以便单元测试成功而无需对已在生产中运行的现有数据流代码进行任何更改?

这是因为 LinkedHashMap 并不优于 LinkedTreeMap 因此它们可能没有相同的方法。 Java 编译器认为以这种方式转换它可能会导致 evt 具有与 row.get(Schema.EVT) 不同的方法,从而导致不好的结果。 但是,您可以将 LinkedTreeMap 转换为 AbstractMap、Map 或 Object,因为它们都优于它。 所以(正如许多评论指出的那样)要修复它,只需使用

Map<String, Object> evt = row.get(Schema.EVT);

你应该没问题。

我会考虑(不仅是那个特定的)施法code smell。每次对转换进行编码时,都会冒 ClassCastException 发生的风险。

正如其他人所说,Map 接口可以像 Map<String, Object> evt = row.get(Schema.EVT); 一样使用。

或者,new LinkedHashMap<String, Object>(row.get(Schema.EVT)); 可以构建一个新的 LinkedHashMap

第二种方法的优点是保留 LinkedHashMap 类型,这可能重要也可能不重要,这取决于您的情况。

重新发布解决方案作为答案。

如果不使用它们的特定功能,不建议转换为具体的 类。在这种情况下,最好转换为 Map 而不是 LinkedHashMap。 Gson的LinkedTreeMap也是Map,所以应该没有问题。