JavaMethodWrapper

Facilitate pass-by-reference behavior of primitive arrays to Java methods through the Java reflection API.
66 Downloads
Updated 21 Feb 2020

View License

A known limitation of the MATLAB-to-Java external language interface is that is is impossible to pass an array of primitive type by reference to a Java method using the standard mechanism.This is discussed, for example, here:

https://www.mathworks.com/matlabcentral/answers/66227-syntax-for-call-to-java-library-function-with-byte-reference-parameter

The common motivating example is to be able to use the read(byte[],int,int) overload of java.io.InputStream. The straightforward approach would be:

fis = java.io.FileInputStream(filename)
buf = zeros(1,1024, 'int8');
count = fis.read(buf, int32(0), int32(1024));

However, this fails to do anything useful, because buf is converted into a byte[] by MATLAB, but there is no way to hold the reference to it on the MATLAB side before it "disappears" into the method. In effect, buf is passed by value and remains zero after the call.

This submission seeks to remedy this shortcoming by facilitating the method to be called by an alternative approach, namely the invoke method of the Java reflection API.

For those of you who decide the rest is TLDR, here is how to accomplish the function call by reference using the submission:

methodObj = JavaMethodWrapper(fis, 'read(byte[],int,int)');
[count, buf] = methodObj.invoke(fis, buf, int32(0), int32(1024));

The argument "buf" is passed into the Java method by reference and the new value returned from the MATLAB invoke method. (All the original args to the method can be returned in this way, but the others are uninteresting).

Now the gory details.

A reflection object to a method of a class may be obtained by way of

methodObj = o.getClass().getMethod(...)

where getMethod takes a java.lang.Class[].
In particular, the methodObj for read(byte[],int,int) may be obtained by

ta = javaArray('java.lang.Class',3);
ta(1) = java.lang.Class.forName('[B');
ta(2) = java.lang.Integer.TYPE;
ta(3) = java.lang.Integer.TYPE;
methodObj = fis.getClass().getMethod('read',ta)

Then the method may be invoked with

argsList = java.util.Arrays.asList({
zeros(1,1024,'int8')
int32(0)
int32(1024)
});
count = methodObj.invoke(fis, argsList.toArray())

Two tricks have occurred here. First of all, we have managed to place a byte[] inside an Object[] through the use of the Collections API. Note that the straightforward approach

args = javaArray('java.lang.Object', 3);
args(1) = zeros(1,1024,'int8');

will fail with a message that primitives cannot be placed in the array. This is because MATLAB assumes that it cannot place a primitive in the array without boxing. However, it WOULD be possible to place a primitive array by reference, but this is not supported by the interface.

The second trick is that, due to the signature of invoke(Object, Object[]), the Object[] containing the arguments is passed directly into "Java land" from MATLAB as a reference type. Therefore, the reference to the byte[] is retained inside the Object[] and is accessible later.

This is about as intuitive as quantum mechanics, especially the use of the internal identifier '[B' for byte[]. This process is a useful trick for overcoming the limitations of MATLAB, but it needs better usability.

Hence this submission - the class JavaMethodWrapper hides all the ugliness of the reflection interface from the user and allows simply:

methodObj = JavaMethodWrapper(fis, 'read(byte[],int,int)');
[count, buf] = methodObj.invoke(fis, buf, int32(0), int32(1024));

The MATLAB version of the invoke method hides all the Java reflection ugliness and allows the method to be looked up by the human-readable name returned from 'methods -full' or methodsview.

Other than working around the limitation of primitive array pass-by-reference, JavaMethodWrapper may be used to resolve a particular overload of a class method a-priori, rather than relying on MATLAB's implicit resolution.

For example:
methodObj = JavaMethodWrapper('java.lang.String', 'valueOf(double)');
%use [] for instance arg when calling static method
str = methodObj.invoke([], single(5.0)) %works
str = methodObj.invoke([], 5.0) %also works

methodObj = JavaMethodWrapper('java.lang.String', 'valueOf(float)');
str = methodObj.invoke([], single(5.0)) %works
str = methodObj.invoke([], 5.0) %fails - narrowing conversion

I hope you find this submission useful. I have tested it back to 2016a, but it is probably supported by versions earlier than that.

Cite As

Benjamin Davis (2024). JavaMethodWrapper (https://www.mathworks.com/matlabcentral/fileexchange/74308-javamethodwrapper), MATLAB Central File Exchange. Retrieved .

MATLAB Release Compatibility
Created with R2019b
Compatible with R2016a and later releases
Platform Compatibility
Windows macOS Linux
Categories
Find more on Graphics Objects in Help Center and MATLAB Answers

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!
Version Published Release Notes
1.0.0