在 java 中使用 Method.invoke 时忽略异常检查

Ignoring exception checking while using Method.invoke in java

在我参与的一个项目中,有很多 classes,每个都实现了一个名为 add 的方法,它们都以相同的方式工作,例如MyVector sum = add(vector1, vector2),其中 vector1vector2 都是 MyVector 类型。我无权修改所有具有 add 的 classes,所以我可以让它们实现一些接口“IAddable”。

现在,我想制作一个通用的 class 形式

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;
    }
}

由于总和很大,我想用一个 lambda 表达式来完成这项工作。我还在启动时进行类型检查。但是,java 希望我在每次调用该方法时检查异常,所以

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

它迫使我写类似

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

恐怕这种异常检查会消耗大量的机器时间,而实际上所有对象的性质都很基本, add 的应用实际上是少数的事情拖鞋。如果它是 C# 并且所有有问题的 classes 都有一个重载 + 操作,我可以用 System.Linq.Expressions 包解决我的问题,使用支持的二进制操作:异常检查不是强制性的。但是...我要在 java.

工作

也许,有一些方法可以至少忽略异常检查?

如果您想要一些实用程序来简化数组中不同对象的所有实例的总和,您应该使用 Java 8.

中的函数引用和流

假设您有一些 class MyVector 和静态 add 函数:

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

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

}

然后你可以按以下方式对数组求和:

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();
    }

}

如果 add 是一个实例方法,情况会变得有点棘手。但假设它不依赖于对象属性:

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

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

}

你可以申请这样的东西:

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();
    }

}

在你的 class 中,少了一些东西。您在 cVector 上调用 getDeclaredMethod ,而该 cVector 不在范围内。由于类型擦除,泛型 class 不可能自己获得参数化的 Class 对象,因此要使此 class 正常工作,必须有人实例化 Summinator 使用实际类型参数化 传递适当的 Class 对象,例如

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

最干净的解决方案是更改 Summinator class 让此实例化代码首先通过预期的 add 函数:

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;
    }
}

并将来电者更改为

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

就这些。

但是注意VeryLargeSum整个操作可以简化为

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

在使用现场,使整个 Summinator 过时。


如果调用代码是不可更改的遗留代码库并且您必须忍受 Class 输入,则可以动态生成等效于方法引用:

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"));
    }
}

请注意,这两种解决方案都可能 运行 比基于 Method.invoke 的解决方案更快,但这并不是因为函数中没有异常处理,而是因为通过反射调用通常很昂贵。