Kolya Ivankov
Kolya Ivankov

Reputation: 139

Ignoring exception checking while using Method.invoke in java

In a project I am taking part in, there is a plethora of classes each implementing a method called add which all work the same way, e.g. MyVector sum = add(vector1, vector2), where vector1 and vector2 are both of type MyVector. I have no permission to modify of all the classes that have add, so I could have make them implement some interface "IAddable".

Now, I'd like to make a generic class of the form

class Summinator<TVector>
{
    Function<TVector,TVector,TVector> add;

    public Summinator()
    {
        //... somehow get the class of TVector, say cVector
        Method addMethod = cVector.getDeclaredMethod("add", new Class[] {cVector, cVector});
        add = (v1, v2) -> (TVector)addMethod.invoke(null, v1, v2);
    }

    public TVector VeryLargeSum(TVector[] hugePileOfVectors)
    {
        TVector sum = hugePileOfVectors[0];
        for (int i = 1; i < hugePileOfVectors.length; i++)
        {
            sum = add(sum, hugePileOfVectors[i]);
        }
        return sum;
    }
}

As the sum is large, I'd like to have a lambda-expression to do the work. I also make type-checking at the initiation time. However, java wants me to check for exceptions every time I invoke the method, so instead of

add = (v1, v2) -> (TVector)addMethod.Invoke(null, v1, v2);

it forces me to write something like

add = (v1, v2) -> 
{
    try {
    return add.invoke(null, v1, v2);
    } catch (IllegalAccessException e) {
         e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    }
};

I am afraid that this exception-checking will consume lots of machine time, whereas all the objects are in fact quite basic in their nature and the application of add is in fact a matter of a handful of flops. If it was C# and all classes in question had an overloaded + operation, I could have solved my problem with System.Linq.Expressions package, using the supported binary operations: there exception checking is not obligatory. But... I am to work in java.

Perhaps, there is some way around to at least ignore exception checking?

Upvotes: 2

Views: 596

Answers (2)

Holger
Holger

Reputation: 298183

In your class, there’s something missing. You are calling getDeclaredMethod on a cVector that is nowhere in scope. Due to type erasure, a generic class has no possibility to get that Class object of the parameterization on its own, so for this class to work correctly, there must be someone instantiating the Summinator with the actual type parameterization and passing the appropriate Class object, e.g.

Summinator<Actual> actualSumminatior = new Summinator<>(Actual.class);

The cleanest solution is to change the Summinator class to let this instantiating code pass the intended add function in the first place:

class Summinator<TVector>
{
    final BinaryOperator<TVector> add;

    public Summinator(BinaryOperator<TVector> addFunction)
    {
        add = addFunction;
    }

    public TVector VeryLargeSum(TVector[] hugePileOfVectors)
    {
        TVector sum = hugePileOfVectors[0];
        for (int i = 1; i < hugePileOfVectors.length; i++)
        {
            sum = add.apply(sum, hugePileOfVectors[i]);
        }
        return sum;
    }
}

and change the caller(s) to

Summinator<Actual> actualSumminatior = new Summinator<>(Actual::add);

That’s all.

But note that the entire operation of VeryLargeSum can be simplified to

return Arrays.stream(hugePileOfVectors).reduce(Actual::add)
    .orElseThrow(() -> new IllegalArgumentException("empty array"));

at the use site, rendering the entire Summinator obsolete.


If the calling code is an unchangeable legacy code base and you have to live with the Class input, you can generate the equivalent to method references dynamically:

class Summinator<TVector>
{
    final BinaryOperator<TVector> add;

    public Summinator(Class<TVector> cVector)
    {
        MethodHandles.Lookup l = MethodHandles.lookup();
        MethodType addSignature = MethodType.methodType(cVector, cVector, cVector);
        try
        {
            MethodHandle addMethod = l.findStatic(cVector, "add", addSignature);
            add = (BinaryOperator<TVector>)LambdaMetafactory.metafactory(l, "apply",
                  MethodType.methodType(BinaryOperator.class),
                  addSignature.erase(), addMethod, addSignature)
                .getTarget().invokeExact();
        }
        catch(RuntimeException|Error t)
        {
            throw t;
        }
        catch(Throwable t) {
            throw new IllegalArgumentException("not an appropriate type "+cVector, t);
        }
    }

    public TVector VeryLargeSum(TVector[] hugePileOfVectors)
    { // if hugePileOfVectors is truly huge, this can be changed to parallel execution
        return Arrays.stream(hugePileOfVectors).reduce(add)
            .orElseThrow(() -> new IllegalArgumentException("empty array"));
    }
}

Note that both solutions likely run faster than the Method.invoke based, but not due to the absence of exception handling in the function, but rather because invocation through Reflection is expensive in general.

Upvotes: 2

Aleh Maksimovich
Aleh Maksimovich

Reputation: 2650

If you want some utility that will make it simple to sum all of the instances of different objects in array you should make use of function references and streams in Java 8.

Let's assume that you have some class MyVector with static add function:

/**
 * Class with static add method.
 */
public class MyVector {

    public static MyVector add(MyVector one, MyVector two) {
        return new MyVector();
    }

}

Then you can do the array summing the following way:

import java.util.stream.Stream;

public class Summing {

    public static void main(String args[]) {
        MyVector[] myValues = new MyVector[]{/* values */};
        MyVector sum = Stream.of(myValues).reduce(MyVector::add).get();
    }

}

The situation gets a bit more tricky if add is an instance method. But assuming that it doesn't depend on object properties:

/**
 * Class with instance add method.
 */
public class OtherVector {

    public OtherVector add(OtherVector one, OtherVector two) {
        return new OtherVector();
    }

}

You can apply something of the kind:

import java.util.function.BinaryOperator;
import java.util.stream.Stream;

public class Summing {

    public static void main(String args[]) {
        OtherVector[] otherValues = new OtherVector[]{/* values */};
        // If add is an instance method than you need to decide what instance you want to use
        BinaryOperator<OtherVector> otherAdd = new OtherVector()::add;
        OtherVector sum = Stream.of(otherValues).reduce(otherAdd).get();
    }

}

Upvotes: 0

Related Questions