Passing Java lists into JS

Attila Szegedi attila.szegedi at oracle.com
Thu Nov 27 11:57:19 UTC 2014


I'd still refrain from automatically wrapping into an Array anything that implements a List interface because it might be implementing other interfaces too, and it might be passed back to Java and have semantics dependent on referential identity. 

There's also the issue that currently, we don't do automatic wrapping of anything; Nashorn uses POJOs without wrappers and doesn't try to pretend that POJOs are native JS objects. Speaking of, some people would expect to be able to invoke Java collection API methods on the List object (stream, spliterator, removeIf, etc.) so automatic wrapping would foil that too (except of course if we had special JS Arrays that also expose these operations…)

There's also the issue of passing such a List-wrapping-Array back to a Java API: do we unwrap it? Of course, we can have a filter that unwraps the Array back to a List when it goes back, although the current policy is that whenever a native JS object passes an API boundary back to Java, it becomes a ScriptObjectMirror, so we'd then violate that policy for some Arrays (but not for others).

So, that's a number of issues :-)

Of course, none of these issues are arguments against having an Array backed by a List, only arguments against automatic conversion.

Now, speaking about a potential List-backed Array implementation: 

Nashorn arrays abstract their backing storage into ArrayData objects, so every JS Array normally looks like this: NativeArray->ArrayData->[actual storage]. This additional indirection allows us to switch between dense and sparse representations, and between different types of representations; e.g. if you only have ints in your array, you'll have NativeArray->IntArrayData->int[], with getters and setters linked as fast pass-through array element getters and setters (MethodHandles.arrayElementGetter). Only when you set an element to a double will it evolve into NativeArray->NumberArrayData->double[].

I can imagine having a ListArrayData. It'd have few surprising properties, though. E.g. writes to the array would of course write through to the List, and writes to the List would mostly be reflected in the Array. I say "mostly" because JS arrays aren't necessarily continuos after a delete operation. In Nashorn, we implement deletion by adding a DeletedArrayFilter (a special ArrayData that filters underlying ArrayData) into the chain, so if you did "delete arrayFromList[5]" then your NativeArray->ListArray->List becomes NativeArray->DeletedArrayFilter->ListArray->List, and even if you later set list.set(5, ...) from Java, on JS level the filter will intercept any [] operations and still claim the element doesn't exist.

Also, such an Array would still need to behave as a JS Array for indices in range of 2^31…2^32-1, which can't be represented in a java.util.List, so a backup storage might be required for those indices, further creating a discrepancy between Array.length and list.size().

So anyway, none of these are arguments against having a List-backed Array, just saying that it's not possible to entirely reconcile the collection models, so there'll always be corner cases where abstraction leaks through. JS arrays are heterogenous and non-continuos, with max capacity of 2^32-1. Java Lists are heterogeneous and continuous, with max capacity of 2^31-1. They're still a better match than Java arrays, which are homogenous and continuous with max capacity of 2^31-1 :-)

Attila.

On Nov 27, 2014, at 12:18 PM, Tim Fox <timvolpe at gmail.com> wrote:

> On 27/11/14 10:33, Attila Szegedi wrote:
>> Nashorn explicitly allows [] operator on java lists and it also supports for…in on them. ".length" is also supported since 8u20, see <https://bugs.openjdk.java.net/browse/JDK-8039387>.
>> 
>> We indeed don't implicitly convert Lists into JS Array objects in Nashorn. The reason is that JS Array has a Java array as backing storage; wrapping a large list would incur a lot of copying.
> 
> To avoid the copying, could the Nashorn JS Array implementation be changed so it backs either:
> 
> a) A Java Array (as it does currently) - this would the case if you created the JS array directly in JS code.
> b) A Java List - this would be the case if a List was passed from Java to JS, providing the user with a JS array
> 
> Then we can have our cake and eat it? I.e. we have a real JS Array object (not a half way house with some of the functions and properties but not all), and no copying overhead.
> 
>>  You can use "Java.from(someObject.provideList())" to explicitly convert a Java List to a JS array. We think it's better to provide an explicit conversion API than incur a linear conversion cost whenever a List object passes into the JS context.
>> The Java.from created copy is shallow; if you have a List of Lists, the nested List objects are not converted.
>> 
>> Typically you'll want to create a real Array if you want to use Array functionality that points beyond [], .length, and for…in, e.g. JS Array comprehension operations (arguably, we *could* implement even those so that Array.prototype.forEach.call(javaList, ...) works as expected, but we aren't there yet.)
>> 
>> Attila.
>> 
>> On Nov 27, 2014, at 10:41 AM, Tim Fox <timvolpe at gmail.com> wrote:
>> 
>>> Hello again,
>>> 
>>> I am a bit confused about how Lists passed from Java into JS are converted.
>>> 
>>> I have a Java class as follows:
>>> 
>>> public class SomeClass {
>>> 
>>>   public List<String> provideList() {
>>>     List<String> list = new ArrayList<>();
>>>     list.add("foo");
>>>     list.add("bar");
>>>     return list;
>>>   }
>>> }
>>> 
>>> I call this from JavaScript:
>>> 
>>> var io = Packages.io;
>>> var someObject = new io.vertx.scratchpad.SomeClass();
>>> 
>>> var arr = someObject.provideList();
>>> 
>>> console.log(arr[0]); // prints: foo
>>> console.log(typeof arr.length); // undefined
>>> console.log(arr instanceof Array); // false
>>> 
>>> I was under the impression that Nashorn automatically converted Java lists passed into JS into JS Arrays.
>>> 
>>> The object arr returned in some ways resembles a JavaScript array - the operators [] and []= work on it, however it doesn't have the array property "length" and it's not an instanceof Array.
>>> 
>>> Can anyone clarify to me what this object is? Any reason why Nashorn doesn't just wrap it as a real JS Array?
>>> 
>>> Cheers
> 



More information about the nashorn-dev mailing list