21

I am trying to pass a selected "get"-method of class A to a method in class B. I have checked out Java Pass Method as Parameter, but I was not able to adopt the interface approach to my problem in a reasonable way. I would prefer to not use java 8 (lambdas) and if possible avoid reflection as well. My feeling is, that I am looking at my problem the wrong way. Here is the specific simplified example of what I am trying to accomplish:

I have a class containing some fields and get-methods:

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

}

Next I have my main class instantiating DataStore as Values of a Map and then accessing specific fields of DataStore like:

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

Clearly this wont work, as I have to figure out how to pass my selected get method into getValuesAsArray(). Somehow, I guess, my approach may be wrong. So I am open for suggestions.

Community
  • 1
  • 1
W.E.Coyote
  • 313
  • 1
  • 2
  • 6
  • Maybe I'm just being a noob but why do you _need_ to pass a method here? What is this program trying to accomplish? – px06 Aug 24 '16 at 08:41
  • @px06 - The actual DataStore will have more than 3 fields. I am collecting a data set in that dataMap in a random way. The key of the treeMap is a sparse set of integers associated to each data set. At some point I will need to retrieve or output the individual field values as "sorted" array, where sorting should be done based on the key integers. – W.E.Coyote Aug 24 '16 at 09:00

5 Answers5

39

Your getX() methods can be seen as a Function that accepts a DataStore instance and returns a float.

In Java 8 you can represent them with method references :

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

Then your getValuesAsArray will accept a Function<DataStore,Float> parameter and execute the function :

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

Without using Java 8, you can define your own interface that contains a method that accepts a DataStore instance and returns a float. Then, instead of using Java 8 method references, you would have to pass to your getValuesAsArray method an implementation of that interface (you could use an anonymous class instance implementing the interface) which calls one of the getX() methods.

For example :

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

And

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;
}
Eran
  • 387,369
  • 54
  • 702
  • 768
  • @EarthEngine It states "Java 8 **(Lambdas)**" ... so I think it is legit to suggest functional interfaces. – Fildor Aug 24 '16 at 08:43
  • @EarthEngine I added a suggestion for pre-Java 8 implementation, though it would be much less elegant. – Eran Aug 24 '16 at 08:45
  • @Eran what about the [Supplier](https://docs.oracle.com/javase/8/docs/api/java/util/function/Supplier.html) interface? I feel like it would be very fitting here. But I am not too experienced with the new Java 8 stuff - so just asking. – Fildor Aug 24 '16 at 08:46
  • Generally, I am happy to see suggestions even involving java 8. So Thank you! However, for the current project I would prefer to stick to java 7, unfortunately. – W.E.Coyote Aug 24 '16 at 08:46
  • 1
    @Fildor A `Supplier` produces a value of type T without taking any input. It would be fitting in this question if the `getX()` methods were static. – Eran Aug 24 '16 at 08:50
  • 1
    @SwanAH Please see edit for Java 7 solution (which unfortunately is less elegant). – Eran Aug 24 '16 at 09:10
  • @Eran Less elegant, but great anyway, thank you! I thought in that direction, but didnt get it straight to work for me. Also didnt think of putting the interface implementation anonymously as "one-liner" in the actual call. – W.E.Coyote Aug 24 '16 at 09:17
4

Awhile ago I used java.util.concurrent.Callable but it doesn't seem to work out, thanks to @Eran.

Instead, you can use Java 8's java.util.function.Function, like so (without the lambdas):

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;
}
Julius Delfino
  • 991
  • 10
  • 27
  • Based on the question, the `getA()`,`getB()`,`getC()` methods are not static. They should be called for a specific instance of DataStore. – Eran Aug 24 '16 at 08:47
  • I was only using his sample code. The point here is that he can wrap the method with a `Callable` so that the method can be invoked at a later time. – Julius Delfino Aug 24 '16 at 08:49
  • You can only call `DataStore.getA()` if `getA()` is static, regardless of whether or not you wrap it with a Callable. – Eran Aug 24 '16 at 08:52
1

There is a workaround: Scala java apis.

I use Apache Spark and scala offers a series of Anonymous Functions (Function, Function2) which are available since Java 1.5, if I'm not mistaken (although I use it with Java 1.7).
Here is an answer talking about this. Because otherwise the "Function" class is available only from Java 1.8

Community
  • 1
  • 1
Vale
  • 1,104
  • 1
  • 10
  • 29
0

MethodReference is a class for reflection purpose. Your code actually need a lambda-like object, which shall be a single method interface in Java 8.

Without Java 8 or reflection there is no way to directally meet your need though. But you can always pass some internal representation of the method to another calss, and to do so you have to write code to process this internal representation.

Earth Engine
  • 10,048
  • 5
  • 48
  • 78
  • Oh sorry, misleading. I used it merely as a placeholder for some proper mechanism to pass my method without using java 8 or reflection if possible. – W.E.Coyote Aug 24 '16 at 08:39
0

You can use Command Design Pattern

for more info :

https://en.wikipedia.org/wiki/Command_pattern http://www.tutorialspoint.com/design_pattern/command_pattern.htm

Maged Milad
  • 309
  • 1
  • 3
  • 11