TL;DR
There are several ways to parameterize methods in Java:
- The lowest level by reflection;
- The traditional one using a specific interface;
- Directly in the most modern form introduced in Java 8
Examples
Given the class below:
class Sorting {
public static void staticSort(String[] vetor) {
Arrays.sort(vetor);
}
public void sort(String[] vetor) {
Arrays.sort(vetor);
}
}
And the following vector of String
:
String[] vetor = { "Maria", "José" };
Reflection
We can use the guy Method
to receive the method reference, thus:
public static long executar(Method metodo, String[] vetor) {
long initialTime = System.nanoTime();
try {
metodo.invoke(null, new Object[] { vetor });
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
return System.nanoTime() - initialTime;
}
The method invoke
of the instance of a method allows calling the referenced method. The first parameter is the object instance, but if the method is static just pass null
and then an array with the parameters, which in this case is an array of String
.
We get the reference to the method like this:
Method metodo = Sorting.class.getDeclaredMethod("staticSort", String[].class);
And the method call goes like this:
executar(metodo, vetor);
The problems of reflection include:
- It can lead to runtime errors that are not caught at build time as the names of the methods are recovered using simple
String
s.
- Performance. Using reflection is much slower.
- Verbose and "ugly" code, having to deal with several complicated exceptions.
Using interfaces
An indirect way to pass a method is to use specific interfaces for a particular use.
The most common example of this is threads that implement the interface Runnable
. In fact, this interface can and is used in different applications that need to pass a method to be executed that neither receives nor returns value.
Anyway, you can create your own interface. Example:
interface Sorter {
void sort(String[] vetor);
}
And then you can create as many implementations as you want. Example:
class BubbleSorter implements Sorter {
@Override
public void sort(String[] vetor) { ... }
}
And the consumer method will be:
void executar(Sorter sorter, String[] vetor) { ... }
So the main method will be something like this:
Sorter bubbleSorter = new BubbleSorter();
executar(bubbleSorter, vetor);
The advantage of this approach is that it is efficient, makes code easy to understand and also extends with new types of implementations.
The downside is that it requires creating new interfaces for each type of method you want to parameterize.
This is a reasonably flexible approach as it allows any object to implement this interface, but not as flexible as you can only have one method sort
by class and must have the same signature.
Java 8 Method References
It is not only from Lâmbdas that the Java 8 gained fame. This version also introduced references to methods.
There are three main ways of semantically representing method references, using the following interfaces:
Consumer
: a consumer method that takes a parameter and returns nothing.
Supplier
: a vendor that does not receive parameters and returns a value.
Function
: a function that receives a parameter and returns a value.
There are other types of functional interfaces more specialized, but these are the most generic.
A generic execution method could then be written like this:
public static long executar(Consumer<String[]> metodo, String[] vetor) {
long initialTime = System.nanoTime();
metodo.accept(vetor);
return System.nanoTime() - initialTime;
}
We use the interface Consumer
, since our method only receives a vector of String
and returns nothing.
The call to our execution method using the static sorting method described above would look like this:
executar(Sorting::staticSort, vetor);
If we want to refer to an instance method, just use the instance name before the operator ::
, thus:
Sorting instancia = new Sorting();
executar(instancia::sort, vetor);
This approach is more flexible and superior semantically than previous editions.
Note that any method that is compatible with the interface used as a parameter can be passed as a reference. It doesn’t necessarily have to be the 3 interfaces I mentioned so. For example, if you use the interface Comparator
as a parameter, you can reference any method that receives two parameters of the same type. You can use your own interfaces as well.
The limitation here is in the amount of parameters. If you want to pass multiple values, you will need to have an interface with the right amount of parameters with the same types. However, this limitation is essential for the program to be compiled safely, otherwise several errors would go unnoticed by the time of execution.
Considerations
In the above examples, I used a vector of String
as type. This may seem limiting. However, remember that you can use generics to create functional interfaces that "force" the client code to pass a method that is a function, consumer or supplier respecting the type rules imposed by you.
Finally, if you want to print the vector and check the result, you can use this:
System.out.println(Arrays.stream(vetor).collect(Collectors.joining(", ")));
Pass the return of the method as paramtro, yes, now use a method as if it were an object, I believe not.
– user28595
Try to explain better what you intend to do. In the method
main()
what aremetodoA
andmetodoB
? Are methods of the objectteste
?– ramaral
Yes, methodoA and metodoB are test object methods.
– Bruno Brito