29

I'm looking for a very simple way of getting the equivalent of something like the following JavaScript code. That is, for each match I would like to call a certain transformation function and use the result as the replacement value.

var res = "Hello World!".replace(/\S+/, function (word) {
    // Since this function represents a transformation,
    // replacing literal strings (as with replaceAll) are not a viable solution.
    return "" + word.length;
})
// res => "5 6"

Only .. in Java. And, preferably as a "single method" or "template" that can be reused.

user2864740
  • 60,010
  • 15
  • 145
  • 220

4 Answers4

30

Your answer is in the Matcher#appendReplacement documentation. Just put your function call in the while loop.

[The appendReplacement method] is intended to be used in a loop together with the appendTail and find methods. The following code, for example, writes one dog two dogs in the yard to the standard-output stream:

Pattern p = Pattern.compile("cat");
Matcher m = p.matcher("one cat two cats in the yard");
StringBuffer sb = new StringBuffer();
while (m.find()) {
    m.appendReplacement(sb, "dog");
}
m.appendTail(sb);
System.out.println(sb.toString());
user2864740
  • 60,010
  • 15
  • 145
  • 220
jtahlborn
  • 52,909
  • 5
  • 76
  • 118
  • 2
    [Here](http://stackoverflow.com/a/1282099/20938) is a more complete solution. It deals with the problem of dollar signs and backslashes being treated as special by `appendReplacement()`. – Alan Moore Nov 02 '13 at 01:51
16

When allowing Java 8 you can use Lambda-Expressions, to have a JavaScript like replace:

String result = StringReplacer.replace("Hello World!", Pattern.compile("\\S+"), m -> ("" + m.group().length()));

StringReplacer.java:

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class StringReplacer {
    public static String replace(String input, Pattern regex, Function<Matcher, String> callback) {
        StringBuffer resultString = new StringBuffer();
        Matcher regexMatcher = regex.matcher(input);
        while (regexMatcher.find()) {
            regexMatcher.appendReplacement(resultString, callback.apply(regexMatcher));
        }
        regexMatcher.appendTail(resultString);

        return resultString.toString();
    }
}

Source: http://www.whitebyte.info/programming/string-replace-with-callback-in-java-like-in-javascript

Nick Russler
  • 4,608
  • 6
  • 51
  • 88
  • 4
    You might want to call `Matcher.quoteReplacement` on the replacement string, to avoid slashes and dollar signs being treated specially. – Gareth Apr 25 '16 at 10:57
  • @Gareth it would be awesome if you could fork the [gist](https://gist.github.com/nickrussler/42f8619f1a36eea25364), improve it and comment back with the link. I'll then update the post and my gist ASAP. – Nick Russler Apr 30 '16 at 14:55
16

Since Java 9 Matcher.replaceAll:

Pattern.compile("\\S+").matcher("Hello World!")
        .replaceAll(mr -> "" + mr.group().length());

The parameter freely named mr is a MatchResult, with access methods like mr.group(1) or mr.end() - mr.start().

Joop Eggen
  • 107,315
  • 7
  • 83
  • 138
-1

Not sure what your precise requirements are but something like this could work:

String res = "";
for (String piece : "hello world".split(" "))
  res += Integer.toString(piece.length()) + " ";

Of course there are other ways to write that, and tweaks that can be made depending on requirements (e.g. use a more accurate delimiter than a space).

For a precise implementation of your snippet, you could use e.g. StreamTokenizer with a StringReader and some wrappers to parse out the delimiters and insert them between the counts.

Jason C
  • 38,729
  • 14
  • 126
  • 182
  • 2
    While this would work here, I'm looking for a solution that can remain generic (through utilizing regular expression support). – user2864740 Nov 02 '13 at 01:10