https://kotlinlang.org logo
Title
d

dave08

01/04/2022, 12:20 PM
Will using this (with the `as`s, componentX and type params) get the array boxed?
@JvmInline
value class MethodParams1<A1>(val value: Array<Any?>) {
  
    operator fun component1() = value[0] as A1
    var arg1: A1
        get() = value[0] as A1
        set(value) { this.value[0] = value }
}
j

Joffrey

01/04/2022, 12:27 PM
In general, it's the usage of
MethodParams1
that will decide this, not the implementation. To check a particular piece of code, you can try to decompile it to Java with the IDE. For instance, you can try with this code:
fun main() {
    val params = MethodParams1<String>(arrayOf("bob", "fred"))

    println(params.component1())
    println(params.arg1)
}
Which is decompiled this way:
public final class ValueClassExampleKt {
   public static final void main() {
      Object[] params = MethodParams1.constructor-impl(new Object[]{"bob", "fred"});
      Object var1 = MethodParams1.component1-impl(params);
      System.out.println(var1);
      var1 = MethodParams1.getArg1-impl(params);
      System.out.println(var1);
   }

   // $FF: synthetic method
   public static void main(String[] var0) {
      main();
   }
}
As you can see, the array is not boxed into a
MethodParams1
instance.
But if you intend to use this approach for other elements in the underlying array, why not just use a class with properties instead? Are you wrapping an existing array that you get from somewhere else with type safety?
d

dave08

01/04/2022, 12:34 PM
Exactly, and I'm afraid to box it for performance reasons
What's these lines:
Object[] params = MethodParams1.constructor-impl(new Object[]{"bob", "fred"});
      Object var1 = MethodParams1.component1-impl(params);
That's not being boxed?
Oh... the
Object var1 = MethodParams1.component1-impl(params);
is the function (even though I'm really not doing much there...)
But it seems like a new instance IS still being created but not used?
Object[] params = MethodParams1.constructor-impl(new
Yup, you're right:
@NotNull
public static Object[] constructor_impl/* $FF was: constructor-impl*/(@NotNull Object[] value) {
   Intrinsics.checkNotNullParameter(value, "value");
   return value;
}
Doesn't box, but calls that method...
Seems like this usage (with componentX decomposition) seems to get boxed, but using inline all over doesn't box for direct access and doesn't even call any functions...:
@JvmInline
value class MethodParamsO<A1>(val value: Array<Any?>) {

    inline operator fun component1() = value[0] as A1
    var arg1: A1
        inline get() = value[0] as A1
        inline set(value) { this.value[0] = value }
}

fun process(paramsO: MethodParamsO<String>, block: (MethodParamsO<String>) -> Unit) {
    block(paramsO)
}

fun main() {
    val params = MethodParamsO<String>(arrayOf("this", 0))

    process(params) { (foo) ->
        println("process $foo")
    }

    println(params.arg1)
    params.arg1 = "this2"
    println(params.arg1)
}
public static final void process_IC3gMN4/* $FF was: process-IC3gMN4*/(@NotNull Object[] paramsO, @NotNull Function1 block) {
   Intrinsics.checkNotNullParameter(paramsO, "paramsO");
   Intrinsics.checkNotNullParameter(block, "block");
   block.invoke(MethodParamsO.box-impl(paramsO));
}

public static final void main() {
   Object[] params = MethodParamsO.constructor-impl(new Object[]{"this", 0});
   process-IC3gMN4(params, (Function1)null.INSTANCE);
   int $i$f$getArg1 = false;
   Object var1 = params[0];
   System.out.println(var1);
   Object value$iv = "this2";
   int $i$f$setArg1 = false;
   params[0] = value$iv;
   $i$f$getArg1 = false;
   var1 = params[0];
   System.out.println(var1);
}
Even though component1 makes this static method...:
public static final Object component1_impl/* $FF was: component1-impl*/(Object[] $this) {
   int $i$f$component1 = 0;
   return $this[0];
}
Is there any way to avoid that @Joffrey?
j

Joffrey

01/04/2022, 1:01 PM
MethodParams1.constructor-impl
That's not being boxed?
Doesn't box, but calls that method...
Regarding
constructor-impl
, this is the body of the
MethodParams1
constructor. This is what allows you to write initialization code in
init
blocks in your value class. This is really not a concern at all for performance.
it seems like a new instance IS still being created but not used?
Object[] params = MethodParams1.constructor-impl(new
Only one instance of the array is created, it's the
arrayOf
function that creates the initial array, but the body of
constructor-impl
does not return a new array, it returns the same array.
Seems like this usage (with componentX decomposition) seems to get boxed
The reason for boxing in your snippet is not due to decomposition methods, it's because you're using
MethodParams0
as a generic type argument in
Function1
(because you use it in
block: (MethodParamsO<String>) -> Unit
. Avoiding boxing is not easy. As a rule of thumb, boxing will happen each time you use the value class as "another type" - this means each time it's assigned to a variable of a parent type, or used in a generic type where a generic type parameter is expected (like in
Function1.invoke(T)
)
d

dave08

01/04/2022, 1:06 PM
Is there any way to avoid that in my case?
I need to pass MethodParams as the param of a lambda, and I need the type safety within the lambda itself (for property access)
j

Joffrey

01/04/2022, 1:10 PM
What exactly are you trying to avoid by keeping this array? Depending on your use case, I would advise to consider mapping the array into a class with fields. It wouldn't "box" the array because there will be no array, so there will not be 2 indirections to access fields. However it will create a second object, but that really isn't an issue in general, hence my question.
d

dave08

01/04/2022, 1:12 PM
I'm really trying to add compile-time type safety to work being done with reflected class access, but it could get pretty heavy by creating a new object for each instance I need type safety for...
But, then, maybe your right, if anyways there's no way to do this with value classes without them getting boxed, what you're proposing is the next best solution.
j

Joffrey

01/04/2022, 1:14 PM
If you're using reflection in the first place, you've already given up on performance, so I don't believe creating an instance out of an array will be a concern here
1
if anyways there's no way to do this with value classes without them getting boxed
I don't see one, but maybe I'm missing something
d

dave08

01/04/2022, 1:24 PM
Ok, thanks!
e

ephemient

01/04/2022, 4:51 PM
the MethodHandle.invokeExact() etc. added in Java 7 do allow for reflective calls without boxing, if you really need to squeeze performance out of it
but yeah if you're just using classic reflection then I don't think there's much point, whatever you do is not going to make a major impact on performance