Импорт карты в javax.scripting javascript environment

Я вижу некоторое нечетное поведение в реализации карты javax.scripting.

В онлайн-примерах показано пример добавления в список в среде js:

ScriptEngineManager mgr = new ScriptEngineManager();
  ScriptEngine jsEngine = mgr.getEngineByName("JavaScript");
  List<String> namesList = new ArrayList<String>();
  namesList.add("Jill");
  namesList.add("Bob");
  namesList.add("Laureen");
  namesList.add("Ed");
  jsEngine.put("namesListKey", namesList);
  System.out.println("Executing in script environment...");
  try
  {
   jsEngine.eval("var names = namesListKey.toArray();" + "for(x in names) {" + "  println(names[x]);" + "}"
     + "namesListKey.add(\"Dana\");");
  } catch (ScriptException ex)
  {
   ex.printStackTrace();
  }
  System.out.println(namesList);

Однако, если вы попытаетесь сделать что-то подобное с картой, вы увидите странное поведение. Во-первых, если вы пытаетесь выполнить итерацию с помощью клавиш карты, например

 HashMap<String, Object> m = new HashMap<String, Object>();
 jsEngine.put("map", m);

Нет способа получить ключи карты - если вы попытаетесь выполнить итерацию по клавишам, вы получите имена методов -

jsEngine.eval(" for (var k in m.keySet()){ println(k)};");

приводит к:

notifyAll
removeAll
containsAll
contains
empty
equals
...

В контексте js вы можете адресовать значения на карте с помощью m.get(key), но не с m[key], и если ключ не существует, он выдает ошибку. Может ли кто-нибудь пролить свет на это поведение, или он просто сломан? Спасибо.

Ответ 1

for..in в JavaScript - это не то же самое, что и для... на Java, хотя они выглядят одинаково. for..in в JavaScript выполняет итерации над именами свойств в объекте. Имена методов подвергаются Rhino как свойства на собственном объекте Java HashMap, так же, как если бы у вас был следующий объект JavaScript:

{
notifyAll:function(){},
removeAll:function(){},
containsAll:function(){},
contains:function(){},
empty:function(){},
equals:function(){}
}

Моя рекомендация заключается в том, что вы либо конвертируете серию HashMap в массив, используя метод Set.toArray, либо получаете итератор, используя Set.iterator(). Здесь короткий Rhino script, показывающий, как вы можете выполнить это, используя метод toArray:

x=new java.util.HashMap();
x.put("foo","bar");
x.put("bat","bif");
x.put("barf","boo");

var keyArray = x.keySet().toArray();
for(var i=0, l = keyArray.length; i < l; i++){
    var key = keyArray[i]; 
    var value = x.get(key);
    print(value);
}

Какие выходы:

bif
bar
boo

Здесь вы можете сделать то же самое с помощью Set.iterator:

x=new java.util.HashMap();
x.put("foo","bar");
x.put("bat","bif");
x.put("barf","boo");

var keyIter = x.keySet().iterator();
while(keyIter.hasNext()){
    var key = keyIter.next() 
    var value = x.get(key);
    print(value);
}

Ответ 2

Если вы конвертируете java.util.Map в собственный объект, ваш JavaScript будет более чистым:

final Map<String,String> javaMap = new HashMap<>();
javaMap.put("alpha", "bravo");
javaMap.put("charlie", "delta");

final NativeObject jsMap = new NativeObject();
for (Map.Entry<String,String> entry : javaMap.entrySet()) {
    jsMap.defineProperty(
        entry.getKey(), entry.getValue(), NativeObject.READONLY
    );
}

final ScriptEngine jsEngine =
 (new ScriptEngineManager()).getEngineByName("JavaScript");
jsEngine.put("map", jsMap);
jsEngine.eval(
    "for (var idx in map) print(idx + '; ' + map[idx] + '\\n');"
);

В противном случае вы застряли со стандартным синтаксисом Java.