How to pass a method name as a parameter in Java

Viewed 37

Is there a way to parameterize a method name?

Example:

JournalLine {

    BigDecimal ccyAmount;
    BigDecimal lcyAmount;
    BigDecimal rptAmount;
   
    // Getters and Setters
}

Original (working)

// Calculate totals
BigDecimal totalCcyAmount = journalLines.stream()
                                        .map(journalLine -> journalLine.getCcyAmount())
                                        .reduce((a, b) -> a.add(b))
                                        .orElse(BigDecimal.ZERO);

BigDecimal totalLclAmount = journalLines.stream()
                                       .map(journalLine -> journalLine.getLclAmount())
                                       .reduce((a, b) -> a.add(b))
                                       .orElse(BigDecimal.ZERO);

BigDecimal totalRptAmount = journalLines.stream()
                                        .map(journalLine -> journalLine.getRptAmount())
                                        .reduce((a, b) -> a.add(b))
                                        .orElse(BigDecimal.ZERO);
                                    
                                    

This duplication is used in different locations of the application. Not always together.

Is there a way to do something like:

//Calculate totals and pass the method name
BigDecimal totalCcyAmount = getTotal(journalLines, "getCcyAmount");
BigDecimal totallclAmount = getTotal(journalLines, "getCcyAmount");
BigDecimal totalRptAmount = getTotal(journalLines, "getCcyAmount");
                                        
public BigDecimal getTotal( List<JournalLine> journalLines, String METHOD_NAME) {
    return journalLines.stream()
                         .map(journalLine -> journalLine.METHOD_NAME)
                         .reduce((a, b) -> a.add(b))
                         .orElse(BigDecimal.ZERO);
}

I want to pass METHOD_NAME (getCcyAmount() or getLcyAmount() or getRptAmount ()) or use a different approach to avoid duplication of code.

1 Answers

Without using reflection, your best choice is probably passing a FunctionalInterface to the method:

Method call:

getTotal(journalLines, JournalLine::getCcyAmount);

and the method looks like this:

public BigDecimal getTotal(List<JournalLine> journalLines,
        Function<JournalLine, BigDecimal> function) {
    return journalLines.stream().map(function).reduce((a, b) -> a.add(b))
            .orElse(BigDecimal.ZERO);
}

If you are really fixed on using a String as method argument, you wont get around using reflection. And that is where it becomes ugly, codewise:

private static BigDecimal getTotal(List<JournalLine> journalLines, String methodName) {
    Method method;
    try {
        method = JournalLine.class.getDeclaredMethod(methodName);
    } catch (NoSuchMethodException | SecurityException e2) {
        e2.printStackTrace();
        throw new RuntimeException("Unhandled", e2);
    }
    return journalLines.stream().map(e -> {
        try {
            return (BigDecimal) method.invoke(e);
        } catch (IllegalAccessException | IllegalArgumentException
                | InvocationTargetException e1) {
            e1.printStackTrace();
            throw new RuntimeException("Unhandled", e1);
        }
    }).reduce((a, b) -> a.add(b)).orElse(BigDecimal.ZERO);
}

This would then work by calling

getTotal(list, "getCcyAmount");

However, you are way more prone to errors, by e.g. having a typo in your methodname string. The compiler can't even tell you before running into an error, as it is just a String, until the reflection starts.


Overall I suggest using FunctionalInterfaces over reflection. Not only is the code less cluttered, but also you have the support of the compiler while coding.

Related