35

I need to replace a dynamic substring withing a larger string, but only once (i.e. first match). The String class provides only replace(), which replaces ALL instances of the substring; there is a replaceFirst() method but it only takes regexp instead of a regular string. I have two concerns with using regex:

1) my substring is dynamic, so might contain weird characters that mean something else in regex, and I don't want deal with character escaping.

2) this replacement happens very often, and I'm not sure whether using regex will impact performance. I can't compile the regex beforehand since the regex itself is dynamic!

I must be missing something here since this seems to me is a very basic thing... Is there a replaceFirst method taking regular string somewhere else in the java franework?

1
  • 3
    Your second concern may be valid, but as for your first point: you can use Pattern.quote to construct a regex that will literally match a given string. That is, it'll deal with the escaping for you. Commented Oct 24, 2009 at 5:43

7 Answers 7

36

You should use already tested and well documented libraries in favor of writing your own code!

StringUtils.replaceOnce("aba", "a", "")    = "ba"

The StringUtils class is from Apache Commons Lang3 package and can be imported in Maven like this:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.8.1</version>
</dependency>
Sign up to request clarification or add additional context in comments.

1 Comment

you are suggesting to include a whole library for a simple string replacement . Do you think it's worth? Forget about whether it;s worth or not.. Isn't it weird that just for a simple functionality , I have to go for a new library altogether.
32

As Laurence suggested, you can use Pattern.quote like this:

newString = string.replaceFirst(Pattern.quote(substring),
                                Matcher.quoteReplacement(replacement));

quote creates a regex that literally matches the substring, and quoteReplacement creates a literal replacement string.

Another approach is simply to compile the substring as a literal regex like this:

newString = Pattern.compile(substring, Pattern.LITERAL).
        matcher(string).replaceFirst(Matcher.quoteReplacement(replacement));

This might be slightly more efficient, but also a bit less clear.

You could also do it manually, since you're only wanting to replace the first occurrence. But regexes are pretty efficient, so don't optimise prematurely!

2 Comments

this fails to mention that the replacement also needs to be quoted.
@Clashsoft Good point (also made by others) - I have updated the answer.
17

Use bigString.indexof(smallString) to get the index of the first occurrence of the small string in the big one (or -1 if none, in which case you're done). Then, use bigString.substring to get the pieces of the big string before and after the match, and finally concat to put those before and after pieces back together, with your intended replacement in the middle.

3 Comments

amen - this answer demonstrates a key principle of s/w development: just because you have a tool, doesn't mean you have to use it. I can't tell you how many times I see mid-level programmers sort list to find the smallest element.
That's a good comment. But note that this manual approach takes several lines of code, which may require comments and unit tests to avoid bugs. Using a single line `replaceFirst' call is clearer and less error-prone, even though it may also be slower at runtime. Don't optimise prematurely.
I am amazed that there is not be a simple library function encoding the complex algorithm mentioned! See this for example (python): stackoverflow.com/a/4628646/243233
7

make sure to also replace \s and $s in the replacement as well. This is probably what you want because you can't have any subgroups with yours ()'s being removed).

newStr = str.replaceFirst(Pattern.quote(target), Matcher.quoteReplacement(replacement));

1 Comment

You have a good point here, but you don't directly answer to the question. It would be more appropriate as a comment on Bennet McElwee's answer.
5

Solution with Pattern

You could also change the String.replace method that uses the literal interpretation of characters to replace only the first occurrence of the target character sequence:

/**
 * Replaces the first subsequence of the <tt>source</tt> character sequence
 * that matches the literal target sequence with the specified literal
 * replacement sequence.
 * 
 * @param source source sequence on which the replacement is made
 * @param target the sequence of char values to be replaced
 * @param replacement the replacement sequence of char values
 * @return the resulting string
 */
private static String replaceFirst1(CharSequence source, CharSequence target, CharSequence replacement) {
    return Pattern.compile(target.toString(), Pattern.LITERAL).matcher(
        source).replaceFirst(Matcher.quoteReplacement(replacement.toString()));
}


From the documentation of Pattern.LITERAL:

When this flag is specified then the input string that specifies the pattern is treated as a sequence of literal characters. Metacharacters or escape sequences in the input sequence will be given no special meaning.


Solution without Pattern

The other and more efficient way, of course, is to use Alex Martelli's hint to produce the following functionality:

/**
 * Replaces the first subsequence of the <tt>source</tt> string that matches
 * the literal target string with the specified literal replacement string.
 * 
 * @param source source string on which the replacement is made
 * @param target the string to be replaced
 * @param replacement the replacement string
 * @return the resulting string
 */
private static String replaceFirst2(String source, String target, String replacement) {
    int index = source.indexOf(target);
    if (index == -1) {
        return source;
    }

    return source.substring(0, index)
        .concat(replacement)
        .concat(source.substring(index+target.length()));
}


Time measurement

Based on 10 runs, the replaceFirst2 method executes about 5 times faster than the replaceFirst1 method. I have put both of these methods in a loop with 100.000 repeats and got the following results, in milliseconds:

    Method                 Results (in ms)             Average
replaceFirst1: 220 187 249 186 199 211 172 199 281 199 | 210
replaceFirst2:  40  39  58  45  48  40  42  42  43  59 |  45

Comments

1

StringBuilder class in java can be used very easily to do a non-regex based replacement of one string with another.

private static String replace(String in, String ths, String that) {
    
    StringBuilder sb = new StringBuilder(in);

    int idx = sb.indexOf(ths); 
    
    while (idx > -1) {
        sb.replace(idx, idx + ths.length(), that);
        idx = sb.indexOf(ths);
    }
    
    return sb.toString(); 
    
}

2 Comments

this does the same as String.replace, i.e. replace all occurrences
@Clashsoft replace while loop with if condition then it will replace only the first occurrence.
0

Pattern.quote does not seem to work in all cases. Try `Pattern.quote(":-)");

1 Comment

That would be more fitted as a comment to the Laurent Gonsalves's comment on the question or on Bennet McElwee's answer than as a separate answer, since you don't address the question itself.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.