You sketched two ways of offering a certain functionality
The first one is to explicitly offer it as a method
public static String putInQ1(String s) {
return Q1.concat(s).concat(Q1);
}
which is supposed to be used via a method reference.
The second one is to offer it as a Function
object:
Function<String, String> putInQ1 = n -> Q1.concat(n).concat(Q1);
(Here, you did not say where these instances should be located. I assume that you would also create a class that contained all these Function
instances as (possibly public static final
fields)
JBNizet mentioned a third option: You could use the methods directly, and not via method references. Indeed, the purpose of the transform
function is not entirely clear. The only justification for this would be that you want to pass in arbitrary method references there, but these method references would just be Function
objects - like in the second approach...
However, in a technical sense, the difference is not so large. Just to illustrate the point: Both approaches can trivially be converted into each other! The method can be implemented based on the function object
public static String putInQ1(String s) {
return putInQ1.apply(s);
}
And a function object can be created from the method reference:
Function<String, String> putInQ1 = StringUtils::putInQ1;
So the main question may be: How do you want to offer this functionality to the user of your library?
For this, consider the use case the you have an input string, and want to put it into (
parentheses )
, and the result into '
single quotes '
:
String doItWithMethodReferences(String input) {
String result = input;
result = StrUtils.transform(result, StrUtils::putInParens);
result = StrUtils.transform(result, StrUtils::putInQ1);
return result;
}
String doItWithFunctionObjects(String input) {
String result = input;
result = StringFunctions.putInParens.apply(result);
result = StringFunctions.putInQ1.apply(result)
return result;
}
String doItWithMethods(String input) {
String result = input;
result = StrUtils.putInParens(result);
result = StrUtils.putInQ1(result);
return result;
}
You can see that there is hardly a difference between the approaches that would qualify one of them as "better" or "worse" than the other in terms of readability, except for the obvious fact that the last one is simpler than the first one by avoiding the unnecessary transform
calls.
Of course, each of these methods could be written "more compactly", in a single line. But depending on the number and the structure of the operations, this could severely reduce the readability, and in fact, this leads to another point: I could imagine that extensibility may something to consider. Imagine you wanted to create a single operation that placed a string into '(
single quotes and parentheses )'
at once.
With methods:
public static String putInPandQ1(String s) {
return putInQ1(putInParens(s));
}
With functions:
Function<String, String> putInPandQ1 = putInParens.andThen(putInQ1);
I think that the andThen
function would be a nice feature that helps to compose more complex string manipulations.
(But taking that arbitrarily far, one has to ask whether you are not actually attempting to implement a template engine or a new domain-specific programming language...)
A short note: All this seems fairly unrelated to performance. Whether you do return s0 + s1;
or return s0.concat(s1)
will often not matter, and in the few cases where it does matter, you can change the implementation later - because, given the functionality that is sketched in the question, the decision about using +
or concat
or some StringBuilder
trickery is exactly that: An implementation detail.
And another note, as pointed out in the comments: Instead of defining your own StringFunction
interface, you could use UnaryOperator<String>
. Both are "structurally equal", but the first one is part of the standard API. Imagine that there are already many libraries out there, with methods that expect the standard UnaryOperator<String>
as an argument. When you only have instances of your own StringFunction
, then you may have to convert these instances so that your code can cooperate with other code. This is trivial, of course, but the interfaces in the functional
package are carefully chosen to cover a large range of application cases, and I think that the interoperability between libraries can be greatly increased when programmers don't needlessly create new interfaces that already exist in the standard API. One could argue that the introduction of the StringFunction
makes code easier, because it does not need the <String>
generic parameter. But if you want this, then you should simply declare the iterface as interface StringFunction extends UnaryOperator<String> { }
, which simply is a further specialization, and will keep the compatibility with other code. Additionally, you'll then conveniently inherit all the default
methods from Function
, like the andThen
that I mentined above.