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

5

很抱歉又提出一个关于这个普遍问题的问题,但是我在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 无法转换为 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,以便在不对已在生产环境中运行的现有数据流代码进行任何更改的情况下使单元测试成功?

@RomanPuchkovskiy 老实说,我不确定。将 LinkedHashMap 强制转换为现有代码已经存在一段时间了。将强制转换更改为 Map 会有什么后果吗? - Max
没错。针对接口编写代码,用Map替换LinkedHashMap即可。这样你就不需要关心API返回的是哪种映射实现了。 - Mick Mnemonic
@Max 如果 LinkedTreeMapMap 的子类,那就不应该有问题,除非你使用了该类的特定功能。但我认为你最好的选择是尝试一下,看看是否可行。 - Andrei Sfat
这是一个 AbstractMap,因此它也是一个 Map。如果以下代码没有使用到 LinkedHashMapHashMap 的特定内容,那么应该是可以的。但当然,我会先进行测试。 请参考:https://github.com/google/gson/blob/master/gson/src/main/java/com/google/gson/internal/LinkedTreeMap.java - Roman Puchkovskiy
@RomanPuchkovskiy 看起来已经成功了!至少单元测试不再失败了,但我还需要再次运行主代码。感谢您的帮助,Roman! - Max
显示剩余4条评论
3个回答

6

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

如果您不使用其特定功能,则不建议将其转换为具体类。在这种情况下,最好将其转换为 Map 而不是 LinkedHashMap 。 Gson的 LinkedTreeMap 也是 Map ,因此不应出现问题。


1
我会考虑(不仅仅是那个特定的)将强制类型转换视为代码异味。每次编写强制类型转换时,都存在发生ClassCastException的风险。
正如其他人已经说过的那样,Map接口可以像Map<String, Object> evt = row.get(Schema.EVT);这样使用。
或者,可以通过new LinkedHashMap<String, Object>(row.get(Schema.EVT));构造一个新的LinkedHashMap
第二种方法的优点是保持LinkedHashMap类型,这可能很重要,也可能不重要,这取决于您的情况。

0

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

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

你应该没问题了。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接