Issue
Using Unsafe.putXXX
one can put primitive type into an array or object field.
But code like the following generate errors.
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
VarHandle varHandle = MethodHandles.arrayElementVarHandle(long[].class);
byte[] array = new byte[32];
printArray(array);
varHandle.set(array, 1, 5);
printArray(array);
System.out.println(varHandle.get(array, 1));
}
private static void printArray(byte[] array) {
System.out.println(Arrays.toString(array));
}
}
Exception in thread "main" java.lang.ClassCastException: Cannot cast [B to [J
at java.base/java.lang.Class.cast(Class.java:3780)
at Main.main(Main.java:15)
Also the bytes
may be write as:
byte[] array = new byte[32];
long v = 5,
int i = 8;
int high = (int) (v >>> 32);
int low = (int) v;
array[i + 0] = (byte) (high >>> 24);
array[i + 1] = (byte) (high >>> 16);
array[i + 2] = (byte) (high >>> 8);
array[i + 3] = (byte) high;
array[i + 4] = (byte) (low >>> 24);
array[i + 5] = (byte) (low >>> 16);
array[i + 6] = (byte) (low >>> 8);
array[i + 7] = (byte) low;
Is there an efficient way to write reinterpret different types and write them into fields and arrays possibly avoiding Unsafe
but as efficient.
Any special cases where the compiler or JIT will recognise the intent and optimise accordingly.
Solution
For byte[]
in particular you can use MethodHandles::byteArrayViewVarHandle
:
public static void main(String[] args) {
VarHandle varHandle = MethodHandles.byteArrayViewVarHandle(long[].class,
ByteOrder.nativeOrder());
byte[] array = new byte[32];
printArray(array);
varHandle.set(array, 1, 5);
printArray(array);
System.out.println(varHandle.get(array, 1));
}
private static void printArray(byte[] array) {
System.out.println(Arrays.toString(array));
}
There are some hoops you have to jump through with VarHandles to make them just as fast as Unsafe;
- Make sure the VarHandle itself is constant, this can be done by putting it in a static final field and accessing it from there.
- Make sure the VarHandle invocation is exact. Here this would mean
casting the second argument to a long since the VarHandle excepts a
long as well. (in the latest JDK you can useVarHandle::withInvokeExactBehavior
to enforce that).
This could be made easier by wrapping the VarHandle set and get calls in helper methods that do the cast:
private static final VarHandle LONG_ARR_HANDLE
= MethodHandles.byteArrayViewVarHandle(long[].class,
ByteOrder.nativeOrder());
public static void setLong(byte[] bytes, int index, long value) {
LONG_ARR_HANDLE.set(bytes, index, value);
}
public static long getLong(byte[] bytes, int index) {
return (long) LONG_ARR_HANDLE.get(bytes, index);
}
Answered By – Jorn Vernee
Answer Checked By – Mary Flores (BugsFixing Volunteer)