2

Suppose I have an application that needs to apply several custom transformation on strings. The needs will grow by time. The following two approaches do exactly the same thing, but I am wondering which one is more beneficial in the long run. Are they the same? Or, does one offer more benefits than the other as the number of transforms increase and vary?

Suppose we have these:

public static final String PL = "(";
public static final String PR = ")";
public static final String Q1 = "'";

Here is each approach's setup and usage.

Approach 1:

@FunctionalInterface
public interface StringFunction {
   String applyFunction(String s);
}

public class StrUtils {

   public static String transform(String s, StringFunction f) {
      return f.applyFunction(s);
   }

   public static String putInQ1(String s) {
      return Q1.concat(s).concat(Q1);
   }

   public static String putInParens(String s) {
      return PL.concat(s).concat(PR);
   }

   // and so on...
}

Which I would use like this:

System.out.println(StrUtils.transform("anSqlStr", StrUtils::putInQ1));
System.out.println(StrUtils.transform("sqlParams", StrUtils::putInParens));

Approach 2:

Here, I use straightforward Function:

Function<String, String> putInQ1 = n -> Q1.concat(n).concat(Q1);
Function<String, String> putInParens = n -> PL.concat(n).concat(PR);
// and so on...

Which I would use like this:

System.out.println(putInQ1.apply("anSqlStr");
System.out.println(putInParens.apply("sqlParams");
mohsenmadi
  • 2,277
  • 1
  • 23
  • 34
  • 4
    What's the point? Why not just use `StrUtils.putInQ1("anSqlStr")` and `StrUtils.putInParens("sqlParams")`? And why not just use `return "(" + s + ")"`, which is much more readable, and also more efficient? – JB Nizet Dec 24 '16 at 00:04
  • The `.concat()` method is much slower than using `+`, which the compiler optimizes. – 4castle Dec 24 '16 at 00:04
  • 1
    You don't really need to define `StringFunction` at all. You can just use `UnaryOperator`. (Not that it's very important, because the approach suggested by JB Nizet will be much better in the long run). – 4castle Dec 24 '16 at 00:08
  • If you're only going to access a method using a method reference, there is no point in having the method. Otherwise it's better to keep it. – Bubletan Dec 24 '16 at 00:13
  • What's the point? Just using Java-8 features really; otherwise, what's the point of creating `Function` as a Java 8 feature with its method `apply` then? Is there a scenario that `Function` will do/perform better than the normal method definition way? – mohsenmadi Dec 24 '16 at 00:54
  • Why say `concat()` method is slower? Why does [this](http://stackoverflow.com/questions/47605/string-concatenation-concat-vs-operator) say different? – mohsenmadi Dec 24 '16 at 00:59
  • Functions are useful because they can be passed to methods expecting functions, like Stream methods (map, flatMap, etc.) But creating a transform() method that just delegates to a method doesn't bring any advantage over just calling the method directly. repeated calls to concat() is slower() because it creates several temporary copies of the string, whereas using + just creates a stringbuilder, appends everything, and then creates the result. The link you gave explains just that, and does not say otherwise. – JB Nizet Dec 24 '16 at 09:00

2 Answers2

1

You sketched two ways of offering a certain functionality

  1. 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.

  2. 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.

Marco13
  • 53,703
  • 9
  • 80
  • 159
  • Thank you Marco, and thank you for handling the `+` x `concat()` issue also. I knew that it could be a matter of preference more than anything else, and that performance-wise, they are similar. Kudos for the `putInParens.andThen(putInQ1)`, this is a plus for the Java 8 features over standard method usage. I hope to get more hits on either side as we go along. And yes, `public static final` qualifiers are to be there if such a list of functions grow long so they better end up in one place. – mohsenmadi Dec 24 '16 at 04:43
-1

Why not simply define the method 'putInWhatever(String s, String left, String right) { return left + s + right; }

with overloaded variants in case left and right are equal. No complicated functional interfaces and lambda's needed

  • 1
    This completely misses the point. The question asks about the difference between custom functional interfaces and just plain Java library Functions. The example with strings is purely hypothetical. – HTNW Dec 24 '16 at 01:28