- Java Function passing using Interfaces / Strategy Pattern
- Using Template Method Pattern
- Think about alternatives for passing functions
- Using Lambda to pass a function in Java
Interfaces / Strategy Pattern
With an interface, it is possible to mimic function passing in Java. An example:
/**
* returns the added results of func. Func recieves [0..times-1].
*/
private int accumulate(Function func, int times) {
int result = 0;
for (int i = 0; i < times; i++) {
result += func.calc(i);
}
return result;
}
/**
* A function interface.
*/
interface Function {
int calc(int x);
}
* returns the added results of func. Func recieves [0..times-1].
*/
private int accumulate(Function func, int times) {
int result = 0;
for (int i = 0; i < times; i++) {
result += func.calc(i);
}
return result;
}
/**
* A function interface.
*/
interface Function {
int calc(int x);
}
/**
* adds all the numbers from 1 to the given number.
*/
public int addNumbersTo(int limit) {
return accumulate(new Function() {
@Override
public int calc(int x) {
return x + 1;
}
}, limit);
}
/*
* adds up the first X even numbers.
*/
public int addFirstXEvenNumbers(int x) {
return accumulate(new Function() {
@Override
public int calc(int x) {
return (2 * x) + 2;
}
}, x);
}
* adds all the numbers from 1 to the given number.
*/
public int addNumbersTo(int limit) {
return accumulate(new Function() {
@Override
public int calc(int x) {
return x + 1;
}
}, limit);
}
/*
* adds up the first X even numbers.
*/
public int addFirstXEvenNumbers(int x) {
return accumulate(new Function() {
@Override
public int calc(int x) {
return (2 * x) + 2;
}
}, x);
}
Basically, this is the Strategy Pattern without context and with anonymous implementations of the interfaces.
Advantages:
I hope the advantage of doing this becomes clear: We can extract code that is used by a whole lot of functions to make the code cleaner and easier to maintain.
Disadvantages:
The code is quite verbose in Java.
Speed: it is slower than writing all functions individually. There are two reasons for this: the general overhead from function calling (which can generally be neglected) and the fact that the Java compiler is not able to perform a lot of optimizations. Most of the time, the speed difference will not matter though.
Template Method Pattern
In some situations it makes more sense to use the template method pattern.
public abstract class Accumulator {
/**
* returns the added results of this accumulator.
*/
public int accumulate(int times) {
int result = 0;
for (int i = 0; i < times; i++) {
result += getNextNumber(i);
}
return result;
}
/**
* returns the i'th number.
*/
abstract int getNextNumber(int i);
}
public class AllNumberAcc extends Accumulator {
@Override
int getNextNumber(int i) {
return i + 1;
}
}
public class EvenNumberAcc extends Accumulator {
@Override
int getNextNumber(int i) {
return (2 * i) + 2;
}
}
/**
* returns the added results of this accumulator.
*/
public int accumulate(int times) {
int result = 0;
for (int i = 0; i < times; i++) {
result += getNextNumber(i);
}
return result;
}
/**
* returns the i'th number.
*/
abstract int getNextNumber(int i);
}
public class AllNumberAcc extends Accumulator {
@Override
int getNextNumber(int i) {
return i + 1;
}
}
public class EvenNumberAcc extends Accumulator {
@Override
int getNextNumber(int i) {
return (2 * i) + 2;
}
}
EvenNumberAcc ena = new EvenNumberAcc();
ena.accumulate(10);
ena.accumulate(10);
It is more flexible than the approach described above
Disadvantages:
It is more work to add a new function and if too many are needed, the many classes may get confusing.
Think about alternatives
If the code is only used by some functions which do nearly the same think about how the function can be changed (sometimes it is enough to add an extra boolean parameter to the signature and an if clause to the method body).
If for example you can be sure that anything you will ever accumulate are numbers up to ten or the first ten even numbers, the above example could look like this:
/**
* returns the added results of func. Func receives [0..times-1].
*/
private int accumulate(boolean allNumbers, int times) {
int result = 0;
for (int i = 0; i < times; i++) {
if (allNumbers) {
result += (i + 1);
} else {
result += ((2 * i) + 2);
}
}
return result;
}
/**
* adds all the numbers from 1 to 10 (result will always be 55).
*/
public int addNumbersToTen() {
return accumulate(true, 10);
}
/*
* adds up the first ten even numbers.
*/
public int addFirstTenEvenNumbers() {
return accumulate(false, 10);
}
* returns the added results of func. Func receives [0..times-1].
*/
private int accumulate(boolean allNumbers, int times) {
int result = 0;
for (int i = 0; i < times; i++) {
if (allNumbers) {
result += (i + 1);
} else {
result += ((2 * i) + 2);
}
}
return result;
}
/**
* adds all the numbers from 1 to 10 (result will always be 55).
*/
public int addNumbersToTen() {
return accumulate(true, 10);
}
/*
* adds up the first ten even numbers.
*/
public int addFirstTenEvenNumbers() {
return accumulate(false, 10);
}
Also, there is a performance loss as the if statement has to be executed each time. But again, in most cases this can be neglected.
The above solution is fine for smaller programs, but Java is an object oriented programming language, so that is what should generally be used (most of the time it is a bad idea to fight against the way a language was designed).
Using Lambda to pass a function in Java
So Java now finally offers lambda, which makes passing a function easier (but it is not nearly perfect).
With Lamdba, the code would look similar to the strategy pattern described above. But instead of having to define a new (anonymous) class, we can just pass a function. The accumulate function and Function class look the same, but here is how they are called:
/**
* adds all the numbers from 1 to the given number.
*/
public int addNumbersTo(int limit) {
return accumulate((int i) -> i + 1, limit);
}
/*
* adds up the first X even numbers.
*/
public int addFirstXEvenNumbers(int x) {
return accumulate((int i) -> (2 * i) + 2, x));
}
* adds all the numbers from 1 to the given number.
*/
public int addNumbersTo(int limit) {
return accumulate((int i) -> i + 1, limit);
}
/*
* adds up the first X even numbers.
*/
public int addFirstXEvenNumbers(int x) {
return accumulate((int i) -> (2 * i) + 2, x));
}
This is a lot shorter than the strategy pattern used to pass a function in Java. And once you get used to it, also a lot more readable.
The ways to mimic function passing in Java are quite similar, but they are not the same. It depends on the situation which one to use (or if it would be better to use something entirely different).