Импорт карты в 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.