Избежание непроверенных приведения вложенных Карт в Java - PullRequest
1 голос
/ 14 марта 2012

У меня есть структура данных Java, которая получается в результате десериализации этого JSON:

{
  'level1 value1': {
    'level2 value1': {
      'level3 value1': [ "25", "45", "78" ],
      // ...
      'level3 valueN': [ "59", "17", "42" ]
    },
    // ...
    'level2 valueN': {
      'level3 value1': [ "34", "89", "54" ],
      // ...
      'level3 valueN': [ "45", "23", "23" ]
    },
  },
  // ...
  'level1 valueN': {
    // ...
  }
}

В Java это становится:

Map<String, Map<String, Map<String, List<String>>>> data;

Конечно, количество уровней произвольно,поэтому я не могу вкладывать коллекции в объявление переменных.Вместо этого я делаю следующее:

void traverse(Map<String, ?> children) {
  for (Map.Entry<String, ?> node : data.entrySet()) {
    if (node.getValue() instanceof Map) {
      doSomethingWithNonLeafNode((Map<String, Map<String, ?>>) node);
    } else if (node.getValue() instanceof List) {
      doSomethingWithLeafNode((Map <String, List<String>>) node);
    }
  }
}


void doSomethingWithNonLeafNode(Map <String, Map<String ?>> node) {
  // do stuff
}


void doSomethingWithLeafNode(Map <String, List<String>> node) {
  // do stuff
}

Это, очевидно, а) использует некоторые непроверенные приведения и б) ужасно.Я попытался определить новые типы, чтобы обойти это:

private interface Node extends Map<String, Map<String, ?>> {
}

private interface LeafNode extends Map<String, List<String>> {
}

// ...

    if (node.getValue() instanceof Map) {
      doSomethingWithNonLeafNode((Node) node);
    } else if (node.getValue() instanceof List) {
      doSomethingWithLeafNode((LeafNode) node);
    }

Однако, это дает мне исключение времени выполнения:

java.lang.ClassCastException: java.util.HashMap cannot be cast to com.foo.ReportDataProcessor$Node

Как я могу сделать это в чистом, без предупреждениямода?

Ответы [ 3 ]

1 голос
/ 14 марта 2012

Я не думаю, что вы можете сделать это красиво без типа объединения.

Если вы подумаете об этом, Map, с которым вы имеете дело, может фактически содержать два типа объектов - либо другую картудля уровня ниже, или список строк для конечного узла.Поэтому универсальным параметром для этой карты должен быть какой-то общий супертип карт и списков.И, в конечном счете, когда вы обнаружите, что у вас есть листовой узел и вы работаете со списком, вам нужно будет сотворить .

1 голос
/ 14 марта 2012

Для более чистого решения вы можете забыть о Map как о контейнере и создать объект для представления дерева, лучше

public class Node{
   String text;
   List<String> data;
   List<Node> children; 
}

И вы можете реализовать текущий способ заполнения дерева с помощью Map, используя эту древовидную структуру. Если дочерние узлы не пусты, вам не нужно думать о данных.

void traverse(Node node) {
  if(node.getChildren()!=null){
      doSomethingWithNonLeafNode(node);
      // Or for recursion if you need
      // List<Node> children=node.getChildren();
      // for(Node c:children){
      //    traverse(c);
      // }
  }else{
      doSomethingWithLeafNode(node);
  }
}
1 голос
/ 14 марта 2012

Определение новых типов не поможет вам, потому что десериализатор JSON не знает о ваших интерфейсах Node & LeafNode, поэтому созданные объекты конкретной коллекции не могут их реализовать.

Поскольку вы на самом деле не знаете типы элементов ваших карт заранее, нет необходимости обеспечивать безопасность типов времени компиляции: вы можете полагаться только на проверки типов во время выполнения. Поэтому дженерики только усложняют вашу жизнь и делают ваш код более уродливым, но не приносят вам никакой пользы.

Так что я думаю, что наименее наихудшим решением здесь было бы отказаться от дженериков, используя в коде неуниверсальные типы Map и List.

...