开发者

How can I map a String to a function in Java?

Currently, I have a bunch of Java classes that implement a Processor interface, meaning they all have a processRequest(String key) method. The idea is that each class has a few (say, <10) member Strings, and each of those maps to a method in that class via the processRequest method, like so:

class FooProcessor implements Processor
{
    String key1 = "abc";
    String key2 = "def";
    String key3 = "ghi";
    // and so on...

    String processRequest(String key)
    {
        String toReturn = n开发者_如何学JAVAull;
        if (key1.equals(key)) toReturn = method1();
        else if (key2.equals(key)) toReturn = method2();
        else if (key3.equals(key)) toReturn = method3();
        // and so on...

        return toReturn;
    }

    String method1() { // do stuff }
    String method2() { // do other stuff }
    String method3() { // do other other stuff }
    // and so on...
}

You get the idea.

This was working fine for me, but now I need a runtime-accessible mapping from key to function; not every function actually returns a String (some return void) and I need to dynamically access the return type (using reflection) of each function in each class that there's a key for. I already have a manager that knows about all the keys, but not the mapping from key to function.

My first instinct was to replace this mapping using if-else statements with a Map<String, Function>, like I could do in Javascript. But, Java doesn't support first-class functions so I'm out of luck there. I could probably dig up a third-party library that lets me work with first-class functions, but I haven't seen any yet, and I doubt that I need an entire new library.

I also thought of putting these String keys into an array and using reflection to invoke the methods by name, but I see two downsides to this method:

  • My keys would have to be named the same as the method - or be named in a particular, consistent way so that it's easy to map them to the method name.
  • This seems WAY slower than the if-else statements I have right now. Efficiency is something of a concern because these methods will tend to get called pretty frequently, and I want to minimize unnecessary overhead.

TL; DR: I'm looking for a clean, minimal-overhead way to map a String to some sort of a Function object that I can invoke and call (something like) getReturnType() on. I don't especially mind using a 3rd-party library if it really fits my needs. I also don't mind using reflection, though I would strongly prefer to avoid using reflection every single time I do a method lookup - maybe using some caching strategy that combines the Map with reflection.

Thoughts on a good way to get what I want? Cheers!


There aren't any first-class standalone functions, but you can do what you want with an interface. Create an interface that represents your function. For example, you might have the following:

public interface ComputeString
{
    public String invoke();
}

Then you can create a Map<String,ComputeString> object like you want in the first place. Using a map will be much faster than reflection and will also give more type-safety, so I would advise the above.


While you can't have first class functions, there are anonymous classes which can be based on an interface:

interface ProcessingMethod {
   String method();
}

Map<String, ProcessingMethod> methodMap = new HashMap<String, ProcessingMethod>();
methodMap.put("abc", new ProcessingMethod() {
   String method() { return "xyz" }
});
methodMap.put("def", new ProcessingMethod() {
   String method() { return "uvw" }
});

methodMap.get("abc").method();

Or you could use Scala :-)


Couldn't you do String to Method? Then you can cache the methods you need to execute.


This example uses an enum of named functions and an abstract FunctionAdapter to invoke functions with a variable number of homogeneous parameters without reflection. The lookup() function simply uses Enum.valueOf, but a Map might be worth it for a large number of functions.


As you've noticed, you can do what you want using the Reflection API, but you loose some benefits of the Java compiler, on top of the issues you've already come up with. Would wrapping your Strings in an object and using the Visitor pattern solve your issue? Each StringWrapper would only accept a Visitor that has the right method, or something along those lines.


Use a Map where the key is a string and the value is an object that implements an interface containing method(). That way you can get the object containing the method you want out of the map. Then just call that method on the object. For example:

class FooProcessor implements Processor{

    Map<String, FooMethod> myMap;

    String processRequest(String key){
        FooMethod aMethod = myMap.getValue(key);
        return aMethod.method();
    }        
}


What about Method class from the reflection API? You can find methods of a class based on name, parameters, or return type. Then you just call Method.invoke(this, parameters).

That's pretty much the same as a Map from JavaScript you are talking about.


public class CarDetailsService {

    private final CarRepository carRepository;

    private final Map<String, Function<CarDTO, String>> carColumnMapper = new HashMap<>();

    public ApplicationDetailsServiceImpl(CarRepository carRepository) {
        this.carRepository = carRepository;

       //---- Initialise all the mappings ------- //

        carColumnMapper.put("BRAND", CarDTO::getBrandName);
        carColumnMapper.put("MILEAGE", CarDTO::getMileage);
    }

    public Map<String, List<CarDTO>> getListOfCars(String groupBy) {

        return carRepository.findAll()
                .stream()
                .map(toCarDTO)
                .collect(groupingBy(carColumnMapper.get(groupBy.toUpperCase())));
    }

    Function<CarDetails, CarDTO> toCarDTO = (carDetails) -> CarDTO
            .builder()
            .brand(carDetails.getBrand())
            .engineCapacity(carDetails.getEngineCapacity())
            .mileage(carDetails.getMileage())
            .fuel(carDetails.getFuel())
            .price(carDetails.getPrice())
            .build();
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜