Skip to content

Converting Script Values to Java

Attila Szegedi edited this page Mar 16, 2021 · 2 revisions

When values (including both primitives and objects) are passed back as parameters to Java methods, they are often converted. Nashorn supports a wide variety of automatic value conversions, that seek to incorporate both the flexibility of ECMAScript value conversion system as well as the rules of Java. In addition, in some situations explicit conversion operations are possible. We describe all below.

Automatic conversions

When a method on a Java object is invoked, the arguments are converted to the formal parameter types of the Java method using all allowed ECMAScript conversions. This can be surprising, as in general, conversions from string to number will succeed according to Standard's section 9.3 "ToNumber" and so on; string to boolean, number to boolean, Object to number, Object to string all work. In detail the supported conversions are as follows:

Java type Conversion
boolean As per ECMAScript 9.2 ToBoolean conversion. Colloquially, as if you applied !! to the value.
byte, short, int, float, long, double As per ECMAScript 9.3 ToNumber conversion. Colloquially, as if you applied + to the value to obtain a JavaScript number and then if necessary used Java casting to convert to the target type.
char If the value is null, (char)0. If the value is a number between 0-65535 then (char)i, where i is the value of the number truncated to integer. Otherwise the value is converted to a string using ECMAScript 9.8 ToString conversion. If the result of the conversion is null, then (char)0. Otherwise if the string length is not exactly 1, a TypeError is thrown. Otherwise, the first character of the string is returned.
String, CharSequence null is passed as-is, otherwise ECMAScript 9.8 ToString conversion is used.
Number null is passed as-is, but the concrete type might be either Integer or Double.
Character as char, except null is passed as-is and not converted to (char)0.
Byte, Short, Int, Float, Long, Double null is passed as-is, otherwise as for the primitive types. Undefined values are converted to either Nan for floats and doubles or null for integer types.
lambda types If the type is a Java lambda type (an interface or abstract class with a single method), you can pass in an ECMAScript function and Nashorn will do the right thing, and create an adapter for the lambda type. Nashorn is even more versatile than Java though, you can pass a function for a type that has multiple abstract methods, but they all share the same name (the name is overloaded.) In that case, the passed function will be used as the implementation for all the overloads and must analyze the number and types of its parameters to decide which overload was invoked.
Map null is passed as-is. Any script object will be passed as-is, as they all implement the Map interface with string keys. This is true of script arrays too. Primitive types can not be passed (they are not run automatically through ECMAScript ToObject conversion.)
List, Collection, Queue, Dequeue null is passed as-is. For native script arrays, an adapter is returned that exposes the array. Note that with ECMAScript arrays, push and pop operate at the end of the array, while in Java Deque they operate on the front of the queue and as such the Java dequeue push and pop operations will translate to unshift and shift script operations respectively, while Java addLast and removeLast will translate to push and pop. Primitive values and objects that are not arrays can not be passed. It is possible to enforce converting other array-like objects to lists and queues with explicit call to Java.to, see later.
Java array types Script arrays are converted to Java arrays using per-component conversions. If the target Java array type is multidimensional, components are recursively converted, so it will properly convert arrays-of-arrays etc. Objects that are not native script arrays can not be passed. It is possible to enforce converting other array-like objects to Java arrays with explicit call to Java.to, see later.
Object if the Java parameter type is just Object, values are passed as they are. JavaScript primitive values are passed as Boolean (for booleans), Number (for numbers), and CharSequence (for strings). Strings are not necessarily a Java String but they are always a CharSequence. Numbers will usually be Integer or Double. Script objects will be passed as object implementing both Nashorn's JSObject interface and also the Map interface. This is true of script arrays too –- if you need arrays to implement List instead, you'll need to use Java.asJSONCompatible explicit conversion method found in the built-in Java global object.

Explicit conversions

The built-in Java object contains two methods useful for explicit specialized conversions to Java types: Java.asJSONCompatible and Java.to.

Java.asJSONCompatible

This method is useful if you need to pass a script array (or an object graph containing script arrays recursively) such that array adapters should expose List interface instead of the Map interface that is by default exposed by all script objects to Java. The name of the method stems from the fact that various Java JSON libraries typically have this expectation. Unfortunately, it is also impossible for a single Java class to implement both Map and List interfaces, hence we are providing the developer here with a choice of how to represent script arrays in Java.

Java.to

While Nashorn automatically converts native script arrays (those of script class Array) to either Java arrays or to Collection, List, Queue, and Deque interface, it won't do that for other objects that might still be "array-like". A script object is array-like if it has a length property and push, pop, shift, unshift, and splice methods, with the expectation that all of them have same behaviors as the native script arrays. Using Java.to you can explicitly convert such array-like objects to Java arrays or collections:

var someArrayLike = ...
javaObj.methodExpectingIntArray(Java.to(someArrayLike, Java.type("int[]"))

If converting to lists or queues, the adapter will call splice to add or remove elements in the middle of the list, push, and unshift to add elements to the start and end of the list, and pop and shift to remove elements from the start and end of the list.

Note that with ECMAScript arrays, push and pop operate at the end of the array, while in Java Deque they operate on the front of the queue and as such calls to the Java dequeue addFirst/push and removeFirst/pop methods will translate to unshift and shift script methods respectively, while Java addLast and removeLast will translate to script push and pop.