2

I noticed that Java Reflection supports access on fields of primitive types such as boolean or int without boxing:

public final class Field extends AccessibleObject implements Member {
    //...
    public boolean getBoolean(Object obj) throws IllegalArgumentException, IllegalAccessException { ... }
    public char getChar(Object obj) throws IllegalArgumentException, IllegalAccessException { ... }
    public byte getByte(Object obj) throws IllegalArgumentException, IllegalAccessException { ... }
    public short getShort(Object obj) throws IllegalArgumentException, IllegalAccessException { ... }
    public int getInt(Object obj) throws IllegalArgumentException, IllegalAccessException { ... }
    public long getLong(Object obj) throws IllegalArgumentException, IllegalAccessException { ... }
    public float getFloat(Object obj) throws IllegalArgumentException, IllegalAccessException { ... }
    public double getDouble(Object obj) throws IllegalArgumentException, IllegalAccessException { ... }
    //...
}

However, there is only invoke(...) for methods which always returns Object. This forces boxing when it is used with methods with primitive return values. I wonder why there is no support for that yet. Has it not been asked for or are there serious issues which prevent it?

mmirwaldt
  • 843
  • 7
  • 17
  • Hard to imagine that there would be any issue preventing it - some simple wrapper methods like `public int invokeInt(...) { return ((Integer) invoke(...)).intValue(); }` would do the job. I suspect it's because it's more common to access a private field via reflection, or all of an object's fields, and there is rarely a need to dynamically call a private method or all methods on an object. – kaya3 Dec 31 '20 at 00:58
  • 2
    @kaya3 or you could use `MethodHandle`s, that will generate proper bytecode, I guess. – Eugene Dec 31 '20 at 03:19
  • @Eugene Thank you for your code sample with the method handle. I will try it out myself soon. – mmirwaldt Dec 31 '20 at 04:46
  • 1
    The answer from @Andreas is exactly right, but I'll add: I think the question is caught up in a "glass 1% empty" mis-perspective. (A better question might be: "why does Field even bother with unboxed access?".) Further, are you really sure that boxing actually has a performance impact on the hot path here, or are you just guessing? I would expect this to be a small contributor to the actual costs. (And, does it even need to be said that, if you've got reflective calls on hot performance-critical code paths, maybe there's your problem?) – Brian Goetz Dec 31 '20 at 14:51
  • @Brian Goetz Frankly speaking: I guess.I am afraid my framework solution cannot be used with large data later if boxing happens just too often by accident. Special handling of boxing is not something you can just introduce later though. However, I agree I must measure performance with JMH and find the slow parts. And yes, reflective calls might be so slow that boxing might not be the main issue. Thank you so far for your answers, comments and links. They helped me a lot and gave me good advice. – mmirwaldt Jan 01 '21 at 00:30

2 Answers2

3

are there serious issues which prevent it?

Yes, the return value is not the only part of invoke() that boxes primitives, the method arguments are boxed too.

E.g. if you have method boolean foo(String a, int b, double c), then you invoke it like this:

String a = ...;
int b = ...;
double c = ...;

boolean result = method.invoke(obj, a, b, c);

Before auto-boxing and varargs, it would have been like this, which is what the compiler actually generates:

boolean result = method.invoke(obj,
                               new Object[] { a,
                                              Integer.valueOf(b)/*auto-boxing*/,
                                              Double.valueOf(c)/*auto-boxing*/ })
                       .booleanValue()/*auto-unboxing*/;

To eliminate the need for boxing of primitives, the API would need to provide an overloaded invoke() method that exactly matches the signature of the method, not just one that matches the return type.

There would have to be a gazillion overloads, and that would be a serious issue.

Making overloads for the various return types without also making overloads for the arguments wouldn't make sense, because what are you trying to fix with that? Nothing.

The overhead of making a reflexive method call is high enough that the boxing of the return value is a non-issue.

Andreas
  • 154,647
  • 11
  • 152
  • 247
  • Well, I thought of 7 more methods like invokeBoolean(Object object, Object...args). That would be enough! Of course you cannot avoid boxing with the args but you can with the return value. This would still be very useful if you think of getters which never have args but often return primitive values. Especially if pojos or entities are proxied so that you never see any fields but only getters (e.g. when you only define interfaces with getters and setters). – mmirwaldt Dec 31 '20 at 04:40
  • 1
    @mmirwaldt But it's an incomplete solution to only do the return type, and the boxing overhead is minuscule compared to the reflexive call itself, so what are you trying to solve? Whether you need to write `boolean x = (Boolean) invoke()` or `boolean x = invokeBoolean()` makes very little difference to how much you have to write, or to the understanding of what it does. I see no reason to even try this, without also doing something about the arguments. Filling up the `Method` class with overloaded methods for such little gain is overkill. – Andreas Dec 31 '20 at 04:44
  • Well, why do those getter methods exist for Field? Moreover, 7 more methods would not make Method explode ;-) I mean, if you only have some (<10000) invocations, boxing will not matter. Yes, I agree. When you have more invocations though, you might notice it and then it get's really ugly. Imagine a program where you have many points (100,000), each with an x and y coordinate which are ints and only available via getters. You won't like boxing there! ;-) – mmirwaldt Dec 31 '20 at 04:59
  • Btw: Joshua Bloch warns in "Item 5" in his book "Effective Java" (Second edition): "The lesson is clear: prefer primitives to boxed primitives, and watch out for unintentional autoboxing." (https://softwareengineering.stackexchange.com/a/203971) – mmirwaldt Dec 31 '20 at 23:49
  • 2
    @mmirwaldt where do you get that 7 from? The `Field` class has 16 methods in addition to the handle-all methods accepting or returning `Object`. Why should the return type of `Method` be more eligible for overloading than argument types? And why on Earth are considering reading coordinates of 100,000 field via Reflection? If you care for performance, just don’t use Reflection. – Holger Jan 05 '21 at 12:48
  • @Holger Sorry about the 7. I meant 8 primitive types: boolean,char,byte,short,int,long,float,double. Well, you are right that mixing boxed args with a non-boxed return value is questionable and reflection is slow. However, the 100,000 points were just an example because some people cannot imagine that too much boxing CAN destroy the performance as Joshua Bloch showed in an example. – mmirwaldt Jan 06 '21 at 12:18
  • 3
    There’s no doubt that too much boxing is undesirable. That’s why, e.g. `IntStream` exists, instead of solely relying on `Stream`. That’s why the alternative to Reflection, `MethodHandle`, exists and if you really have to invoke a particular method very often, consider converting the `MethodHandle` to an interface implementation with a matching signature via `LambdaMetaFactory` first. – Holger Jan 06 '21 at 12:24
3

There is a way, but you need the "newer" reflection. For example:

static class Test {

    public long go(int x, int y) {
        return x + y;
    }

}


static void methodHandles() throws Throwable {
    MethodType mt = MethodType.methodType(long.class, int.class, int.class);
    MethodHandles.Lookup lookup = MethodHandles.lookup();
    MethodHandle goMethod = lookup.findVirtual(Test.class, "go", mt);

    long result = (long)goMethod.invokeExact(new Test(), 40, 2);
    System.out.println(result);
}

This will compile to:

Method java/lang/invoke/MethodHandle.invokeExact:(LDeleteMe$Test;II)J

Notice the signature : ...II)L, not java/lang/Integer nor java/lang/Long. These are also called compiler overloads.

Eugene
  • 117,005
  • 15
  • 201
  • 306
  • 2
    While the technique you outline does indeed allow for box-less invocation, calling this the "new reflection" is misleading at best. The purpose of the method handle API is emphatically *not* a replacement for reflection, it has an entirely different purpose. Please don't try to convince yourself that this is any sort of "reflection++" mechanism. – Brian Goetz Dec 31 '20 at 14:45
  • 2
    See further explanation here: https://stackoverflow.com/a/30678419/3553087 – Brian Goetz Dec 31 '20 at 19:14
  • I understand reflection and method handles were designed for completely different target groups and using methods handles right can be very tricky. I will get familiar with it before using it. I think https://docs.oracle.com/javase/8/docs/api/java/lang/invoke/MethodHandle.html (and https://docs.oracle.com/javase/9/docs/api/java/lang/invoke/VarHandle.html for VarHandle since Java 9) is a good start to understand the concepts. – mmirwaldt Jan 01 '21 at 00:11
  • 2
    As addendum to @BrianGoetz’s comment, note the name of [`unreflect`](https://docs.oracle.com/javase/8/docs/api/java/lang/invoke/MethodHandles.Lookup.html#unreflect-java.lang.reflect.Method-) emphasizing the change from Reflection to non-reflective. Calling the result “Reflection” again is contradicting. – Holger Jan 05 '21 at 12:41
  • @Holger agreed, I used some bad comparison :( – Eugene Jan 05 '21 at 12:59