Java将方法引用作为参数传递给其他方法

21

我正在尝试将类A中选定的“get”方法传递给类B中的一个方法。我查看了Java将方法作为参数传递,但我无法以合理的方式采用接口方法解决我的问题。如果可能的话,我更愿意使用Java 8(lambda),并尽量避免反射。我的感觉是,我从错误的角度来看待我的问题。

这里是我试图实现的具体简化示例:

我有一个包含一些字段和get方法的类:

public class DataStore {
    private float a;
    private float b;
    private float c;

    public float getA() {
        return a;
    }

    public float getB() {
        return b;
    }

    public float getC() {
        return c;
    }

}

接下来,我有一个主类实例化DataStore作为映射的值,然后访问DataStore的特定字段,例如:

public class App {

    public static void main(String[] args) {
        // declare TreeMap using DataStore class as value
        Map<Integer, DataStore> dataMap = new TreeMap<Integer, DataStore>();

        // populate Map with example data
        dataMap.put(2,  new DataStore(1f,2f,3f));
        dataMap.put(10, new DataStore(3f,4f,5f));
        dataMap.put(4,  new DataStore(6f,7f,8f));

        // work with specific fields in DataStore, e.g. assign to array
        float[] aArray = getValuesAsArray(dataMap, DataStore.getA());
        float[] bArray = getValuesAsArray(dataMap, DataStore.getB());
        float[] cArray = getValuesAsArray(dataMap, DataStore.getC());
    }

    /**
     * Assign specific field of DataStore from Map to Array
     * @param dataMap
     * @param getVar - reference for specified getter method
     * @return 
     */
    private static float[] getValuesAsArray(Map<Integer, DataStore> dataMap, MethodReference getVar()) {
        int i = 0;
        int nMap = dataMap.size();
        float[] fArray = new float[nMap];
        for (Map.Entry<Integer, DataStore> entry : dataMap.entrySet()) {
            DataStore ds = entry.getValue();
            fArray[i] = ds.getVar();
            i++;
        }
        return fArray;
    }
}

很显然这样行不通,因为我需要想办法将我选择的get方法传递给getValuesAsArray()函数。我猜我的方法可能是错误的,所以我很乐意听取建议。


也许我只是个新手,但为什么你需要在这里传递一个方法?这个程序试图实现什么? - px06
@px06 - 实际的DataStore将拥有超过3个字段。我以随机方式在dataMap中收集数据集。treeMap的键是与每个数据集关联的稀疏整数集。在某些时候,我需要检索或输出单个字段值作为“排序”数组,其中排序应基于键整数完成。 - W.E.Coyote
5个回答

39

您的getX()方法可以看作是接受DataStore实例并返回浮点数的函数。

在Java 8中,您可以使用方法引用来表示它们:

    float[] aArray = getValuesAsArray(dataMap, DataStore::getA);
    float[] bArray = getValuesAsArray(dataMap, DataStore::getB);
    float[] cArray = getValuesAsArray(dataMap, DataStore::getC);

那么你的getValuesAsArray将接受一个Function<DataStore,Float>参数,并执行该函数:

private static float[] getValuesAsArray(Map<Integer, DataStore> dataMap, Function<DataStore,Float> func) {
    int i = 0;
    int nMap = dataMap.size();
    float[] fArray = new float[nMap];
    for (Map.Entry<Integer, DataStore> entry : dataMap.entrySet()) {
        DataStore ds = entry.getValue();
        fArray[i] = func.apply(ds);
        i++;
    }
    return fArray;
}

在不使用Java 8的情况下,您可以定义自己的接口,其中包含一个接受DataStore实例并返回float的方法。然后,您需要传递该接口的实现(可以使用实现接口的匿名类实例)到getValuesAsArray方法中,而不是使用Java 8方法引用,该实现调用其中一个getX()方法。

例如:

public interface ValueGetter
{
    public float get (DataStore source);
}

float[] aArray = getValuesAsArray(dataMap, new ValueGetter() {public float get (DataStore source) {return source.getA();}});
float[] bArray = getValuesAsArray(dataMap, new ValueGetter() {public float get (DataStore source) {return source.getB();}});
float[] cArray = getValuesAsArray(dataMap, new ValueGetter() {public float get (DataStore source) {return source.getC();}});

并且

private static float[] getValuesAsArray(Map<Integer, DataStore> dataMap, ValueGetter func) {
    int i = 0;
    int nMap = dataMap.size();
    float[] fArray = new float[nMap];
    for (Map.Entry<Integer, DataStore> entry : dataMap.entrySet()) {
        DataStore ds = entry.getValue();
        fArray[i] = func.get(ds);
        i++;
    }
    return fArray;
}

@EarthEngine 我为Java 8之前的实现添加了一个建议,尽管它可能不太优雅。 - Eran
@Eran,Supplier接口怎么样?我觉得它在这里非常适合。但是我对新的Java 8东西不太熟悉 - 所以只是问问。 - Fildor
通常情况下,我很高兴看到涉及Java 8的建议。所以谢谢你!然而,对于当前的项目,我更愿意坚持使用Java 7,不幸的是。 - W.E.Coyote
1
@Fildor Supplier<T> 是一种不需要任何输入就能生成类型为 T 的值的接口。如果 getX() 方法是静态的,那么它将非常适合这个问题。 - Eran
1
@SwanAH 请查看编辑后的Java 7解决方案(不幸的是,它不够优雅)。 - Eran
显示剩余2条评论

4

之前我使用了java.util.concurrent.Callable,但似乎效果不佳,感谢@Eran的指导。

相反地,你可以使用Java 8的java.util.function.Function,就像这样(没有lambda表达式):

public static void main(String[] args) {
 //...
    getValuesAsArray(dataMap, new Function<DataStore,Float>(){ public Float apply(DataStore input) { return input.getA(); }});
    getValuesAsArray(dataMap, new Function<DataStore,Float>(){ public Float apply(DataStore input) { return input.getB(); }});
    getValuesAsArray(dataMap, new Function<DataStore,Float>(){ public Float apply(DataStore input) { return input.getC(); }});
}

private static float[] getValuesAsArray(Map<Integer, DataStore> dataMap, Function<DataStore, Float> function) {
    int i = 0;
    int nMap = dataMap.size();
    float[] fArray = new float[nMap];
    for (Map.Entry<Integer, DataStore> entry : dataMap.entrySet()) {
        DataStore ds = entry.getValue();
        fArray[i] = function.apply(ds);
        i++;
    }
    return fArray;
}

根据问题所述,getA()getB()getC()方法不是静态的。它们应该针对特定的DataStore实例进行调用。 - Eran
我只是在使用他的示例代码。这里的重点是,他可以使用 Callable 将该方法进行封装,以便稍后调用该方法。 - Julius Delfino
无论您是否使用Callable对其进行包装,只有在getA()是静态的情况下才能调用DataStore.getA() - Eran

1
有一个解决方法:使用Scala Java API。
我使用Apache Spark,Scala提供了一系列匿名函数(Function,Function2),从Java 1.5开始可用(尽管我在Java 1.7上使用它)。这里有一个讨论此问题的答案。否则,“Function”类仅适用于Java 1.8。

0

MethodReference 是一个用于反射目的的类。实际上,您的代码需要一个类似于 lambda 的对象,它应该是 Java 8 中的单方法接口。

如果没有 Java 8 或反射,就没有直接满足您需求的方法。但是,您总可以将某个方法的内部表示传递给另一个类,并且为此编写处理此内部表示的代码。


哦,对不起,我表述有误。我只是用它作为占位符,以便在没有使用Java 8或反射的情况下传递我的方法时使用一些适当的机制。 - W.E.Coyote


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接