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, "getLclAmount");
BigDecimal totalRptAmount = getTotal(journalLines, "getRptAmount");
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.
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 FunctionalInterface
s over reflection. Not only is the code less cluttered, but also you have the support of the compiler while coding.