1

I'm currently stuck figuring out how method references works. This is what I'm trying to achieve.

User user = new User(...);
user.getValue(ADerived::getPropertyFromA);
user.getValue(BDerived::getPropertyFromB);

ADerived and BDerived are as following (simplified)

public abstract class Base { }

public class ADerived extends Base {
    private static final String A_OUTPUT = "Test A";

    public String getPropertyFromA() {
        return A_OUTPUT;
    }
}

public class BDerived extends Base {
    private static final String B_OUTPUT = "Test B";

    public String getPropertyFromB() {
        return B_OUTPUT;
    }
}

The getPropertyFromX method is not defined in the the Base class on purpose since they differ to much.

In the BaseUser class I save an instance of the subclasses in a map and try to execute the passed method on this instance. Atleast this code compiles but of course doesn't work as expected. Other approaches around Function<> did not work for me either.

public class User {
    public Map<Class, Base> map;

    public User(...) {
        ... creates instances of ADerived and BDerived, puts them into map
    }

    ...

    public <R> String getValue(Function<R, String> func) {
        return func.apply((R) map.get((R) Base.class));
    }
}

This is clearly not working for multiple reasons but I can't figure out a way to get the usage described above.

Is it even realistic to get the described usage? Is there anything I'm missing?

Would love to hear ideas or get some help.

AndiAn
  • 83
  • 1
  • 5
  • 1
    When the key is always the same (`Base.class`), how can you assume that is mapping to an appropriate input to a function that requires an arbitrary `R`? – Holger Jun 22 '21 at 07:23

1 Answers1

1

It's not possible as you're currently doing. This answer explains it well.

You can, however, do something like that:

class User {
    // ...

    public <C extends Base> String getValue(Class<C> cls, Function<C, String> func) {
        return func.apply(cls.cast(map.get(cls)));
    }
}

class Main {
    public static void main(String[] args) {
        User user = new User();
        String value;
        
        value = user.getValue(ADerived.class, ADerived::getPropertyFromA);
        System.out.println(value); // Outputs Test A
        
        value = user.getValue(BDerived.class, BDerived::getPropertyFromB);
        System.out.println(value); // Outputs Test B 
    }
}

Or, if you want to avoid repeting the class name, you can use the method name as string. CAUTION: this approach will lose compile-time checking and uses reflection, but I'm putting it here just for completeness.

import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;

// ...

class User {
    // ...

    public <C extends Base> String getValue(Class<C> cls, String methodName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        final Method method = cls.getMethod(methodName);
        return (String) method.invoke(map.get(cls));
    }
}

class Main {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        User user = new User();
        String value;
        
        value = user.getValue(ADerived.class, "getPropertyFromA");
        System.out.println(value); // Outputs Test A
        
        value = user.getValue(BDerived.class, "getPropertyFromB");
        System.out.println(value); // Outputs Test B 
    }
}

You should handle

  • NoSuchMethodException when the method name does not exists in the given class;

  • IllegalAccessException when the method trying to be accessed is protected or private;

  • InvocationTargetException when the method throws when accessed.

enzo
  • 9,861
  • 3
  • 15
  • 38