Sequences of unboxed primitives in JavaFX
Read JavaFX-sequence-basics first.
A sequence whose JavaFX type is T[]
translates to
the Java generic type Sequence<? extends T>
.
A primitive
type like Integer
is mapped to the Java primitive type int
.
Unfortunately, the JVM doesn't support generics with
primitive type parameters such as Sequence<int>
;
instead we have to use Sequence<java.lang.Integer>
.
An ArraySequence
would be implemented using an array of
java.lang.Integer
instances, which are much more expensive
than a Java int
array: java.lang.Integer
requires memory allocation, which takes more time and memory.
Before you can do arithmetic the java.lang.Integer
has to be converted to an int
.
Converting an int
to a java.lang.Integer
(typically using Integer.valueOf(int)
)
is called boxing, because you create a box
(the
Integer
object) to hold the value.
Converting a java.lang.Integer
to an int
(typically using Integer#intValue()
) is called unboxing.
As boxing is quite expensive, we'd like to find a way to use sequences of primitive unboxed values directly.
Abandoned approach: IntegerSequence
One approach is to define a new interface for each primitive type:
public interface IntegerSequence extends Sequence<java.lang.Integer> { int getAsInt(int position); void toArray(int sourceOffset, int length, int[] array, int destOffset); }
Then the compiler would translate Node[]
to Sequence<Node>
, while Integer[]
would be translated
to IntegerSequence
.
We'd create similar XxxSequence
interfaces for each
unboxed type Xxx
.
Likewise for XxxAbstractSequence
,
XxxArraySquence
, and so on.
Generating these classes can be done conveniently with a
template processor.
Because a Sequence<Integer>
doesn't implement
IntegerSequence
,
every sequence class that depends on a type parameter T
would need to be replicated for each primitive type.
While there aren't very many Sequence
interfaces and classes, it's still somewhat costly in terms of static
footprint.
Naively, the sequence-variable classes also have to be multiplied,
but it is possible to be smarter. For example, we could parameterize
the SequenceVariable
class in terms of both the element
type and the sequence type:
class SequenceVariable<T, TSEQ extends Sequence<T>> { /** Get current value, as a Sequence. */ public TSEQ get() { ... } ... }
An Integer[]
variable would be compiled to a
Sequencevariable<Integer,IntegerSequence>
.
However, this was getting complicated, plus it entailed a lot of
code-duplication so I set this approach aside.
Direct unboxed support in Sequence
Instead, I added support for the primitive types
directly in Sequence
:
public interface Sequence<T> { public T get(int position); ... other methods as before ...; byte getAsByte(int position); short getAsShort(int position); int getAsInt(int position); ... etc ...; }
Similarly for AbstractSequence
,
which provides default implementations in terms of get
:
public class AbstractSequence<T> implements Sequence<T> { public int getAsInt(int position) { return ((Number) get(position)).intValue(); } ... etc ..; }
We do need separate ArraySequence
sub-classes
for each primitive type. (The following is simplified in
that it ignores the buffer-gap.)
class abstract ArraySequence<T> extends AbstractSequence<T> { protected abstract T getRawArrayElementAsObject(int index); public T get(int position) if (position < 0 || position >= array.length) return getDefaultValue(); return getRawArrayElementAsObject(position); } } class ObjectArraySequence<T> extends ArraySequence<T> { T[] array; protected abstract T getRawArrayElementAsObject(int index) { return array[index]; } } class IntegerArraySequence extends ArraySequence<java.lang.Integer> int[] array; protected Integer getRawArrayElementAsObject(int index) { return Integer.valueOf(array[index]); } public int getAsInt(int position) { if (position < 0 || position >= array.length) return 0; else return array[position]; } }
This works pretty well. The compiler translates:
def x : Integer[] = ...; x[i]
to
SequenceVariable<java.lang.Integer> x = ....; x.get().getAsInt(i)
Then if the value in x
is an IntegerArraySequence
then we end up calling IntegerArraySequence#getAsInt
,
which does no boxing or unboxing.
Note it is possible the sequence value at run-time is an
ObjectArraySequence<Integer>
rather than
an IntegerArraySequence
. That could happen in
the case of bind
forms, or other expressions which
have not (yet) been optimized to avoid boxing.
The right thing still happens in those cases,
though with the boxing overhead.
Various methods in SequenceVariable
also needed to be
replicated for each primitive type.