Reputation: 139
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
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
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