Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Regex replace with Stream

By Curiosity, is there a way to write this with Java Stream ?

private final static Pattern decodePattern = Pattern.compile("&#(\\d+);");

StringBuffer buf = new StringBuffer();

Matcher m = decodePattern.matcher(somestring);
while (m.find()) {
    m.appendReplacement(buf, String.valueOf((char) Integer.parseInt(m.group(1))));
}
m.appendTail(buf);

String decodeString = buf.toString();
like image 279
lvr123 Avatar asked Sep 04 '26 19:09

lvr123


1 Answers

Java 9

Cleaner code could be achieved with overloaded in Java 9 method from Matcher class:
public String replaceAll​(Function<MatchResult,String> replacer). Basically we can now replace code like

StringBuffer sb = new StringBuffer();
while(matcher.find()){
    matcher.appendReplacement(sb, /*create replacement*/);
}
matcher.appendTail(sb);
String result = sb.toString;

with

String replaced = matcher.replaceAll(match -> /*create replacement*/);

For example

String replaced = Pattern.compile("\\b\\w")
                         .matcher("foo bar baz")
                         .replaceAll(match -> match.group().toUpperCase());
//replaced: "Foo Bar Baz"

There was also added support for stream of elements matching pattern: public Stream<MatchResult> results​(), but IMO your loop doesn't look like good candidate for streams. Even with results() your code would look like:

//BTW Java 9 provides support for StringBuilder beside StringBuffer 
//for appendReplacement/appendTail

Matcher matcher = ...
StringBuilder buf = new StringBuilder(); 

matcher.results()
       .map(result -> String.valueOf((char) Integer.parseInt(result.group(1))) )
       .forEach(replacement -> matcher.appendReplacement(buf, replacement));
matcher.appendTail(buf);

String decodeString = buf.toString();

so it doesn't look much cleaner.


Java 8

In Java 8 Pattern and Matcher classes didn't change much in terms of stream support. Only Pattern received public Stream<String> splitAsStream(CharSequence input) method, but it creates stream of elements where pattern represents delimiter on which we want to split, not text which we want to find.

If you want to simplify your code in Java 8, write your own method in which you will supply Matcher and function which will map matched content (preferably represented by MatchResult or Matcher so it could access group(...) methods) to replacement which should be put instead of it.

Such method could look like:

public static String replaceMatches(Matcher m, Function<MatchResult, String> mapping){
    
    StringBuffer sb = new StringBuffer();
    while(m.find()){
        MatchResult matchResult = m.toMatchResult();
        m.appendReplacement(sb, mapping.apply(matchResult));
    }
    m.appendTail(sb);
    
    return sb.toString();
}

and you could use it like:

Pattern p = Pattern.compile("\\b\\w");
Matcher m = p.matcher("foo bar baz");

String result = replaceMatches(m, mr -> mr.group().toUpperCase());

System.out.println(result);

Result: Foo Bar Baz.

like image 144
Pshemo Avatar answered Sep 06 '26 08:09

Pshemo



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!